Method Swizzling 原理 & 风险

10 阅读3分钟

Method Swizzling 原理 & 风险

适合 iOS / Objective-C Runtime 学习、面试复习、博客发布


一、什么是 Method Swizzling

Method Swizzling 是 Objective-C Runtime 提供的一种能力:

在程序运行时,交换两个方法的实现(IMP) ,从而改变方法调用的实际行为,而不修改原始代码。

一句话概括:

selector 不变,IMP 被替换


二、Objective-C 方法调用回顾

2.1 表面语法

[obj foo];

2.2 底层真实调用

objc_msgSend(obj, @selector(foo));

2.3 Runtime 查找流程(简化)

  1. 通过 obj->isa 找到类对象

  2. 在方法缓存(cache)中查找 selector

  3. 缓存未命中 → 遍历方法列表

  4. 找到 selector 对应的 IMP

  5. 跳转执行该函数

📌 关键点

Runtime 中 SEL → IMP 的映射是可变的,这正是 Swizzling 能生效的基础。


三、Method 的底层结构

简化后的 Runtime 结构:

typedef struct method_t {
    SEL name;        // 方法名
    const char *types; // 类型编码
    IMP imp;         // 函数指针
} method_t;

Swizzling 的本质

交换两个 method_t 中的 imp 指针


四、Method Swizzling 的核心 API

4.1 获取 Method

Method m1 = class_getInstanceMethod(cls, @selector(foo));
Method m2 = class_getInstanceMethod(cls, @selector(my_foo));

4.2 交换实现

method_exchangeImplementations(m1, m2);

4.3 交换后的效果

Selector实际执行的 IMP
foomy_foo 的实现
my_foofoo 的实现

五、为什么 Swizzling 后调用“自己”不会死循环

示例代码:

- (void)my_foo {
    NSLog(@"before");
    [self my_foo];
    NSLog(@"after");
}

表面看起来是递归,但实际上:

  • my_foo selector → 指向原始 foo 的 IMP

  • 所以这是一次对“原方法”的调用

📌 记忆口诀:

Swizzling 后:名字是假的,IMP 才是真的


六、标准、安全的 Swizzling 写法

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = [self class];

        Method original = class_getInstanceMethod(cls, @selector(viewDidAppear:));
        Method swizzled = class_getInstanceMethod(cls, @selector(xxx_viewDidAppear:));

        method_exchangeImplementations(original, swizzled);
    });
}

- (void)xxx_viewDidAppear:(BOOL)animated {
    NSLog(@"统计埋点");
    [self xxx_viewDidAppear:animated]; // 实际调用原实现
}

为什么必须写在

+load

  • 类加载即执行
  • 线程安全
  • 保证在任何方法调用前完成替换

七、Method Swizzling 的主要风险

7.1 全局生效(不可控)

[UIViewController viewDidAppear:]

一旦 Swizzle:

  • 所有 UIViewController 子类

  • 系统 + 业务 + 第三方 SDK

全部受影响。


7.2 多方 Swizzling 冲突(最致命)

场景:

  • SDK A swizzle viewDidAppear:

  • SDK B 也 swizzle viewDidAppear:

结果取决于 加载顺序

  • 后者可能覆盖前者

  • 调用链断裂

  • 死循环 / 逻辑丢失

📌 Swizzling 没有命名空间,也没有优先级控制


7.3 系统实现变化风险

Apple 不保证系统方法的内部实现稳定。

swizzle dealloc / viewWillDisappear:

iOS 升级后可能导致:

  • EXC_BAD_ACCESS
  • 野指针
  • 不可预期行为

7.4 破坏继承体系

@interface A : NSObject
- (void)foo;
@end

@interface B : A
@end

Swizzle A.foo:

  • B 也被强制修改
  • 无法只影响父类

7.5 调用时机 & 线程安全问题

❌ 错误示例:

  • 在 +initialize

  • 多线程中动态 Swizzle

可能导致:

  • 方法缓存未刷新
  • IMP 状态不一致

7.6 可维护性差

  • 调用栈不真实
  • 行为被“暗改”
  • 新成员极难理解
  • Debug 成本极高

八、哪些场景可以使用 Swizzling(慎用)

场景说明
统计埋点页面曝光、点击统计
Crash 防护数组越界、空指针保护
Debug 工具调试、日志注入
SDK / 基础设施能完全控制使用环境

九、不推荐使用的场景

🚫 核心业务逻辑

🚫 安全 / 支付相关

🚫 强依赖系统行为

🚫 多 SDK 混合项目


十、比 Swizzling 更安全的替代方案

10.1 继承 + Override(首选)

@interface BaseViewController : UIViewController
@end

10.2 Protocol + 组合

@protocol Trackable
- (void)track;
@end

10.3 消息转发机制

forwardingTargetForSelector:
forwardInvocation:
  • 可控
  • 可局部生效
  • 更符合 Runtime 设计哲学

十一、总结一句话

Method Swizzling 是通过运行时交换方法 IMP 来改变行为的技术,它强大但危险,具有全局生效、顺序依赖和高维护成本的特点,只适合框架级或工具级场景,业务代码中应尽量避免。


十二、延伸阅读

  • objc_msgSend 汇编实现
  • 方法缓存(cache_t)
  • Aspects 原理分析
  • Swift 中的 Method Swizzling 限制