1.方法交换的原理
每一个继承于NSObject的类都能自动获得runtime的支持。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类创建的。在这个结构体中又包括了,指向其父类类定义的指针以及Dispatch table。Dispatch table是一张SEL和IMP的对应表。
也就是说方法编号SEL最后还是要通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法。
-
方法编号SEL和方法实现IMP的对应关系 -
方法交换后对应关系
oriSEL的方法实现变成了swiIMPswiSEL的方法实现变成了oriIMP
也就是调用
oriSEL方法,最终方法实现是swiIMP。 -
方法交换的方式
// 类中获取oriSEL对应的方法实现 Method oriMethod = class_getInstanceMethod(cls, oriSEL); // 获取swiSEL对应的方法实现 Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); // 将两个方法实现进行交换, method_exchangeImplementations(oriMethod, swiMethod);在进行方法交换操作时,建议放在单例下进行,避免重复调用导致交换了个寂寞。通过上面的方法可以理解,交换的是两者的方法实现。
2.方法交换案例分析
1.递归问题分析
-
案例描述
创建一个
LGStudent类,类中有两个实例方法,lg_studentInstanceMethod和studentInstanceMethod,在load方法中对两个方法进行交换,同时,lg_studentInstanceMethod的实现中再次调用lg_studentInstanceMethod方法。 -
实现代码
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGStudent *s = [[LGStudent alloc] init]; [s studentInstanceMethod]; @end @implementation LGStudent + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"方法交换---:%s", __func__); Method oriIMP = class_getInstanceMethod(self, @selector(studentInstanceMethod)); Method swiIMP = class_getInstanceMethod(self, @selector(lg_studentInstanceMethod)); method_exchangeImplementations(oriIMP, swiIMP); }); } // 是否递归 - (void)lg_studentInstanceMethod{ [self lg_studentInstanceMethod]; NSLog(@"LGStudent对象方法:%s", __func__); } - (void)studentInstanceMethod{ NSLog(@"LGStudent对类方法:%s", __func__); } @end -
问题分析
在
lg_studentInstanceMethod中再次调用该方法,是否会引起递归调用呢?运行程序看看结果,见下图:
分析:并没有引起递归,因为进行了方法交换,所以调用对象方法
studentInstanceMethod,会找到lg_studentInstanceMethod的方法实现,而lg_studentInstanceMethod中有调用lg_studentInstanceMethod,而此时它的方法实现已经指向了studentInstanceMethod。见下图:
2.交换父类的方法
-
案例描述
创建一个
LGStudent类,类中有一个实例方法,lg_studentInstanceMethod,其父类LGPerson中有一个实例方法personInstanceMethod,在LGStudent类的load方法中对进行方法交换,将lg_studentInstanceMethod方法交换成父类中的personInstanceMethod方法。 -
实现代码
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGStudent *s = [[LGStudent alloc] init]; // 调用父类中的方法 [s personInstanceMethod]; @end // LGPerson是父类 @implementation LGPerson - (void)personInstanceMethod{ NSLog(@"person对象方法:%s",__func__); } @end // LGStudent是子类 @implementation LGStudent + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"方法交换---:%s", __func__); Method oriIMP = class_getInstanceMethod(self, @selector(personInstanceMethod)); Method swiIMP = class_getInstanceMethod(self, @selector(lg_studentInstanceMethod)); method_exchangeImplementations(oriIMP, swiIMP); }); } // 是否递归 - (void)lg_studentInstanceMethod{ [self lg_studentInstanceMethod]; NSLog(@"LGStudent对象方法:%s", __func__); } @end -
问题分析
LGStudent对象是否能够成功调用personInstanceMethod方法?运行程序看看结果,见下图:
分析:成功调用,因为子类对象调用父类方法
personInstanceMethod,我们在学习消息发送的原理时已经知道,其会进行慢速方法查找找到父类方法。但是此时父类方法对应的方法实现已经被交换成了,子类的lg_studentInstanceMethod方法,所有会执行子类的lg_studentInstanceMethod方法实现。于此同时子类中调用lg_studentInstanceMethod方法,最终的方法实现是父类的personInstanceMethod方法。 -
案例扩展
如果调用父类对象的
personInstanceMethod方法会怎样呢?运行程序看看结果,见下图:
分析:报错,原因是什么呢,首先父类调换用
personInstanceMethod方法会执行子类中的lg_studentInstanceMethod实现,但是但是此时又调用lg_studentInstanceMethod方法,而此时的调用者是LGPerson对象,父类中并没有lg_studentInstanceMethod方法的实现。所以方法找不到,进而报错。分析:
在开发中,如果进行方法交换,一定要确保方法已经实现,否则会出现本例中啃爹的现象(方法交换,而父类没有方法的实现,导致报错)。所以在进行相关方法交换时,尽量避免涉及到其父类或者其子类的方法。
3.方法交换设计思路
通过上面的案例,为避免上面案例的问题,总结以下实现思路:
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
// 获取类中的方法
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
// 要被交换的方法
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 判断类中是否存在该方法-避免动作没有意义
if (!oriMethod) {
// 在oriMethod为nil时,添加oriSEL的方法,实现为swiMethod
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
// 替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
NSLog(@"来了一个空的 imp");
}));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
// 向类中添加oriSEL方法,方法实现为swiMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
// 自己有意味添加方法失败-所以这里会是false
if (didAddMethod) {
// 如果添加成功,表示原本没有oriMethod方法,此时将swizzledSEL的方法实现,替换成oriMethod实现
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
// 方法交换
method_exchangeImplementations(oriMethod, swiMethod);
}
}