前言
MethodSwizzing
方法交换是比较常用的所谓 黑魔法
。但正如武侠小说中的绝世武功一般,也存在使用不恰当发生 伤敌一千,自损八百
的情况。
本文就带你来探索一下其中的坑,避免走火入魔。
一、 MethodSwizzingTool
为了方便,我们封装一下常规的方法交换逻辑
@implementation MethodSwizzingTool
+ (void)swizzingClass:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
if (!cls) { return; }
Method oldM = class_getInstanceMethod(cls, oldSel);
Method newM = class_getInstanceMethod(cls, newSel);
method_exchangeImplementations(oldM, newM);
}
@end
1.1 验证是否有效
@implementation RYModel
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[MethodSwizzingTool swizzingClass:self oldSEL:@selector(functionA) toNewSel:@selector(functionB)];
});
}
- (void)functionA {
NSLog(@"%s", __func__);
}
- (void)functionB {
NSLog(@"%s", __func__);
[self functionB];
}
@end
调用:
RYModel *ry = [[RYModel alloc] init];
[ry functionA];
输出:
2021-08-07 17:01:53.954201+0800 MethodSwizzing[72309:15057493] -[RYModel functionB]
2021-08-07 17:01:53.954534+0800 MethodSwizzing[72309:15057493] -[RYModel functionA]
嗯~感觉封装的没问题。
二、 子类的坑
2.1 用自类方法替换父类方法,会怎样?
思考:
在子类 RYSubModel
中用 子类
的 subFunctionA
替换 父类
的 functionA
,子类实例
和父类实例
分别调用 functionA
会是什么样的结果呢?(父类中未做交换)
子类代码如下:
@implementation RYSubModel
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[MethodSwizzingTool swizzingClass:self oldSEL:@selector(functionA) toNewSel:@selector(subFunctionA)];
});
}
- (void)subFunctionA {
NSLog(@"%s", __func__);
}
@end
调用:
RYModel *ry = [[RYModel alloc] init];
[ry functionA];
RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
输出:
2021-08-07 17:10:37.990097+0800 MethodSwizzing[72705:15063434] -[RYSubModel subFunctionA]
2021-08-07 17:10:37.990530+0800 MethodSwizzing[72705:15063434] -[RYSubModel subFunctionA]
分析
用子类中的方法 subFunctionA
替换父类中的方法 functionA
, functionA
的实现变成了 subFunctionA
。
2.2 调用原实现
一般我们交换方法后想要继续调用原本的实现一般会如上文中functionB那样调用一下自己。
那么我们在用子类中的方法 subFunctionA
替换了父类中的方法 functionA
后想要继续调用 functionA
同理应该这么写:
- (void)subFunctionA {
[self subFunctionA];
NSLog(@"%s", __func__);
}
调用:
RYModel *ry = [[RYModel alloc] init];
[ry functionA];
RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
输出:
为什么呢?居然找不到了
分析
我们在用子类中的方法 subFunctionA
替换了父类中的方法 functionA
后
父类中:
functionA
调用 subFunctionA
但是父类本身方法列表中并没有 subFunctionA
,所以父类就报了 unrecognized selector
的错误。
修改调用
如果我们只调用子类:
RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
输出:
2021-08-07 17:33:28.874108+0800 MethodSwizzing[73690:15075457] -[RYModel functionA]
2021-08-07 17:33:28.874564+0800 MethodSwizzing[73690:15075457] -[RYSubModel subFunctionA]
这里就是正常的
三、 优化 MethodSwizzingTool
我们能不能优化 MethodSwizzingTool
来防止这样的问题出现呢?
可以!
3.1 优化思路
出现上面找不到方法的原因是:子类用自己的实现直接替换了父类的方法
那么我们能不能为子类动态添加一个和父类一样的方法呢?子类中进行替换的时候就不会影响父类了
3.2 编写优化代码
+ (void)swizzingClassB:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
if (!cls) { return; }
Method oldM = class_getInstanceMethod(cls, oldSel);
Method newM = class_getInstanceMethod(cls, newSel);
// 先尝试给 cls 添加方法(SEL: oldSel IMP: newM),防止子类直接替换父类中的方法
BOOL addSuccess = class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(oldM));
if (addSuccess) { // 添加成功即:原本没有 oldSel,成功为子类添加了一个 oldSel - newM 的方法
// 这里将原 newSel的imp替换为 oldM 的 IMP
class_replaceMethod(cls, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
}
else {
method_exchangeImplementations(oldM, newM);
}
}
3.3 优化代码流程分析
本案例子类调用流程分析:
调用方法,准备用子类中的方法 subFunctionA
替换的方法 functionA
Method oldM
是从父类获取到的方法 (SEL: functionA, IMP: functionA)
Method newM
是从子类自己获取到的方法 (SEL: subFunctionA, IMP: subFunctionA)
是否可以成功添加方法:(SEL: functionA, IMP: subFunctionA)
是:不存在 SEL: functionA
的方法
将子类的 (SEL: subFunctionA, IMP: subFunctionA)
替换为 (SEL: subFunctionA, IMP: functionA)
四、 父类的 functionA 也没有实现呢?
4.1 分析
由于并不存在 functionA
的实现,所以这里的替换方法并没有成功。 subFunctionA
的调用就直接递归死循环了。
4.2 再优化
通过设置默认实现的方式来避免死循环,新实现也为空的情景可以类似处理,这里就不赘述了。
+ (void)swizzingClassB:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
if (!cls) { return; }
Method oldM = class_getInstanceMethod(cls, oldSel);
Method newM = class_getInstanceMethod(cls, newSel);
if (!oldM) {
// 先用新的实现来,临时添加一个(这里忽略新实现也为空的情况,可以类似的处理)
class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
// 对 oldM 变量重新赋值
oldM = class_getInstanceMethod(cls, oldSel);
// 创建默认实现,可以进行一些日志收集之类的
IMP defaultIMP = imp_implementationWithBlock(^(id self, SEL _cmd){
NSLog(@"一些处理");
});
// 为 oldM 设置 IMP
method_setImplementation(oldM, defaultIMP);
}
// 先尝试给 cls 添加方法(SEL: oldSel IMP: newM),防止子类直接替换父类中的方法
BOOL addSuccess = class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(oldM));
if (addSuccess) { // 添加成功即:原本没有 oldSel,成功为子类添加了一个 oldSel - newM 的方法
// 这里将原 newSel的imp替换为 oldM 的 IMP
class_replaceMethod(cls, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
}
else {
method_exchangeImplementations(oldM, newM);
}
}
总结
黑魔法虽好,使用也得倍加小心!
尤其要对方法的本质以及方法调用的流程烂熟于心才能随心所欲,内功到家,进阶武功才不会伤及自身。