代码实现
1 | void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) |
代码示例:
1 | + (void)load |
如何交换类方法
只要把 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));
});
}
为什么不能直接调用
method_exchangeImplementations
比如
1
2
3
4
5
6
7
8void 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。但如果这个类中实现了此方法,那么是没任何问题的参见:方法交换的图示
方法交换为什么写在 +load 中而不是 initialze
方法交换为什么要 dispatch_once 中执行
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) |
交换两个方法的实现
应用实例
- AOP(Aspect of programming),给所有的 Controller 的 viewWillAppear 等生命函数添加数据上报、日志等
- API 安全性保护,替换 NSArray 的 addObject 方法,防止插入 nil 对象,Debug 下报错,详见 Github Demo