10-5.【OC】【extension】Category 和 Extension 在 runtime 中对方法列表的影响有什么差异?

3 阅读3分钟

在 Runtime 层面,Category 和 Extension 的差异本质上是 “静态合并”“动态注入” 的博弈。这种差异直接决定了它们如何影响类的方法列表。


1. Extension:编译期的静态合并

Extension 的方法在 编译阶段 就已经确定了。

  • 处理机制:编译器在编译原类(Class)时,会自动将 Extension 中的方法合并到原类的 class_ro_t(Read-Only Data)结构体的方法列表中。

  • 对方法列表的影响

    • 内存布局:Extension 的方法和原类方法在内存中是连在一起的,处于同一个 method_list_t
    • 查询效率:由于是静态合并,Runtime 不需要额外的逻辑来处理 Extension。在消息转发查找方法时,Extension 的方法和普通方法没有区别。
    • 不可替代性:因为数据写死在了 class_ro_t 里,你无法在运行时通过普通的内存操作移除 Extension 添加的方法。

2. Category:运行时的动态注入

Category 的方法是在 程序启动(Runtime 加载镜像) 时才“挂载”上去的。

  • 处理机制

    1. 编译器将 Category 编译成独立的 category_t 结构体。
    2. Runtime 在初始化类时,调用 attachCategories 函数。
    3. Runtime 会动态创建一个新的二维数组(method_array_t),将 Category 的方法列表插入到原类方法列表的前面。
  • 对方法列表的影响

    • 顺序优先:Category 的方法会排在方法列表的最前端。根据消息传递(objc_msgSend)的线性查找规则,同名方法下,Category 会优先被命中,产生“覆盖”原类方法的现象。
    • 动态性:Category 的方法存在于 class_rw_t(Read-Write Data)中。这意味着理论上你可以在运行时通过 Runtime API 动态地修改或替换这些方法。
    • 冲突风险:如果多个 Category 实现了同名方法,方法列表会变得非常臃肿,且只有最后被加载的那个 Category 方法能被正常查找到。

3. 核心差异对比表

特性Extension (类扩展)Category (分类)
影响的结构体class_ro_t (只读,编译期确定)class_rw_t (可读写,运行时确定)
方法位置与原类方法混在一起,无固定先后强制插入到列表头部,优先级最高
方法列表性质一维数组(合并后的结果)二维数组(原类列表 + 多个分类列表)
编译产物直接合并进类的 DATA生成独立的 category_t 结构体
性能损耗无(等同于原生方法)启动时有加载损耗,列表过长会影响查找速度

4. 深度洞察:为什么 Category 容易影响性能?

由于 Category 采用的是 “头部插入” 的方式,当一个类拥有大量的 Category 时:

  1. 方法查找变慢objc_msgSend 在缓存未命中时,必须遍历这个庞大的二维方法列表。
  2. 启动时间增加:Runtime 在 map_images 阶段需要花费大量 CPU 时间来搬运和重组这些方法列表(即 attachLists 操作)。

💡 总结建议

如果你拥有源码,且扩展是私有的,请务必使用 Extension,因为它不仅能添加成员变量,而且对 Runtime 方法查找没有任何额外负担。只有当你需要扩展系统类,或者需要将大型类按功能模块化拆分时,才考虑使用 Category