在使用Method_swizlling黑魔法,首先要考虑一个问题,什么时操作呢?
load方法
为什么要在load方法的时候使用呢,具体有以下几个原因:
- 执行比较早,在main函数之前调用。(具体是可以参考load调用过程应用加载过程初探 (三)的load_images分析中有描述)
- 自动执行
- 保证执行过程唯一且不会覆盖
遇到的坑
坑一
针对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);
}