在 Objective-C 中,+load 方法是一个极其特殊的机制。与普通方法遵循“分类覆盖原类”的规则不同, +load 方法是“人人有份,按序执行” 。
Runtime 在启动时会直接通过函数指针调用所有的 +load,而不是通过 objc_msgSend。这决定了它的调用顺序有一套严格的层级规则:
1. 核心调用顺序规则
第一阶段:所有的类(Class)
系统会先处理所有的类,再处理分类。
- 先父类,后子类:父类的
+load永远先于子类执行。 - 同级按编译顺序:如果是两个没有继承关系的类,则按照 Xcode 中
Compile Sources的文件排列顺序从上到下执行。
第二阶段:所有的分类(Category)
当所有类的 +load 都跑完后,系统才开始处理分类。
- 按编译顺序执行:分类之间没有“父子”之分。谁在
Compile Sources中排在后面,谁的+load就后执行。 - 不覆盖原则:分类中的
+load不会遮蔽原类中的+load,两者都会被执行。
2. 总结流程图
- 父类
+load - 子类
+load - 原类
+load(其他无继承关系的类) - 分类 A
+load - 分类 B
+load(按编译顺序排列)
3. 为什么规则如此特殊?(底层原理)
普通方法存储在类的 method_list 中,而 +load 在 Runtime 加载镜像时会被单独提取出来,存入两个全局列表:
loadable_classes:存放类的load指针。loadable_categories:存放分类的load指针。
在 call_load_methods 函数中,Runtime 会先遍历 loadable_classes(内部有递归逻辑保证先父后子),全部完成后再遍历 loadable_categories。由于它是直接通过内存地址访问函数,避开了消息转发,所以不存在“覆盖”一说。
4. +load vs +initialize
这是面试中最常被拿来对比的两个方法。它们的调用规则完全不同:
| 特性 | +load | +initialize |
|---|---|---|
| 调用时机 | App 启动,类加载进内存时 | 第一次接收到消息时(懒加载) |
| 调用次数 | 仅一次 | 仅一次(除非子类没实现会调父类) |
| 调用方式 | 直接通过地址调用 | 通过 objc_msgSend 调用 |
| 分类影响 | 均执行,不覆盖 | 分类会覆盖原类的实现 |
| 安全性 | 此时环境可能不稳,慎用 | 相对安全 |
5. 注意事项与禁忌
- 不要手动调用
[super load]:系统会自动处理继承链,手动调用会导致父类的load执行两次。 - 避免耗时操作:
+load会在main函数之前执行,过多的耗时逻辑会直接增加 App 的启动耗时(Pre-main time)。 - 警惕依赖关系:在
+load时,其他的类可能还没加载完成。如果你在Class A的load里调用Class B的方法,可能会出问题。