3-4.【OC】【Runtime】Category 的方法是如何被合并进类的方法列表的?

0 阅读2分钟

Category 的合并过程本质上是一个**“后进先出”的列表插入操作**,它发生在类加载的第三和第四阶段(Realization & Category Attachment)。

我们可以将这个过程拆解为三个具体的步骤:


1. 数据的提取 (Preparation)

libobjc 扫描到一个镜像(Image)中的分类时,它会从 Mach-O 文件的 __objc_catlist 段中提取出 category_t 结构体。

这个结构体包含了:

  • 分类名和归属的主类。
  • 方法列表 (method_list_t)
  • 属性列表 (property_list_t)。
  • 协议列表 (protocol_list_t)。

2. 内存中的“倒序”排列 (Sorting)

如果一个主类有多个分类,Runtime 会收集这些分类。

  • 加载顺序:取决于编译顺序(在 Xcode 的 Compile Sources 中的排列顺序)。
  • 处理逻辑:Runtime 会将这些分类存入一个数组。注意,后编译的分类会被放在数组的前面

3. 核心步骤:列表的“平移与插入” (Attach Categories)

这是合并最关键的一步,发生在 attachCategories 函数中。假设主类原本有一个方法列表:

  1. 扩容:Runtime 会计算所有分类带来的方法总数,然后给主类的 class_rw_t 中的方法列表扩容。
  2. 平移(Memmove) :将主类原有的方法列表(以及之前已经合并进去的分类方法)向后移动,空出前面的位置。
  3. 拷贝(Memcpy) :将新分类的方法列表依次拷贝到空出的起始位置。

[Image illustrating category methods being inserted at the beginning of the class method list, pushing original methods back]


4. 结果:为什么分类会“覆盖”主类方法?

通过上面的步骤你会发现,主类的方法并没有被“删除”或“替换”,它只是被挤到了后面

  • 当你调用 [obj method] 时,Runtime 的 objc_msgSend 会在类的方法列表中顺着索引从前往后找
  • 由于分类的方法被插在了最前面,Runtime 找到第一个匹配的方法名后就会立即去执行,不再往后找了。
  • 结论:分类方法之所以生效,是因为它在搜索路径上抢占了“先机”。

5. 两个分类有同名方法怎么办?

根据第 2 步的逻辑:

  • 后编译的分类,其方法会排在更前面
  • 因此,在 Xcode 的 Compile Sources 中排在最后的那个分类,其方法会最终“胜出”。

💡 进阶知识:如何调用被分类“覆盖”掉的原生方法?

既然原生方法只是被挤到了后面,理论上我们是可以找到它的。你可以通过 class_copyMethodList 获取完整的方法列表,然后遍历这个数组,找到最后一个同名的方法手动调用。