Method Swizzling的方法

开发中总会遇到需要使用Method Swizzling的时候,记录一下Method Swizzling的正确方法

一、方法

以hookUIViewController 为例

#import 

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end

上面是一个通用的方式,会先尝试添加要hook的Method(以swizzledMethodIMP),如果添加成功,再把swizzledSelector的实现替换为originalMethod的,这样就完成了两个方法实现的互换。如果添加失败(即该class 已经存在对应Method),则直接交换两个Method的实现。

对于UIViewController 这样的已经确定有viewWillAppear: 方法的类,当然可以偷懒直接使用method_exchangeImplementations 来完成,但还是以上面的方式更为严谨。

二、+load vs. +initialize

Method Swizzling永远应该在+load方法中完成

+initialize,仅会在该类的方法或者它的实例第一次被调用前,+initialize 方法被调用。即它有可能不被调用。
+load 方法会在main 函数运行前调用,每个类、分类的load 方法都会被调用。被调用时,所有的 framework 都已经加载到了运行时中(但有的类可能还未加载)。在一个类的 load 方法中调用其他类的方法,如果被调用的类还未load,并不会触发被调用的类的load 方法。

三、dispatch_once

为了尽可能地保证Method Swizziling只进行一次,代码应该包含在dispatch_once 中。

四、第三方

GitHub上有类似Aspect 这样的第三方,封装了Method Swizzling 方法,并提供了其他功能,如选择代码在原方法前或后执行,或者是直接替换原方法。Hook一个对象、一个类等。
一些第三方统计明显也是使用了类似的方法来实现统计的功能。

五、参考

  1. Method Swizzling – Mattt Thompson,详细讲了Method Swizzling的使用。
  2. 你真的了解load方法么?– Draveness,解析了load方法的原理
  3. load 和 initialize 方法的执行顺序以及类和对象的关系,各个类load方法调用顺序会与compile source中文件顺序有关系

你可能感兴趣的:(Method Swizzling的方法)