标准的Method Swizzling定义

标准的Method Swizzling定义

之前,我在Category中实现Method Swizzling 时,会使用如下方式定义:

+ (void)load {
        Class class = [self class];
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(cz_viewWillAppear:);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        method_exchangeImplementations(originalMethod, swizzledMethod);
}

但是在看公司项目代码时是下面这种方式实现的:

void MoerSwizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector)
{
    Method originalMethod = class_getInstanceMethod(cls, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
    
    BOOL didAddMethod =
    class_addMethod(cls,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(cls,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

比我之前那种方式在方法交换前多了两个函数调用class_addMethod()class_replaceMethod。刚开始搞不懂为什么要这样处理,然后就开始写代码自己测试,最终搞明白了。举个不太恰当的例子,比如定义了一个BaseViewController,继承自UIViewController,在BaseViewController中没有实现viewWillAppear:方法,但是此时又定义了一个BaseViewController的Category,在这个Category中,定义了一个私有的xx_viewWillAppear:方法,并且在load方法中实现了按照上文第一种方式实现了Method Swizzling。此时,由于BaseViewController中没有定义viewWillAppear:方法,所以通过Method originalMethod = class_getInstanceMethod(class, originalSelector);该方式得到的Method是从父类UIViewController中拿到的。在method_exchangeImplementations后,会将Category中定义的方法实现xx_viewWillAppear:与父类UIViewController中的viewWillAppear:的实现进行交换。此后,当你再次调用UIViewController中的viewWillAppear时,都是执行xx_viewWillAppear:的方法实现。

而“摩尔”中的方式则会避免的这种情况的发生。首先如果BaseViewController:中没有实现viewWillAppear:方法,则Method originalMethod = class_getInstanceMethod(cls, originalSelector); 通过该方式的得到的Method是从父类UIViewController中拿到的,然后BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));这个方法的作用就是如果本类中没有实现viewWillAppear:,则会将父类拿到的方法实现添加到本类的方法列表中,并返回YES,如果本类已经有viewWillAppear:的方法实现,则返回NO,之后用便会执行正常的方法交换流程。

总结:标准的Method Swizzling实现

+ (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);
        }
    });
}

之所以加dispatch_once是为了防止有人手动调用load方法。

参考文章:
method swizzling

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。