方法交换

代码实现

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
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// 获取 Method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

// 确保这两个方法一定存在(要么在本类,要么在其父类里)
if (originalMethod && swizzledMethod)
{
// 如果本类没有 origin 方法,则给 originalSelector 添加 swizzled 实现(origin 方法在父类,因为 originalMethod 不为空),返回 YES
// 如果本类有 origin 方法,则添加失败,返回 NO
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

if (didAddMethod)
{
// 添加成功,表示已实现 originalSelector -> swizzledIMP
// 接下来实现 swizzledSelector -> originalIMP
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else
{
// 添加失败,表示类里原本就有 originalIMP,只需要交换这两个方法的实现即可
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
}

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzleMethod(self, @selector(viewDidLoad), @selector(my_viewDidLoad));
});
}

- (void)viewDidLoad
{
[super viewDidLoad];
}

- (void)my_viewDidLoad
{
NSLog(@"View Did Load");
[self my_viewDidLoad];
}
  1. 如何交换类方法

    只要把 self 换成 object_getClass(self) 即可

    1
    2
    3
    4
    5
    6
    7
    + (void)load
    {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    swizzleMethod(object_getClass(self), @selector(originClassMethod), @selector(swizzleClassMethod));
    });
    }
  1. 为什么不能直接调用 method_exchangeImplementations

    比如

    1
    2
    3
    4
    5
    6
    7
    8
    void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
    {
    // 获取 Method
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    method_exchangeImplementations(originalMethod, swizzledMethod);
    }

    直接交换 IMP 是很危险的。因为如果这个类中没有实现这个方法,class_getInstanceMethod() 返回的是某个父类的 Method 对象,这样 method_exchangeImplementations() 就把父类的原始实现(IMP)跟这个类的 Swizzle 实现交换了。这样其他父类及其其他子类的方法调用就会出问题,最严重的就是 Crash。但如果这个类中实现了此方法,那么是没任何问题的

    参见:方法交换的图示

  2. 方法交换为什么写在 +load 中而不是 initialze

  3. 方法交换为什么要 dispatch_once 中执行

    参见:iOS 的 initialize 和 load 区别

class_getInstanceMethod

1
Method class_getInstanceMethod(Class cls, SEL name);

获取指定 SEL 的 Method

如果该类没有指定 SEL 的 Method,父类里面可能有

所以如果该类和其父类都没有该 SEL 的实现,才返回 NULL

class_addMethod

1
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

给指定 SEL 添加新实现。如果添加成功则返回 YES,否则返回 NO(比如该类已经有对应 SEL 的 IMP)

注意,该方法会覆盖父类的实现,但不会替换本类原有的实现。如果要替换本类原有实现,使用 method_setImplementation

class_replaceMethod

1
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);

替换指定 SEL 的实现,返回值为被替换掉的 IMP

  • 如果本类不存在该 name 的 Method,则 class_addMethod 会被调用
  • 如果本类存在该 name 的 Method,则 method_setImplementation 会被调用

method_exchangeImplementations

1
method_exchangeImplementations(Method m1, Method m2)

交换两个方法的实现

应用实例

  1. AOP(Aspect of programming),给所有的 Controller 的 viewWillAppear 等生命函数添加数据上报、日志等
  2. API 安全性保护,替换 NSArray 的 addObject 方法,防止插入 nil 对象,Debug 下报错,详见 Github Demo
  1. 代码实现
    1. class_getInstanceMethod
    2. class_addMethod
    3. class_replaceMethod
    4. method_exchangeImplementations
  2. 应用实例