5-27.【性能分析与优化】weak、unowned、强引用在性能上的真实差异是什么?什么场景下 weak 反而更慢?

3 阅读3分钟

一句话结论(先给你直觉)

性能顺序(单次访问成本)通常是:

强引用 < unowned < weak

但:

  • 强引用:有 retain/release
  • unowned:无 retain,但有校验
  • weak:无 retain,但有 runtime 表查询 + 原子操作

所以在热路径里,weak 往往是最慢的


一、三种引用的真实成本模型

1️⃣ 强引用(strong)

做了什么

  • retain / release
  • 多线程下是原子操作

成本特征

  • 成本可被 合并、下沉、消除
  • 编译器最容易优化

Time Profiler 中看到

  • swift_retain
  • swift_release

什么时候快?

  • 长生命周期
  • 非 escaping
  • final / private 场景

💡 强引用的“慢”,其实是最可控、最可优化的


2️⃣ unowned(非零成本,但很轻)

做了什么

  • 不 retain
  • 每次访问做一次有效性检查(trap 保障)

成本模型

  • 指针 load
  • 轻量校验
  • 无哈希表、无 atomic retain

特点

  • 比 strong 少 ARC
  • 比 weak 少 runtime 查表

风险

  • 对象已释放 → crash(不是 nil)

性能上,unowned 是 “接近裸指针”的安全版本


3️⃣ weak(最贵,但最安全)

真正发生了什么?

weak 不是一个简单的可选引用,它涉及:

  • 全局 weak reference table
  • 对象 deinit 时,表中所有 weak 自动置 nil
  • 多线程安全(atomic)

每次访问 weak:

  1. 通过指针查 weak table
  2. 原子 load
  3. nil-check
  4. 返回 Optional

👉 这是 runtime 行为,不是编译期行为


二、为什么 weak 在很多场景下反而更慢?

🔥 场景 1:热路径中的 weak 访问

for _ in 0..<1_000_000 {
    self?.work()
}

每一轮:

  • weak table lookup
  • atomic load
  • Optional unwrap

👉 比一次 retain + release 还贵


🔥 场景 2:频繁解包 weak self

{ [weak self] in
    guard let self else { return }
    self.work()
}

表面上:

  • 只 unwrap 一次

实际上:

  • closure invoke
  • weak load
  • guard 分支

在 tight loop 中仍然明显


🔥 场景 3:跨线程 + weak

  • weak 本身就需要同步
  • 跨线程访问 → cache miss + fence

👉 成本放大


🔥 场景 4:weak 阻止编译器优化

  • weak 表示生命周期不可预测

  • 编译器:

    • 不能内联
    • 不能消除访问
    • 不能 hoist

三、什么时候 strong 反而比 weak 快?

✅ 情况 1:生命周期明确

func run(_ obj: Obj) {
    for _ in 0..<1000 {
        obj.work()
    }
}
  • obj 被 retain 一次
  • 循环内部无 ARC

✅ 情况 2:用 strong capture + 手动断环

class A {
    func setup() {
        let handler = { [self] in
            self.work()
        }
        self.handler = handler
    }

    deinit {
        handler = nil
    }
}
  • 性能好
  • 生命周期可控

四、unowned 的真实定位(常被误解)

什么时候 unowned 是最优解?

  • 生命周期严格受控
  • 对象 保证 存活
  • 热路径
  • 非并发

性能排序(典型)

裸指针 < unowned < strong < weak

⚠️ 但一旦你不 100% 确定生命周期,unowned 是“性能型炸弹”


五、选择指南(工程级)

性能敏感路径(UI diff / layout / parser)

场景推荐
生命周期明确strong / unowned
热循环strong(外部 retain)
高频 closure解构字段,不捕获 self
SwiftUI body少用 weak

非性能敏感路径(回调 / async / delegate)

场景推荐
delegateweak
async 回调weak
observerweak

六、一个非常重要但少被提及的点

weak 是一种“同步原语”,不是语言糖

你用 weak 的那一刻,就在告诉 runtime:

“这个引用可能在任何时刻被释放,我需要绝对安全。”

安全是有成本的,而且这个成本是每次访问都要付


七、最终总结(记住这个)

weak 是最安全的,也是最慢的。
strong 是最容易优化的。
unowned 是性能与风险的中间态。

所以在性能敏感代码里,我的真实决策顺序是:

  1. 能不用引用 → struct
  2. 能用 strong 且可控 → strong
  3. 生命周期严格 → unowned
  4. 其他 → weak