26·iOS 面试题·什么是 Method Swizzling?
前言
Objective-C 是一门动态语言,在运行时期才能真正具体确定调用哪个函数。运行时,方法的大概调用逻辑如下:
在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。
Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。
简单来说 Method Swizzling 就是替换方法列表中的 selector 对应的 IMP,达到方法交互的目的。
Method Swizzling 的实现方案
1、使用 class_addMethod、class_replaceMethod、method_exchangeImplementations
可以使用系统提供的方法来操作对象的方法列表,具体实现代码如下:
SEL originalSeletor = originalSelector;
SEL swizzledSeletor = swizzledSelector;
Method originalMethod = class_getInstanceMethod(originalClass, originalSeletor);
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSeletor);
if (!isInstanceMethod) {
originalMethod = class_getClassMethod(originalClass, originalSeletor);
swizzledMethod = class_getClassMethod(swizzledClass, swizzledSeletor);
}
//先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
BOOL didAddMethod = class_addMethod(originalClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
// class_addMethod 添加成功则说明:originalClass 没有实现 originalSelector 对应的方法,并且将此时已经将 originalSelector 对应实现改成 swizzledMethod 的实现。
//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
class_replaceMethod(swizzledClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
// class_addMethod 添加失败则说明:originalClass 已经实现 originalSelector 对应的方法,不能再加了,这里直接交换实现就可以了。
//添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
method_exchangeImplementations(originalMethod, swizzledMethod);
}
2、使用 RSSwizzle 库
对于上面的实现方式,在一般情况下是OK的,但是在场景特别复杂的时候会出现异常,例如子类、父类、分类多个类同时对一个方法进行交换,这里可能会出现问题,具体比较可以参阅:Objective-C Method Swizzling、iOS 界的毒瘤:Method Swizzle 中的例子。
RSSwizzle的主要思路是 hook 本类没有实现的方法(继承了父类的方法),不会去copy父类的实现并添加方法到本类中,而是在消息触发时动态去父类中查找以调用原来的实现。
3、如何选择
个人觉得对于小范围功能,并且可以确保不会多次替换方法,可以使用方案一直接进行方法替换;但是对于复杂的场景,推荐使用 RSSwizzle 库来进行方法替换。
Method Swizzling 注意事项
Method Swizzling 是一把双刃剑,使用不当也会给项目带来隐患,这里提一些注意事项:
1、尽量在 +load 方法中,并且用 dispatch_once 来确保交换顺序和线程安全
2、对于 Hook 的方法,需要调用原有实现,避免覆盖原有的方法,导致原有的功能缺失
3、方法命名推荐使用前缀,避免与原方法名字冲突
总结
Method Swizzling 相对于是比较底层的技术,平时开发中使用的不多,但是遇到特定的场景 Method Swizzling 却可以很优雅的解决问题;我们应该掌握 Method Swizzling 的实现原理,避免在项目中滥用,不然会导致很难排查的 Bug。