johnhao
johnhao
发布于 2017-07-05 / 5 阅读
0

iOS-load/initialize区别

在 Objective-C 中,+load+initialize 是两个特殊的类方法,用于在类加载或首次使用时执行初始化代码。它们在运行时系统(Runtime)中有特定的调用时机和顺序,尤其在涉及分类(Category)和继承时,行为有明显差异。

下面从区别、调用顺序、继承场景下的行为三个方面详细说明:


一、+load+initialize 的核心区别

特性

+load

+initialize

调用时机

类/分类被 runtime 加载进内存时立即调用(程序启动时)

在类第一次收到消息前(懒加载)

是否自动调用父类

❌ 不会自动调用父类的 +load(但父类的 +load 会被单独调用)

✅ 如果子类未实现 +initialize,会调用父类的;如果子类实现了,则父类仍可能被调用(见下文)

线程安全性

在主线程中调用,且在 main() 之前,是单线程环境

可能在多线程环境下被调用(需注意线程安全)

调用次数

每个类/分类只调用一次

每个类最多调用一次(但可能因子类触发而多次进入父类实现)

用途

适合做 Method Swizzling、全局 hook 等需要尽早执行的操作

适合做类级别的轻量初始化(如设置默认值)


二、Category 中的调用顺序

1. +load 的调用顺序

  • 先调用父类的 +load,再调用子类的 +load

  • 所有类的 +load 调用完后,才调用分类的 +load

  • 同一类的多个分类:按编译顺序(链接顺序)调用(不确定,依赖 build phase 中的文件顺序)

示例:

// ClassA.m
+ (void)load { NSLog(@"ClassA load"); }

// ClassA+Cat1.m
+ (void)load { NSLog(@"ClassA+Cat1 load"); }

// ClassA+Cat2.m
+ (void)load { NSLog(@"ClassA+Cat2 load"); }

输出可能是:

ClassA load
ClassA+Cat1 load
ClassA+Cat2 load

(具体 Cat1/Cat2 顺序取决于链接顺序)

2. +initialize 的调用顺序

  • 只有在类或其子类首次收到消息时才会触发

  • 分类中的 +initialize 会被忽略!

    ⚠️ 分类中定义的 +initialize 不会被 runtime 调用。因为 +initialize 是通过消息发送机制触发的,而分类会覆盖原类的方法(包括 +initialize),但通常你不应该在分类中重写 +initialize,这会导致原类的 +initialize 丢失。

  • 如果子类没有实现 +initialize,则会调用父类的 +initialize

  • 如果子类实现了 +initialize,那么:

    • 子类首次使用 → 调用子类的 +initialize

    • 同时,父类的 +initialize 可能已经被调用过了(当父类首次使用时)

📌 注意:如果子类没有显式使用父类,父类的 +initialize 可能由子类触发!

例如:

// Parent.m
+ (void)initialize { NSLog(@"Parent initialize"); }

// Child.m
+ (void)initialize { NSLog(@"Child initialize"); }

当第一次向 Child 发消息时:

  • 先确保 Parent 已初始化 → 调用 Parent +initialize

  • 再调用 Child +initialize

但如果 Child 没有实现 +initialize,则只调用 Parent +initialize(且只调一次)。


三、继承场景下的调用过程

+load 继承行为

  • 所有类(包括父类、子类)和它们的分类都会各自调用一次 +load

  • 调用顺序:父类 → 子类 → 父类的分类 → 子类的分类(大致如此,实际是先所有类,再所有分类)

  • 每个 +load 方法独立执行,不会自动 super 调用

示例调用顺序(简化):

SuperClass load
SubClass load
SuperClass+Cat load
SubClass+Cat load

+initialize 继承行为

  • 懒触发:只有类首次收到消息时才调用

  • 如果子类未实现 +initialize,则使用父类的实现(但只调一次)

  • 如果子类实现了 +initialize,则:

    • 父类的 +initialize 会在子类初始化前被调用(如果尚未调用)

    • 子类的 +initialize 会被调用

  • 注意+initialize 中可通过 [self class] 判断当前是哪个类触发的

+ (void)initialize {
    if (self == [MyClass class]) {
        // 只在 MyClass 首次使用时执行
    }
}

否则,如果子类继承了该方法,+initialize 会被调用两次(一次父类触发,一次子类触发),导致重复初始化。


四、最佳实践建议

  • +load:用于 Method Swizzling、注册全局监听等必须在 main() 前完成的操作。

  • +initialize:用于类的轻量级初始化(如设置默认属性),务必加 if (self == [ClassName class]) 判断

  • 不要在 Category 中重写 +initialize:会覆盖原类实现,且行为不可控。

  • 避免在 +load+initialize 中调用其他类的方法:可能对方尚未加载,导致 crash。


总结

场景

+load

+initialize

调用时机

程序启动,类加载时

类首次使用前(懒加载)

Category 是否生效

✅ 会调用

❌ 不应使用(会被覆盖)

继承调用

父类和子类各自独立调用

子类可继承,但需防重复调用

安全性

单线程,安全

多线程风险,需谨慎

希望这能帮你彻底理清 +load+initialize 的区别和使用场景!