3-21.【OC】【Runtime】Category + Swizzling 同时存在时,调用顺序如何保证?

3 阅读3分钟

Category(分类)Swizzling(方法交换) 同时出现时,系统的调用逻辑会变得非常微妙。要理清顺序,我们需要将“方法查找机制”和“Swizzling 带来的指针偏移”结合起来看。

我们可以从以下两个维度来拆解这个过程:


1. 第一阶段:Category 的“覆盖”效应 (编译与加载期)

在 Swizzling 发生之前,Category 的加载机制首先决定了方法列表的状态。

  • 原理:如果一个 Category 实现了与原类同名的方法,Runtime 在加载 Category 时,会将其方法列表插入到原类方法列表的最前面
  • 结果:当 objc_msgSend 查找方法时,会优先找到 Category 中的实现。原类的方法依然存在,但被“隐藏”在列表后方,形成所谓的“覆盖”。

2. 第二阶段:Swizzling 的“交换”效应 (执行期)

当你在 +load 中执行 Swizzling 时,具体的调用顺序取决于你交换的是哪两个 SEL

场景 A:Swizzle 一个 Category 中不存在的方法名(推荐做法)

如果你创建了一个新 SEL(如 my_viewWillAppear:)来交换系统的 viewWillAppear:

  1. 查找class_getInstanceMethod 会在方法列表中查找。如果 Category 实现了 viewWillAppear:,它拿到的就是 Category 的 IMP;否则拿到的是原类的 IMP

  2. 交换:它将 viewWillAppear:my_viewWillAppear: 的指针对调。

  3. 调用顺序

    • 调用 viewWillAppear: \rightarrow 执行你的 Swizzle 实现
    • 在 Swizzle 实现中调用 [self my_viewWillAppear:] \rightarrow 执行 Category 的实现(或原类实现)。

场景 B:Category 本身重写了方法且逻辑不全(高风险)

如果 Category A 重写了 method,而你在 Category B 中 Swizzle 了 method

  • 风险点:如果你没有在 Swizzle 代码中调用 [self swizzled_method],那么原类和 Category A 的逻辑都会被彻底截断。

3. 调用链条全景图

假设我们在 UIViewController 上有一个 Category,并且又做了一次 Swizzling。

最终的调用顺序如下:

  1. 外部触发[obj viewWillAppear:]
  2. 第一站(Swizzle 钩子) :进入你 Swizzle 后的方法实现。在这里你可以做埋点、日志等。
  3. 第二站(回调原 SEL) :你在代码中通过 [self my_viewWillAppear:] 触发原实现。
  4. 第三站(Category 实现) :如果存在同名 Category 方法,此时会执行 Category 的代码。
  5. 第四站(手动调用原类) :如果 Category 内部写了 [super viewWillAppear:](注意:Category 直接重写通常无法调用原类同名方法,除非使用特殊手段),才会回到原类(Base Class)

4. 关键点:如何保证顺序?

由于 Category 和 Swizzling 的加载都发生在 +load,顺序由以下因素决定:

  • 编译顺序:在 Xcode 的 Build Phases -> Compile Sources 中,排在后面的 Category 会覆盖前面的同名方法。
  • 依赖关系:如果 Swizzling 代码写在某个特定的 Category 里的 +load 中,它会在该 Category 被挂载到类之后执行。

警告:如果多个 Category 都 Swizzle 了同一个方法,且其中一个 Category 又通过同名覆盖了该方法,会导致链条断裂


5. 2026 年的开发建议:避开 Category 同名覆盖

在现代开发中,为了保证调用顺序可控,绝不要在 Category 中直接重写(Override)系统已有的方法

正确的姿势:

  1. 使用 Category 来提供 Swizzle 实现(如 xxx_method)。
  2. 在 Category 的 +load 中将 xxx_method 与系统的 method 交换。
  3. 不要在 Category 里直接写一个 -(void)method

这样可以确保:

  • 系统原有的逻辑不会丢失。
  • 你的 Swizzle 逻辑能准确定位到系统实现。
  • 其他开发者(或者你自己)在另一个 Category 里做的 Swizzle 也能形成正确的“指针链”。