Method_Swizzling使用过程中遇到的坑

209 阅读1分钟

在使用Method_swizlling黑魔法,首先要考虑一个问题,什么时操作呢?

load方法

为什么要在load方法的时候使用呢,具体有以下几个原因:

  1. 执行比较早,在main函数之前调用。(具体是可以参考load调用过程应用加载过程初探 (三)的load_images分析中有描述)
  2. 自动执行
  3. 保证执行过程唯一且不会覆盖

遇到的坑

坑一

针对NSArray访问数组越界的处理,我们需要找准真正的方法归属NSArray -> 类簇 -> NSArrayI。

同时,交换的方法包括objectAtIndex:和objectAtIndexedSubscript:这两个。

坑二

如果在进行交换方法交换前,执行load方法,会导致方法交换失败。所以,我们可以通过单例的方式保证只进行交换一次。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        [XLRuntimeTool xl_bestMethodSwizzlingWithClass:self oriSEL:@selector(instanceMethod) swizzledSEL:@selector(xl_instanceMethod)];

    });
}

坑三

当子类没有实现父类的方法,我们通过子类进行交换时,因为是继承的关系,所以针对交换的是父类的方法,由于父类没有swizzling的方法,结果会导致crash。 这时候我们处理的方法是:尝试给自己添加要交换的方法,然后将父类的imp给swizle

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
  
    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}

坑四

交换没有实现的方法,我们需要添加一个老方法编号的实现,把swiMethod的具体的实现赋值一个空实现,防止递归。

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    if (!oriMethod) {
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }

    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }