基本原理
Objective-C 的方法调用采用消息传递机制。每个类都存有一个方法列表(Method List),这个列表将方法选择器(SEL)和对应的实现(IMP)关联起来。方法交换的本质就是改变这种映射关系,让一个选择器对应到另一个方法的实现。
关键函数
方法交换主要借助以下 Runtime 函数来实现:
objective-c
// 获取类的实例方法
Method class_getInstanceMethod(Class cls, SEL name);
// 获取类的类方法
Method class_getClassMethod(Class cls, SEL name);
// 交换两个方法的实现
void method_exchangeImplementations(Method m1, Method m2);
实现示例
下面通过一个具体例子来说明如何进行方法交换。假设我们要在调用UIViewController
的viewWillAppear:
方法时添加日志记录功能。
objective-c
#import <objc/runtime.h>
@implementation UIViewController (Logging)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 获取原始方法
Method originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
// 获取替换方法
Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzled_viewWillAppear:));
// 添加替换方法(避免原始方法不存在的情况)
BOOL didAddMethod = class_addMethod(self,
@selector(viewWillAppear:),
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
// 如果添加成功,说明原始方法不存在,直接设置实现
if (didAddMethod) {
class_setMethodImplementation(self,
@selector(swizzled_viewWillAppear:),
method_getImplementation(originalMethod));
}
// 如果添加失败,说明原始方法存在,进行方法交换
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)swizzled_viewWillAppear:(BOOL)animated {
// 调用交换后的方法实现(即原始的viewWillAppear:)
[self swizzled_viewWillAppear:animated];
// 添加自定义功能
NSLog(@"View will appear: %@", self);
}
@end
执行流程解析
- 获取方法:运用
class_getInstanceMethod
分别获取原始方法和替换方法。 - 添加方法:利用
class_addMethod
尝试添加替换方法。若原始方法不存在,添加会成功,此时需要手动设置方法实现。 - 交换实现:要是添加失败,意味着原始方法存在,就使用
method_exchangeImplementations
交换两个方法的实现。 - 方法调用:当调用
viewWillAppear:
时,实际上会执行swizzled_viewWillAppear:
的代码;而在swizzled_viewWillAppear:
内部调用自身时,执行的则是原始的viewWillAppear:
方法。
注意要点
- 线程安全:方法交换属于全局性操作,所以要使用
dispatch_once
保证代码只执行一次。 - 避免冲突:在交换系统方法时,要留意其他库可能也进行了相同的操作,从而引发冲突。
- 命名规范:替换方法的命名要清晰,建议加上特定前缀,防止与现有方法重名。
- 参数与返回值:替换方法的参数和返回值类型必须与原始方法保持一致。
- 父类检查:如果子类没有实现某个方法,调用时会使用父类的实现,这可能会对多个类产生影响。
应用场景
方法交换常用于以下几种情况:
-
实现 AOP(面向切面编程),比如添加日志记录、埋点统计等功能。
-
修复系统 API 的缺陷。
-
实现无侵入式的功能扩展。
-
实现自动化测试。
方法交换是 Runtime 的强大功能之一,但也存在一定风险,使用时需要谨慎操作。