方法Swizzling不起作用

我想利用方法调整,但我无法得到甚至简单的例子为我工作。 我可能误解了这个概念,但据我所知,它允许交换方法实现。

给定两个方法,A和B,我想交换他们的实现,调用A会执行B代替。 我偶然发现了几个例子( example1和example2 )。 我用一个类创build了一个新项目来testing这个。

class Swizzle: NSObject { func method() { print("A"); } } extension Swizzle { override class func initialize() { struct Static { static var token: dispatch_once_t = 0; } // make sure this isn't a subclass if (self !== Swizzle.self) { return; } dispatch_once(&Static.token) { let originalSelector = Selector("method"); let swizzledSelector = Selector("methodExt"); let originalMethod = class_getInstanceMethod(self, originalSelector); let swizzledMethod = class_getInstanceMethod(self, swizzledSelector); print(method_getImplementation(originalMethod)); print(method_getImplementation(swizzledMethod)); let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if didAddMethod { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } print(method_getImplementation(originalMethod)); print(method_getImplementation(swizzledMethod)); } } func methodExt() { print("B"); } } 

然后我尝试执行它

 var s = Swizzle(); s.method(); 

预期的输出是“B”,但“A”仍在打印。 从我的代码中可以看到,我在swizzle操作之前和之后都包含了每个IMP打印。 这些印刷品显示交换确实发生,但输出保持不变。

输出:

 0x000000010251a920 0x000000010251ad40 0x000000010251ad40 0x000000010251a920 A 

有什么我失踪,当涉及到使这些更改生效?

PS。 目前使用XCode 7.0.1

问题是你的method()缺lessdynamic指令:

 class Swizzle: NSObject { dynamic func method() { print("A") } } 

修改声明,它应该工作。

在Swift中使用方法swizzling时,有两个要求,你的类/方法必须符合:

  • 你的类必须扩展NSObject
  • 您要调整的function必须具有dynamic属性

有关为什么这是必需的完整说明,请查看使用cocoa和Objective-C的Swift :

需要dynamic调度

虽然@objc属性将您的Swift API公开到Objective-C运行时,但它不保证dynamic分派属性,方法,下标或初始值设定项。 Swift编译器仍然可以通过虚拟化或内联成员访问来绕过Objective-C运行时来优化代码的性能 。 使用dynamic修饰符标记成员声明时,对该成员的访问始终是dynamic分派的。 因为使用dynamic修饰符标记的声明是使用Objective-C运行时分派的,所以它们被隐式标记为@objc属性。

很less需要dynamic调度。 但是,当您知道在运行时replaceAPI的实现时,必须使用dynamic修饰符 。 例如,您可以使用Objective-C运行时的method_exchangeImplementations函数在应用程序运行时交换方法的实现。 如果Swift编译器内联了方法的实现或者虚拟化访问, 那么新的实现将不会被使用

Swift 3更新:

关于GCD有一些变化, dispatch_once不再可用。 要执行相同的一次操作,我们可以将代码放在全局静态类常量的初始化块中。

Swift语言保证这个代码在应用程序的生命周期中只会被执行一次。

 class TestSwizzling : NSObject { dynamic func methodOne()->Int{ return 1 } } extension TestSwizzling { //In Objective-C you'd perform the swizzling in load(), //but this method is not permitted in Swift override class func initialize() { struct Inner { static let i: () = { let originalSelector = #selector(TestSwizzling.methodOne) let swizzledSelector = #selector(TestSwizzling.methodTwo) let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector); let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector) method_exchangeImplementations(originalMethod, swizzledMethod) } } let _ = Inner.i } func methodTwo()->Int{ // It will not be a recursive call anymore after the swizzling return methodTwo()+1 } } var c = TestSwizzling() print(c.methodOne()) print(c.methodTwo()) 

Swift 2.2更新:

我已经更新了新的#selector属性的原始示例:

 class TestSwizzling : NSObject { dynamic func methodOne()->Int{ return 1 } } extension TestSwizzling { //In Objective-C you'd perform the swizzling in load(), //but this method is not permitted in Swift override class func initialize() { struct Static { static var token: dispatch_once_t = 0 } // Perform this one time only dispatch_once(&Static.token) { let originalSelector = #selector(TestSwizzling.methodOne) let swizzledSelector = #selector(TestSwizzling.methodTwo) let originalMethod = class_getInstanceMethod(self, originalSelector); let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) method_exchangeImplementations(originalMethod, swizzledMethod) } } func methodTwo()->Int{ // It will not be a recursive call anymore after the swizzling return methodTwo()+1 } } var c = TestSwizzling() print(c.methodOne()) print(c.methodTwo()) 

如果你需要一个例子来玩,请在github上查看这个示例项目。