前言
在平时开发过程中我们好多时候都需要进行方法的交换,就会用到Method Swizzling
方法,但是用的时候我们知道它的原理是什么嘛?那么以下就进行详细的分析。
方法交换的原理
iOS中每一个继承于NSObject
的类都能自动获得runtime
的支持。在这样的一个类中,有一个isa
指针,指向该类定义的数据结构体,这个结构体是由编译器编译时
为类创建的。在这个结构体中又包括了,指向其父类类定义的指针
以及Dispatch table
。Dispatch table
是一张SEL
和IMP
的对应表。
方法编号SEL
最后还是要通过Dispatch table
表寻找到对应的IMP
,IMP
就是一个函数指针
,然后执行这个方法。
- 方法编号
SEL
和方法实现IMP
的对应关系
- 方法
交换后
对应关系
-
selectorC
的方法实现变成了IMPn
-
selectorN
的方法实现变成了IMPc
最后调用selectorC
方法实现selectorN
方法,调用selectorN
方法实现了selectorC
方法,从而实现了方法的交换
。
方法交换的方式
// 类中获取oriSEL对应的方法实现
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
// 获取swiSEL对应的方法实现
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 将两个方法实现进行交换,
method_exchangeImplementations(oriMethod, swiMethod);
注意:在进行方法交换操作时,建议放在单例
下进行,避免重复调用导致没效果,因为两者再交换一次的换就会 换回原来的IMP
方法交换案例分析
递归问题分析
创建一个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
以上代码会出现递归的现象吗?那么我们运行代码看看:
有运行结果可以看出
并没有出现递归
的现象!!
因为进行了方法交换,所以调用对象方法
studentInstanceMethod
,会找到lg_studentInstanceMethod
的方法实现,而lg_studentInstanceMethod
中有调用lg_studentInstanceMethod
,而此时它的方法实现已经指向了studentInstanceMethod
。见下图:
交换父类的方法
创建一个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
方法的实现。消息查找是不能父类找子类的
,所以就报找不到方法的错误出来。
注意:
在开发过程中我们一定要注意方法是否已经实现,不然的话就会出的先案例中的崩溃情况(方法交换,而父类没有方法的实现,导致报错),在方法交换的时候尽量避免涉及到子类根父类的方法!!
方法交换设计思路
通过上面案例的分析总结出以下的方法交换的设计思路:
+ (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);
}
}