苹果明确不建议在 +load 中放置复杂逻辑,核心原因在于它的调用时机极其特殊,且处于系统启动的“黄金时间”。在 +load 中做太多事,本质上是在跟用户体验和系统稳定性“对着干”。
以下是四个深度原因:
1. 阻塞启动耗时(App Launch Time)
+load 方法是在 main 函数执行之前,由 libobjc 在 dyld 加载镜像的过程中串行调用的。
- 串行执行:所有的
+load必须一个接一个地跑完。如果你在+load中执行耗时操作(如 IO、复杂的解密或网络初始化),这些时间会直接叠加到 App 的启动耗时上。 - 看门狗(Watchdog)风险:如果
+load集合的总耗时超过了 iOS 系统规定的阈值(通常几秒钟),系统会认为 App 启动卡死,直接将其杀掉(Crash) 。
2. 环境不安全:类可能尚未“就绪”
在执行 +load 时,Runtime 的状态处于一种“半成品”阶段。
- 加载顺序不确定:虽然父类会在子类之前加载,但**不同镜像(Image/Framework)**之间的类加载顺序是不确定的。
- 潜在风险:如果你在
MyClass的+load中调用了OtherClass的方法,而此时OtherClass的镜像还没被dyld加载或初始化,就可能导致意想不到的错误或崩溃。
3. 锁竞争与死锁风险
libobjc 在执行 +load 时,内部持有了一些全局锁。
- 死锁隐患:如果你在
+load中使用了多线程同步操作(比如dispatch_sync到主线程,或者等待某个信号量),由于主线程此时正在被+load逻辑占用,极易触发死锁。 - 运行时限制:此时很多系统底层服务(如某些 XPC 服务)甚至都还没启动完成。
4. 二进制体积与内存常驻
- 无差别触发:无论这个类在 App 本次运行中是否被用到,
+load都会执行。这违背了**“按需加载(Lazy Loading)”**的优化原则。 - 内存压力:为了执行
+load,系统必须强制 Realize(实现)这些类。如果你有大量类通过+load进行初始化,会导致 App 一启动就占用极高的物理内存。
总结:+load vs. +initialize 的选择逻辑
| 特性 | +load | +initialize |
|---|---|---|
| 推荐做法 | 仅用于 Method Swizzling | 用于单例、全局配置、状态初始化 |
| 对性能影响 | 极大(影响启动,无法规避) | 极小(首次使用触发,支持懒加载) |
| 安全性 | 较低(环境不完全受控) | 较高(Runtime 已完全成熟) |
💡 最佳实践建议
- 能挪就挪:将初始化逻辑移至
+initialize或main函数之后的第一个ViewController中。 - 必须使用
+load时:仅做方法交换(Method Swizzling),且代码应保持极简。 - 使用
__attribute__((constructor))替代? :不推荐,它的时机和+load类似,同样会阻塞启动。