Method Swizzling 是一种能将方法的实现替换掉的技术,运用的是 Objective-C 的运行时方法实现的。其实现的具体思路是将方法的 imp 进行对调。
原理
在 Objective-C 中,所有的方法都是动态绑定的。在 Objective-C 的类结构体中,维护着一张方法表(method list),记录着该类中所有方法的 sel 和 imp。在调用一个方法的时候,其实是通过 sel 寻找 对应的 imp,并调用imp。通过 Method Swizzling , 我们可以将两个方法的 imp 进行对调。
常规实现
以下是摘抄自 NSHipster 的一段实现
1 |
|
通过上面对 UIViewController 的扩展,所有 UIViewController 对象或者其子类的对象在调用 viewWillAppear
的时候,都会输出一段log。注意,上面的 -xxx_viewWillAppear :animated
方法不会产生死循环,因为[self xxx_viewWillAppear:animated];
实际上是在调用viewWillAppear:animated
方法。
+load vs. +initialize
+load
方法的特点是:
- 在类被加载时被调用
- 在main函数之前执行
- 子类的 +load 方法会在它的所有父类(不包括父类分类中的 +load )的 +load 方法执行之后执行
- 分类的 +load 方法会在主类的 +load 方法执行之后执行
+initialize
方法的特点是:
- 在类收到第一个函数调用前执行
为了保证 Method Swizzling 进行得足够早并且减少竞争情况的出现,应该将 Method Swizzling 的相关方法写在 +load
里面。
dispatch_once
为了确保 Method Swizzling 只进行一次,我们最好将相关代码写在dispatch_once里面。因为如果 Method Swizzling 进行了多次,我们压根不清楚现在调用的方法究竟是调换前的还是调换后的。
使用例子
模拟位置
滴滴开源的工具库 DoraemonKit 工具库中使用了 Method Swizzling 来模拟GPS位置。
下面来看看滴滴是怎么实现的。
实现一个 CLLocationManager 的分类,为 CLLocationManager 添加一个
doraemon_swizzleLocationDelegate:
方法,这个方法将作为替换后的setDelegate:
方法的实现。在 DoraemonGPSMocker 的 +load 方法中,将
CLLocationManager
的setDelegate:
实现改变。这样,当调用CLLocationManager
的setDelegate:
方法时,调用的将会是doraemon_swizzleLocationDelegate:
。doraemon_swizzleLocationDelegate :
的方法的实现是,首先将DoraemonGPSMocker
设置为 CLLocationManager 的 delegate。 并且将该实例和 delegate 记录下来。每当一个 CLLocationManager 调用 delegate 的方法,DoraemonGPSMocker 都会将该方法拦截下来。如果locationManager:didUpdateLocations:
被调用,DoraemonGPSMocker 则会将模拟的位置作为参数调用真正的 delegate 的locationManager:didUpdateLocations:
方法。