前言
- 作为一个
iOS开发,肯定或多或少的听到过iOS黑魔法,也就是方法交换,但这个过程是怎样的,原理又是怎样的,有什么应用场景呢?本文将一一探究这些问题
简介
Method Swizzling我们通常称之为黑魔法,通俗的来讲就是方法交换。- 每个类都有一个方法列表,列表中每个方法的
SEL和IMP是对应关系,方法交换就是使SEL对应新的IMP,如下图所示:
案例分析
方法交换
创建一个类WSPerson,然后创建WSTeacher继承WSPerson:
// WSPerson .h
@interface WSPerson : NSObject
- (void)person_instanceMethod;
@end
// WSPerson.m
@implementation WSPerson
- (void)person_instanceMethod {
NSLog(@"\n🎈print person_instanceMethod: %s\n", __func__);
}
@end
// WSTeacher.h
@implementation WSTeacher
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WSRuntimeUtil ws_methodSwizzlingWithClass:self
oriSEL:@selector(person_instanceMethod)
swizzledSEL:@selector(teacher_instanceMethod)];
});
}
- (void)teacher_instanceMethod {
NSLog(@"\n🎉 print teacher_instanceMethod: %s\n", __func__);
}
@end
// WSRuntimeUtil.m
+ (void)ws_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swizzleMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swizzleMethod);
}
然后两个类创建对象分别去调用person_instanceMethod方法:
运行结果都打印了teacher_instanceMethod方法
- 因为方法交换后
person_instanceMethod指向了teacher_instanceMethod的IMP,进而找到了teacher_instanceMethod的方法; - 而
teacher_instanceMethod指向了person_instanceMethod的IMP,但IMP对应的是person_instanceMethod方法,再继续根据person_instanceMethod方法去找IMP,就找到了交换后的IMP,进而找到了teacher_instanceMethod方法 - 过程如下:
递归问题
将teacher_instanceMethod方法稍作修改:
- (void)teacher_instanceMethod {
[self teacher_instanceMethod];
NSLog(@"\n🎉 print teacher_instanceMethod: %s\n", __func__);
}
再运行,结果如下:
结果出现[WSPerson teacher_instanceMethod],所以就出现异常找不到方法
- 当
[person person_instanceMethod]时,由于方法交换,person_instanceMethod找到了teacher_instanceMethod的IMP,由于消息的接受者是person,所以此时[self teacher_instanceMethod]相当于[person teacher_instanceMethod],因为WSPerson类没有teacher_instanceMethod方法,所以就出现异常了 - 为什么要调用自己呢,是因为有时候做一些处理的时候,需要保持原来的逻辑,所以需要再次调用自己,那怎样才能避免这种异常呢?可以通过
class_addMethod去尝试添加要交换的方法
优化一
- 使用
class_addMethod去尝试添加要交换的方法,如果添加成功,即类中没有这个方法,则通过class_replaceMethod进行替换,其内部会调用class_addMethod进行添加 - 如果添加不成功,即类中有这个方法,则通过
method_exchangeImplementations进行交换 - 优化代码如下:
+ (void)ws_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 尝试添加你要交换的方法
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {// 自己没有 - 替换 - 没有父类进行处理 (重写一个)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else { // 自己有的话就
method_exchangeImplementations(oriMethod, swiMethod);
}
}
优化二
- 如果子类和父类都没有实现
person_instanceMethod方法,调用[self teacher_instanceMethod]时就会产生递归,如果方法不存在,可以在添加方法后给她添加一个空的实现,也就是增加一个不做任何事情的IMP:
- 这样就避免了因都未实现方法而导致的递归