在 ARC 的内存管理体系中,这三种修饰符代表了三种截然不同的指令路径和运行时开销。
其性能差异的核心在于:为了保证安全性,系统究竟在后台做了多少次原子操作和哈希表查找。
1. __strong:标准的性能基准
这是 Swift 和 OC 的默认修饰符,性能开销处于中等水平。
-
操作逻辑:每当发生赋值时,编译器插入
objc_retain,并在作用域结束时插入objc_release。 -
性能消耗:
- 原子性加减:修改引用计数需要执行原子操作(Atomic Increment/Decrement),以防多线程竞争。
- SideTable 访问:如果引用计数溢出或对象被弱引用,可能需要查找全局的
SideTable哈希表。
-
适用场景:绝大多数常规场景。
2. __weak:昂贵的安全屏障
__weak 是三者中最慢的,因为它引入了复杂的运行时维护机制。
-
操作逻辑:
- 注册阶段:对象创建时,其地址会被注册到全局的
Weak Table(哈希表)中。 - 访问阶段:每次读取弱引用变量时,都会调用
objc_loadWeakRetained。为了防止对象在读取瞬间被释放,系统会暂时retain该对象,读取后再release。 - 置空阶段(Nil out) :对象销毁时,运行时必须遍历
Weak Table,通过哈希查找找到所有指向该地址的指针并将其置为nil。
- 注册阶段:对象创建时,其地址会被注册到全局的
-
性能消耗:涉及多次哈希查找、锁竞争(Lock Acquisition)以及额外的函数调用。
-
适用场景:解决循环引用,且允许指针自动置空。
3. __unsafe_unretained:极致的性能表现
它是三者中最快的,性能几乎等同于 C 语言的原始指针。
-
操作逻辑:编译器完全不插入任何内存管理指令(没有 retain/release)。
-
性能消耗:零开销。仅仅是简单的赋值和寻址。
-
致命缺陷:它不具备安全性。如果指向的对象被销毁,指针会变成野指针(Dangling Pointer) ,继续访问会导致 Crash。
-
适用场景:
- 在极高性能要求的紧密循环中。
- 在 Swift 的
unowned对应的低层级优化中(虽然unowned稍有不同,它会检查有效性,但思路一致)。
4. 性能横向对比表
| 特性 | __strong | __weak | __unsafe_unretained |
|---|---|---|---|
| 原子操作 | 是(修改引用计数) | 是(频繁加解锁) | 否 |
| 哈希表查找 | 偶有(SideTable) | 极频繁 (WeakTable) | 否 |
| 运行时函数调用 | objc_retain/release | objc_initWeak/loadWeak | 无 |
| 安全性 | 极高 | 极高(自动置空) | 极低(易崩溃) |
| 相对耗时 | 1x | 5x ~ 10x | ~0x |
5. 深度分析:为什么 __weak 这么慢?
当你执行 id x = weakPointer; 时,底层其实发生了如下动作:
- 进入
objc_loadWeakRetained。 - 获取全局
SideTable的锁。 - 在哈希表中根据对象地址查找对应的弱引用记录。
- 执行
retain(确保对象在赋值给x的过程中活着)。 - 释放锁。
- 返回对象,并在稍后对其执行
release。
在高并发环境下,多个线程同时访问弱引用会导致严重的锁竞争(Lock Contention) ,这才是真正的性能杀手。
💡 性能优化建议
- 避免在循环中大量使用
__weak:如果需要频繁访问弱引用,先在循环外用一个__strong变量将其“锁定”(即著名的Strong-Weak Dance中的strongSelf),这样循环内部就是快速的强引用访问。 - 谨慎使用
__unsafe_unretained:除非你在做音频处理、底层图像算法等对 CPU 周期极度敏感的工作,否则__strong带来的安全性溢价远超那点性能收益。