这两个方法是 Objective-C 类生命周期中最重要的两个钩子,但它们的底层机制、调用时机和对继承的处理完全不同。
以下是它们的本质差异对比:
1. 调用时机:一个是“主动加载”,一个是“懒加载”
-
+load(早且主动) :在 App 启动的 Pre-main 阶段。当
libobjc将类和分类加载进内存并完成“修复(Realization)”后,会立即主动调用。- 即便你的代码里从来没用到过这个类,
+load也一定会执行。
- 即便你的代码里从来没用到过这个类,
-
+initialize(晚且被动) :在 运行时(Runtime) 阶段。它是一种“懒加载”机制,只有当类第一次收到消息(比如调用
alloc或类方法)时,Runtime 才会触发它。- 如果这个类在 App 运行期间从未被使用,
+initialize就永远不会执行。
- 如果这个类在 App 运行期间从未被使用,
2. 调用顺序:一个是“严格排队”,一个是“递归继承”
+load 的顺序:
Runtime 会扫描所有的类,按照以下严格规则排序:
- 父类优先于子类:先执行
Parent的+load,再执行Child的。 - 类优先于分类(Category) :先执行主类的
+load,全部主类跑完后,再跑分类的。 - 分类之间看编译顺序:在
Compile Sources里排在前面的分类先执行。
- 注意:
+load是通过函数地址直接调用的,不会走objc_msgSend,因此不存在“分类覆盖主类”的情况,它们都会被执行。
+initialize 的顺序:
它是通过 objc_msgSend 发送消息激发的:
- 父类优先于子类:Runtime 会确保在子类初始化前,父类已经初始化完成。
- 分类覆盖主类:如果分类实现了
+initialize,主类的实现会被“屏蔽”,只执行分类的。 - 子类不实现会继承:如果子类没有实现
+initialize,它会调用父类的实现(这意味着父类的+initialize可能会被执行多次)。
3. 线程安全与阻塞
-
+load:由于是在启动阶段由系统串行调用的,它一定是线程安全的。但因为它阻塞了启动过程,如果在
+load里做耗时操作,会导致 App 启动变慢甚至被系统看门狗杀掉。 -
+initialize:它是线程安全的。Runtime 内部会加锁,确保即便多线程同时第一次访问类,初始化也只执行一次。
4. 总结对比全景表
| 特性 | +load | +initialize |
|---|---|---|
| 调用时机 | App 启动 (Pre-main) | 第一次收到消息 (Runtime) |
| 调用方式 | 直接通过函数地址调用 | 通过 objc_msgSend 消息传递 |
| 分类处理 | 都会被执行,不覆盖 | 分类会覆盖主类的实现 |
| 继承逻辑 | 父类 子类 | 父类 子类 (且子类会继承父类实现) |
| 使用建议 | 尽量不用 (影响启动速度) | 推荐 (用于一次性配置、单例初始化) |
💡 避坑小指南
-
在
+initialize中防范重复调用:由于子类可能会继承父类的+initialize,建议在实现时判断类名:Objective-C
+ (void)initialize { if (self == [MyClass class]) { // 确保只执行一次 } } -
+load中不建议使用其他类:在执行+load时,其他的类可能还没加载完(除非是你的父类),此时调用其他类的方法会有风险。