基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

+load 和 +initialize 的区别?

知识点图片

+load+initialize 是 Objective-C Runtime 中两个非常特殊的类方法,它们都用于类的初始化,但在调用时机调用方式继承规则以及分类(Category)处理上有显著的区别。

以下是详细的对比分析:

核心区别总结表

特性 +load +initialize
调用时机 被添加到 Runtime 时(App 启动,main 函数之前) 类接收到第一条消息前(懒加载)
调用方式 Runtime 直接获取函数指针调用 objc_msgSend 消息发送机制
父类与子类 父类先调,子类后调。若子类未实现,不会调用父类的 load 父类先调,子类后调。若子类未实现,会再次调用父类的 initialize
分类 (Category) 主类先调,分类后调。所有分类的 load 都会被执行 分类会覆盖主类的 initialize(只执行一个)
执行次数 仅一次 理论上一次,但若子类未实现,父类代码可能运行多次
使用场景 Method Swizzling (方法交换) 初始化静态变量、全局状态

1. 详细解析 +load

调用时机:
当类或分类被加载到 Objective-C Runtime 时调用。通常发生在 App 启动阶段(pre-main),由 dyld 引导加载。

继承与调用顺序:

  1. 父类 -> 子类:Runtime 会自动保证父类的 +load 先于子类执行。
  2. 主类 -> 分类:类的 +load 先于分类的 +load 执行。
  3. 分类之间:取决于编译顺序(Build Phases -> Compile Sources 中的顺序)。
  4. 无继承性:如果子类没有实现 +load,Runtime 不会去调用父类的 +load

注意事项:

  • 不要手动调用:永远不要手动写 [super load][self load]
  • 执行耗时:因为是在 main 函数之前执行,如果在 +load 中进行耗时操作,会直接拖慢 App 的启动速度(Launch Time)。
  • 依赖安全:在 +load 中使用其他类是不安全的(除非是父类),因为其他类可能还没加载完成。
  • 自动释放池+load 方法运行时,系统通常还没有建立自动释放池(Autorelease Pool),如果产生大量临时对象,建议手动加 @autoreleasepool

典型应用:

  • Method Swizzling:这是 +load 最常用的场景,用于在类加载时 hook 系统方法。

2. 详细解析 +initialize

调用时机:
它是懒加载的。只有当类(或其子类)第一次接收到消息时(即第一次被使用时)才会调用。如果类一直没被用到,+initialize 就永远不会执行。

继承与调用顺序:

  1. 消息机制:它通过 objc_msgSend 调用,遵循标准的 Objective-C 方法查找规则。
  2. 父类 -> 子类:Runtime 会确保在子类接收第一条消息前,其父类已经初始化完毕(即父类的 +initialize 已经执行)。
  3. 子类未实现的情况:如果子类没有实现 +initialize,根据继承规则,会调用父类的 +initialize这意味着父类的 +initialize 可能会被执行多次(一次是父类自己初始化,一次是子类借用父类的实现)。

分类(Category)行为:

  • 如果分类实现了 +initialize,它会覆盖主类的实现(这是 Objective-C 消息机制的特性)。主类的 +initialize 将不会被执行(除非分类中显式调用了 super,但这在 initialize 中很少见)。

最佳实践代码:
为了防止父类的逻辑被子类重复触发,通常在实现时加上类型判断:

plaintext
+ (void)initialize {
    if (self == [MyClass class]) {
        // 只在当前类初始化时执行,子类触发时不执行
        // 初始化静态变量等
    }
}

线程安全:
Runtime 内部使用锁来保证 +initialize 在多线程环境下只被执行一次(针对同一个类),是线程安全的。


3. 面试高频追问

Q: 为什么 Method Swizzling 建议在 +load 中做,而不是 +initialize

  • 一致性+load 在类加载时必定执行,且只执行一次。这保证了 Swizzling 一定生效且不会重复交换(重复交换会导致方法换回来了)。
  • 覆盖风险:如果在 +initialize 中做,如果分类重写了 +initialize,主类的 Swizzling 逻辑就会被覆盖失效。
  • 副作用:如果在 +initialize 中做,子类未实现 +initialize 时会触发父类的逻辑,可能导致莫名其妙的多次交换。

Q: 如果一个类有多个分类,且都实现了 +load,会怎么样?

  • 主类的 +load 最先执行。
  • 所有分类的 +load 都会执行。
  • 分类之间的执行顺序取决于 Compile Sources 中的文件顺序。

Q: 如果一个类有多个分类,且都实现了 +initialize,会怎么样?

  • 只有最后编译的那个分类的 +initialize 会被执行。
  • 主类和其他分类的 +initialize 都会被覆盖(Shadowed)。
00:00
00:00