3-18.【OC】【Runtime】Swizzling 应该发生在什么时机最安全?

5 阅读3分钟

在 Objective-C 中,Method Swizzling 最安全、最标准的时机只有一个:在类的 +load 方法中执行


1. 为什么是 +load

+load 方法具有以下几个保证安全性的特质:

  • 执行时机极早+load 在类被加载到内存时立即调用,远早于 main 函数,更早于任何实例对象的创建。这保证了在 App 真正运行业务逻辑之前,方法已经“变脸”成功。
  • 全局唯一性:每个类的 +load 只有在类加载时被系统调用一次。
  • 线程安全:Runtime 在执行 +load 时会加锁,确保方法交换过程不会产生竞争条件。

2. 为什么不能用 +initialize

很多人容易混淆这两个方法,但在 +initialize 中做 Swizzling 是极其危险的:

  • 延迟性+initialize 是在类接收到第一条消息时才调用的。如果你的 App 有多处地方同时访问这个类,可能会导致 Swizzling 过程中产生竞态。
  • 继承陷阱:如果子类没有实现 +initialize,它会调用父类的 +initialize。这会导致父类的 Swizzling 代码被执行多次,从而可能把方法“换回来”,导致拦截失效。

3. 标准的安全代码模板

一个生产级别的 Swizzling 应该遵循“先尝试添加,后替换”的逻辑,防止意外篡改父类的方法。

Objective-C

#import <objc/runtime.h>

+ (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);
        
        // 1. 核心安全步骤:先尝试给当前类添加原 selector
        // 目的是防止当前类没实现该方法(只是继承了父类),直接交换会导致父类方法被改
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) {
            // 如果添加成功,说明原类里本来没这个方法,现在添加了 swizzled 的实现
            // 那么我们只需要把 swizzledSelector 指向原来的父类实现即可
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            // 如果添加失败,说明原类里已经有这个方法实现了,直接交换指针
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

4. 2026 年的高级安全注意事项

随着系统架构的演进,现在做 Swizzling 还需要注意以下几点:

4.1 避免在 Swift 类中盲目使用

如果你的类是纯 Swift 写的(不继承自 NSObject),它是没有 +load 方法的。即使继承自 NSObject,Swift 也不再允许重写 +load

  • 对策:在 Swift 中,建议在 AppDelegate 或特殊的初始化单例中显式调用一个配置函数来触发 OC 写的 Swizzling 代码。

4.2 模块化与隐私权限

2026 年的 iOS 系统审核更加严格。如果你 Swizzling 了敏感的系统 API(如 CLLocationManagerPHPhotoLibrary),Apple 的静态扫描工具会非常容易标记你的 App。

  • 建议:除非是做无埋点监控、性能检测(APM)或极特殊的框架底层修改,否则尽量使用 AOP(面向切面编程)库协议代理(Delegate Proxy) 来替代 Swizzling。

4.3 性能敏感性

正如之前讨论的,Swizzling 会触发 Cache Flush

  • 后果:如果你在 App 运行中频繁执行 Swizzling(虽然很少见),会造成持续的性能抖动。

总结

最安全的时机是 +load。它利用了 Runtime 加载类的天然屏障,确保了交换过程的唯一性、原子性和全局覆盖性