给定两个方法A和B,我想交换它们的实现,这样调用A就会执行B.我偶然发现了一些混合的例子(example1和example2).我创建了一个带有类的新项目来测试它.
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
解决方法
class Swizzle: NSObject { dynamic func method() { print("A") } }
修改声明,它应该工作.
在Swift中使用方法调配时,您的类/方法必须符合以下两个要求:
>您的课程必须扩展NSObject
>您想要调配的函数必须具有动态属性
有关为何需要此操作的完整说明,请查看Using Swift with Cocoa and Objective-C:
Requiring Dynamic Dispatch
While the
@objc
attribute exposes your Swift API to the Objective-C
runtime,it does not guarantee dynamic dispatch of a property,method,
subscript,or initializer. The Swift compiler may still devirtualize
or inline member access to optimize the performance of your code,
bypassing the Objective-C runtime. When you mark a member declaration
with thedynamic
modifier,access to that member is always dynamically
dispatched. Because declarations marked with thedynamic
modifier are
dispatched using the Objective-C runtime,they’re implicitly marked
with the@objc
attribute.Requiring dynamic dispatch is rarely necessary. However,you must use
thedynamic
modifier when you know that the implementation of an API
is replaced at runtime. For example,you can use the
method_exchangeImplementations
function in the Objective-C runtime to
swap out the implementation of a method while an app is running. If
the Swift compiler inlined the implementation of the method or
devirtualized access to it,the new implementation would not be used.
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,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())
如果您需要一个示例,请查看此示例项目on github.