3-5.【OC】【Runtime】+load 与 +initialize 的调用时机和调用顺序有什么本质差异?

4 阅读3分钟

这两个方法是 Objective-C 类生命周期中最重要的两个钩子,但它们的底层机制、调用时机和对继承的处理完全不同。

以下是它们的本质差异对比:


1. 调用时机:一个是“主动加载”,一个是“懒加载”

  • +load(早且主动)

    在 App 启动的 Pre-main 阶段。当 libobjc 将类和分类加载进内存并完成“修复(Realization)”后,会立即主动调用。

    • 即便你的代码里从来没用到过这个类,+load 也一定会执行。
  • +initialize(晚且被动)

    运行时(Runtime) 阶段。它是一种“懒加载”机制,只有当类第一次收到消息(比如调用 alloc 或类方法)时,Runtime 才会触发它。

    • 如果这个类在 App 运行期间从未被使用,+initialize 就永远不会执行。

2. 调用顺序:一个是“严格排队”,一个是“递归继承”

+load 的顺序:

Runtime 会扫描所有的类,按照以下严格规则排序:

  1. 父类优先于子类:先执行 Parent+load,再执行 Child 的。
  2. 类优先于分类(Category) :先执行主类的 +load,全部主类跑完后,再跑分类的。
  3. 分类之间看编译顺序:在 Compile Sources 里排在前面的分类先执行。
  • 注意+load 是通过函数地址直接调用的,不会走 objc_msgSend,因此不存在“分类覆盖主类”的情况,它们都会被执行。

+initialize 的顺序:

它是通过 objc_msgSend 发送消息激发的:

  1. 父类优先于子类:Runtime 会确保在子类初始化前,父类已经初始化完成。
  2. 分类覆盖主类:如果分类实现了 +initialize,主类的实现会被“屏蔽”,只执行分类的。
  3. 子类不实现会继承:如果子类没有实现 +initialize,它会调用父类的实现(这意味着父类的 +initialize 可能会被执行多次)。

3. 线程安全与阻塞

  • +load

    由于是在启动阶段由系统串行调用的,它一定是线程安全的。但因为它阻塞了启动过程,如果在 +load 里做耗时操作,会导致 App 启动变慢甚至被系统看门狗杀掉。

  • +initialize

    它是线程安全的。Runtime 内部会加锁,确保即便多线程同时第一次访问类,初始化也只执行一次。


4. 总结对比全景表

特性+load+initialize
调用时机App 启动 (Pre-main)第一次收到消息 (Runtime)
调用方式直接通过函数地址调用通过 objc_msgSend 消息传递
分类处理都会被执行,不覆盖分类会覆盖主类的实现
继承逻辑父类 \to 子类父类 \to 子类 (且子类会继承父类实现)
使用建议尽量不用 (影响启动速度)推荐 (用于一次性配置、单例初始化)

💡 避坑小指南

  1. +initialize 中防范重复调用:由于子类可能会继承父类的 +initialize,建议在实现时判断类名:

    Objective-C

    + (void)initialize {
        if (self == [MyClass class]) {
            // 确保只执行一次
        }
    }
    
  2. +load 中不建议使用其他类:在执行 +load 时,其他的类可能还没加载完(除非是你的父类),此时调用其他类的方法会有风险。