3-7.【OC】【Runtime】为什么苹果明确不建议在 +load 中做太多逻辑?

6 阅读2分钟

苹果明确不建议在 +load 中放置复杂逻辑,核心原因在于它的调用时机极其特殊,且处于系统启动的“黄金时间”。在 +load 中做太多事,本质上是在跟用户体验和系统稳定性“对着干”。

以下是四个深度原因:


1. 阻塞启动耗时(App Launch Time)

+load 方法是在 main 函数执行之前,由 libobjcdyld 加载镜像的过程中串行调用的。

  • 串行执行:所有的 +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 已完全成熟)

💡 最佳实践建议

  1. 能挪就挪:将初始化逻辑移至 +initializemain 函数之后的第一个 ViewController 中。
  2. 必须使用 +load:仅做方法交换(Method Swizzling),且代码应保持极简。
  3. 使用 __attribute__((constructor)) 替代? :不推荐,它的时机和 +load 类似,同样会阻塞启动。