Runtime方法交换原理

5 阅读3分钟

基本原理

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);

实现示例

下面通过一个具体例子来说明如何进行方法交换。假设我们要在调用UIViewControllerviewWillAppear:方法时添加日志记录功能。

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

执行流程解析

  1. 获取方法:运用class_getInstanceMethod分别获取原始方法和替换方法。
  2. 添加方法:利用class_addMethod尝试添加替换方法。若原始方法不存在,添加会成功,此时需要手动设置方法实现。
  3. 交换实现:要是添加失败,意味着原始方法存在,就使用method_exchangeImplementations交换两个方法的实现。
  4. 方法调用:当调用viewWillAppear:时,实际上会执行swizzled_viewWillAppear:的代码;而在swizzled_viewWillAppear:内部调用自身时,执行的则是原始的viewWillAppear:方法。

注意要点

  1. 线程安全:方法交换属于全局性操作,所以要使用dispatch_once保证代码只执行一次。
  2. 避免冲突:在交换系统方法时,要留意其他库可能也进行了相同的操作,从而引发冲突。
  3. 命名规范:替换方法的命名要清晰,建议加上特定前缀,防止与现有方法重名。
  4. 参数与返回值:替换方法的参数和返回值类型必须与原始方法保持一致。
  5. 父类检查:如果子类没有实现某个方法,调用时会使用父类的实现,这可能会对多个类产生影响。

应用场景

方法交换常用于以下几种情况:

  • 实现 AOP(面向切面编程),比如添加日志记录、埋点统计等功能。

  • 修复系统 API 的缺陷。

  • 实现无侵入式的功能扩展。

  • 实现自动化测试。

方法交换是 Runtime 的强大功能之一,但也存在一定风险,使用时需要谨慎操作。