13-5.【OC】【性能】__strong / __weak / __unsafe_unretained 的性能差异是什么?

5 阅读3分钟

在 ARC 的内存管理体系中,这三种修饰符代表了三种截然不同的指令路径运行时开销

其性能差异的核心在于:为了保证安全性,系统究竟在后台做了多少次原子操作和哈希表查找。


1. __strong:标准的性能基准

这是 Swift 和 OC 的默认修饰符,性能开销处于中等水平。

  • 操作逻辑:每当发生赋值时,编译器插入 objc_retain,并在作用域结束时插入 objc_release

  • 性能消耗

    • 原子性加减:修改引用计数需要执行原子操作(Atomic Increment/Decrement),以防多线程竞争。
    • SideTable 访问:如果引用计数溢出或对象被弱引用,可能需要查找全局的 SideTable 哈希表。
  • 适用场景:绝大多数常规场景。


2. __weak:昂贵的安全屏障

__weak 是三者中最慢的,因为它引入了复杂的运行时维护机制。

  • 操作逻辑

    1. 注册阶段:对象创建时,其地址会被注册到全局的 Weak Table(哈希表)中。
    2. 访问阶段:每次读取弱引用变量时,都会调用 objc_loadWeakRetained。为了防止对象在读取瞬间被释放,系统会暂时 retain 该对象,读取后再 release
    3. 置空阶段(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/releaseobjc_initWeak/loadWeak
安全性极高极高(自动置空)极低(易崩溃)
相对耗时1x5x ~ 10x~0x

5. 深度分析:为什么 __weak 这么慢?

当你执行 id x = weakPointer; 时,底层其实发生了如下动作:

  1. 进入 objc_loadWeakRetained
  2. 获取全局 SideTable
  3. 在哈希表中根据对象地址查找对应的弱引用记录。
  4. 执行 retain(确保对象在赋值给 x 的过程中活着)。
  5. 释放锁。
  6. 返回对象,并在稍后对其执行 release

在高并发环境下,多个线程同时访问弱引用会导致严重的锁竞争(Lock Contention) ,这才是真正的性能杀手。


💡 性能优化建议

  1. 避免在循环中大量使用 __weak:如果需要频繁访问弱引用,先在循环外用一个 __strong 变量将其“锁定”(即著名的 Strong-Weak Dance 中的 strongSelf),这样循环内部就是快速的强引用访问。
  2. 谨慎使用 __unsafe_unretained:除非你在做音频处理、底层图像算法等对 CPU 周期极度敏感的工作,否则 __strong 带来的安全性溢价远超那点性能收益。