Swift 5使用method swizzling

5,679 阅读1分钟

Method swizzling就是在运行时改变方法的实现。

Method swizzling is the process of changing the implementation of an existing selector at runtime. Simply speaking, we can change the functionality of a method at runtime.

OC中的Method是一个C结构体,定义如下:

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
} 

回顾一下OC中使用method swizzling

@interface Father : NSObject
@end
@implementation Father
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class aClass = [self class];
        SEL originSelector = @selector(makeMoney);
        SEL swizzledSelector = @selector(swizzle_makeMoney);
        Method originMethd = class_getInstanceMethod(aClass, originSelector);
        Method swizzleMethod = class_getInstanceMethod(aClass, swizzledSelector);
        BOOL didAdddMethod = class_addMethod(aClass, originSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
        if (didAdddMethod) {
            class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originMethd), method_getTypeEncoding(originMethd));
        } else {
            method_exchangeImplementations(originMethd, swizzleMethod);
        }
    });
}
- (void)makeMoney { NSLog(@"make money"); }
- (void)swizzle_makeMoney { NSLog(@"have a rest and make money"); }

Swift中使用 method swizzling

  1. 传统方法 要在Swift自定义类中使用method swizzling有两个必要条件
    • 包含swizzle方法的类,需继承自NSObject
    • 需要swizzle的方法必须有动态属性dynamic
   class Father: NSObject {
       @objc dynamic func makeMoney() {
           print("make money")
       }
   }
   extension Father {
       static func swizzle() {
           let originSelector = #selector(Father.makeMoney)
           let swizzleSelector = #selector(Father.swizzle_makeMoney)
           let originMethod = class_getInstanceMethod(Father.self, originSelector)
           let swizzleMethod = class_getInstanceMethod(Father.self, swizzleSelector)
           method_exchangeImplementations(originMethod!, swizzleMethod!)
       }
       @objc func swizzle_makeMoney() {
           print("have a rest and make money")
       }
   }
   Father.swizzle()
   var tmp = Father()
   tmp.makeMoney() //  have a rest and make money
   tmp.swizzle_makeMoney() // make money
  1. 使用@_dynamicReplacement(for: )实现
    class Father {
       dynamic func makeMoney() {
           print("make money")
       }
    }
    extension Father {
       @_dynamicReplacement(for: makeMoney())
       func swizzle_makeMoney() {
           print("have a rest and make money")
       }
    }
    Father().makeMoney() // have a rest and make money
    

实现原理

method swizzling都是利用runtime来获取selector的实现,来动态改变。Swift通过OC的runtime特性来实现。

在Swift中使用dynamic属性来标记方法,使该方法只在运行期编译,以达到动态派发

References

  1. Objective-C-Method-Swizzling
  2. Apple: using objective-c runtime features in Swift
  3. Swift 5 之后 "Method Swizzling"?: 介绍了 @_dynamicReplacement(for: )连环Hook的场景