简单直接的答案是:安全。
+initialize 的安全性是由 Objective-C Runtime 在底层通过**线程锁(Lock)**机制严格保障的。即使有多个线程同时第一次访问某个类,Runtime 也能确保该类的初始化逻辑只被执行一次,且其他线程会处于阻塞等待状态。
以下是其底层保障机制的深度解析:
1. 锁的保护机制
在 libobjc 的源码中,+initialize 的触发位于 lookUpImpOrForward(查找方法实现)的过程中。
- 全局加锁:当一个类准备初始化时,Runtime 会获取一个全局的
classInitLock。 - 线程等待:如果线程 A 正在执行
[ClassA initialize],此时线程 B 也尝试调用ClassA的方法,线程 B 会被挂起(阻塞),直到线程 A 完成初始化并释放锁。 - 状态标记:一旦初始化完成,该类会被标记为
isRealized和isInitialized。后续访问将直接跳过初始化逻辑,不再有锁开销。
2. 递归死锁的预防
Runtime 的设计考虑到了递归调用的情况。
- 如果线程 A 在
+initialize内部又调用了该类自己的另一个方法,Runtime 会识别出“当前线程正在进行初始化”,从而允许直接通行,不会发生死锁。
3. 注意事项:虽然“安全”,但仍有“陷阱”
尽管底层机制是线程安全的,但在编写 +initialize 时,如果不注意业务逻辑,仍可能引发死锁或性能问题:
A. 避免耗时操作(阻塞风险)
由于其他线程在类初始化完成前会被阻塞,如果你在 +initialize 里执行了复杂的 IO、网络请求或大量计算,会导致所有并发调用该类的线程全部“卡死”,甚至引发 App 主线程掉帧或看门狗(Watchdog)杀掉进程。
B. 跨类调用的死锁(逻辑风险)
虽然单类初始化是安全的,但跨类相互调用可能导致经典的死锁:
- 线程 A 访问
Class A,进入[Class A initialize]。 - 线程 B 访问
Class B,进入[Class B initialize]。 - 此时,如果
Class A的初始化逻辑里尝试调用Class B,而Class B的初始化逻辑里尝试调用Class A。 - 结果:两个线程互相等待对方释放初始化锁,导致 App 永久卡死。
C. 继承带来的“多次执行”假象
虽然 +initialize 是线程安全的,但你可能会观察到它被“多次调用”。
-
原因:如果子类没有实现
+initialize,它会继承并调用父类的实现。 -
对策:为了确保你的初始化逻辑只运行一次,必须使用类型检查:
Objective-C
+ (void)initialize { if (self == [MyClass class]) { // 真正的初始化逻辑 } }
总结
+initialize 是 线程安全 的。它是执行单例配置、初始数据加载或 Method Swizzling 的理想场所,因为你完全不必担心并发冲突。