3-20.【OC】【Runtime】Swizzling 在继承链中有哪些坑?

2 阅读3分钟

在继承链(Inheritance Chain)中进行 Method Swizzling 是最容易导致“幽灵 Bug”的区域。如果不理解父类、子类与方法列表之间的关系,轻则导致功能失效,重则导致全量崩溃。

以下是四个核心“坑点”及其底层原理解析:


1. “误伤”父类:最严重的后果

如果你直接在子类中使用 method_exchangeImplementations,而子类本身**并没有重写(Override)**这个方法,那么 Runtime 会发生什么?

  • 底层原理class_getInstanceMethod 在子类找不到方法时,会递归向上去父类查找。如果你直接拿这个父类的 Method 去交换,你实际上交换的是父类方法列表里的指针
  • 后果:所有继承自该父类的其他子类、甚至父类本身的实例,都会被强制应用你的 Swizzling 逻辑。
  • 例子:你只想给 MyViewController 增加统计,结果发现整个 App 所有的 UIViewController(包括系统的)都变了。

2. 静态查找导致“失效”

这是坑点 1 的反面。如果你在父类中 Swizzle 了一个方法,而子类已经重写了该方法。

  • 底层原理:子类的方法列表里已经有了一个独立的 method_t。当你 Swizzle 父类时,只改了父类的 IMP
  • 后果:由于子类调用时通过 isa 找到的是子类自己的 IMP,父类的 Swizzling 逻辑对子类完全不生效。你以为全局拦截了,其实漏掉了一大批重写过该方法的子类。

3. class_addMethod:唯一的“安全救生圈”

为了解决上述坑点,行业标准做法是:先尝试向子类添加方法,而不是直接交换。

核心逻辑:

  1. 调用 class_addMethod
  2. 如果返回 YES:说明子类之前没实现这个方法(继承自父类)。此时我们已经成功在子类新建了一个同名方法,并把它的 IMP 指向了我们的 Swizzle 实现。接着只需用 class_replaceMethod 把我们原本准备好的“备选方法”指向父类的原实现即可。
  3. 如果返回 NO:说明子类自己实现了这个方法。此时直接调用 method_exchangeImplementations 是安全的。

4. 无法拦截的 super 调用

假设父类和子类都实现了 viewWillAppear:,你 Swizzle 了父类。

  • 坑点:如果子类的实现是 [super viewWillAppear:animated],理论上会执行你的 Swizzle 逻辑。但如果你在子类的 +load 里又对子类做了某些操作,可能会导致 super 调用的逻辑链路变得极其复杂,难以调试。
  • 死循环风险:如果子类 Swizzle 后内部又调用了 super,而父类也被 Swizzle 了,且两者的 SEL 命名不规范,极易在继承链中形成闭环递归。

总结:继承链 Swizzling 的黄金法则

场景错误做法正确做法
子类没重写方法直接 exchangeclass_addMethod 后再 replace
只想影响特定子类在父类里 Swizzle在子类里 Swizzle,并做好 addMethod 判断
跨模块继承随意命名使用 公司名_类名_前缀 避免冲突

避坑代码检查清单

在 2026 年的 Objective-C 代码审查中,一个合格的 Swizzling 必须包含这段逻辑:

Objective-C

// 1. 尝试添加方法,关联到子类
BOOL didAddMethod = class_addMethod(class,
                                    originalSelector,
                                    method_getImplementation(swizzledMethod),
                                    method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
    // 2. 添加成功:说明原本是继承关系,现在已经把子类的原本位置占了,只需把剩下的钩子挂好
    class_replaceMethod(class,
                        swizzledSelector,
                        method_getImplementation(originalMethod),
                        method_getTypeEncoding(originalMethod));
} else {
    // 3. 添加失败:说明子类已有实现,安全交换
    method_exchangeImplementations(originalMethod, swizzledMethod);
}