Farlanki.

Method Swizzling

字数统计: 758阅读时长: 3 min
2018/03/10 Share

Method Swizzling 是一种能将方法的实现替换掉的技术,运用的是 Objective-C 的运行时方法实现的。其实现的具体思路是将方法的 imp 进行对调。

原理

在 Objective-C 中,所有的方法都是动态绑定的。在 Objective-C 的类结构体中,维护着一张方法表(method list),记录着该类中所有方法的 sel 和 imp。在调用一个方法的时候,其实是通过 sel 寻找 对应的 imp,并调用imp。通过 Method Swizzling , 我们可以将两个方法的 imp 进行对调。

常规实现

以下是摘抄自 NSHipster 的一段实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}

@end

通过上面对 UIViewController 的扩展,所有 UIViewController 对象或者其子类的对象在调用 viewWillAppear的时候,都会输出一段log。注意,上面的 -xxx_viewWillAppear :animated方法不会产生死循环,因为[self xxx_viewWillAppear:animated];实际上是在调用viewWillAppear:animated方法。

+load vs. +initialize

+load方法的特点是:

  1. 在类被加载时被调用
  2. 在main函数之前执行
  3. 子类的 +load 方法会在它的所有父类(不包括父类分类中的 +load )的 +load 方法执行之后执行
  4. 分类的 +load 方法会在主类的 +load 方法执行之后执行

+initialize方法的特点是:

  1. 在类收到第一个函数调用前执行

为了保证 Method Swizzling 进行得足够早并且减少竞争情况的出现,应该将 Method Swizzling 的相关方法写在 +load 里面。

dispatch_once

为了确保 Method Swizzling 只进行一次,我们最好将相关代码写在dispatch_once里面。因为如果 Method Swizzling 进行了多次,我们压根不清楚现在调用的方法究竟是调换前的还是调换后的。

使用例子

模拟位置

滴滴开源的工具库 DoraemonKit 工具库中使用了 Method Swizzling 来模拟GPS位置。
下面来看看滴滴是怎么实现的。

  1. 实现一个 CLLocationManager 的分类,为 CLLocationManager 添加一个 doraemon_swizzleLocationDelegate: 方法,这个方法将作为替换后的 setDelegate: 方法的实现。

  2. 在 DoraemonGPSMocker 的 +load 方法中,将 CLLocationManagersetDelegate: 实现改变。这样,当调用 CLLocationManagersetDelegate: 方法时,调用的将会是 doraemon_swizzleLocationDelegate:

  3. doraemon_swizzleLocationDelegate :的方法的实现是,首先将DoraemonGPSMocker设置为 CLLocationManager 的 delegate。 并且将该实例和 delegate 记录下来。每当一个 CLLocationManager 调用 delegate 的方法,DoraemonGPSMocker 都会将该方法拦截下来。如果 locationManager:didUpdateLocations:被调用,DoraemonGPSMocker 则会将模拟的位置作为参数调用真正的 delegate 的 locationManager:didUpdateLocations: 方法。

应用内抓包

Dotzu

参考资料

1.Dynamic binding

2.Selector

3.Method Swizzling

CATALOG
  1. 1. 原理
  2. 2. 常规实现
    1. 2.1. +load vs. +initialize
    2. 2.2. dispatch_once
  3. 3. 使用例子
    1. 3.1. 模拟位置
    2. 3.2. 应用内抓包
  4. 4. 参考资料