在 Swift 的内存管理中,unowned(无主引用)和 weak(弱引用)都用于打破循环强引用,但它们在安全性、底层实现、性能开销以及业务语义上有显著区别。
以下是深度的对比分析:
1. 核心区别对比表
| 特性 | weak (弱引用) | unowned (无主引用) |
|---|---|---|
| 可选性 | 必须是 Optional (T?) | 必须是 非可选值 (T) |
| 自动置 nil | 是。对象销毁后自动变为 nil | 否。对象销毁后指针依然存在(悬垂) |
| 崩溃风险 | 零风险。访问已销毁对象返回 nil | 有风险。访问已销毁对象会 Runtime Trap (崩溃) |
| 底层实现 | 通过 Side Table (侧表) 管理 | 通过 Unowned 计数 和“僵尸内存”管理 |
| 生命周期 | 引用的对象可以比自己先销毁 | 假定被引用的对象生命周期更长或同步 |
| 性能 | 较低(需处理可选值和侧表寻址) | 较高(接近强引用,无需解包) |
2. 底层机制的差异
Weak:侧表机制
weak 指针并不直接指向对象,而是指向一个 Side Table。
- 当对象销毁时,侧表里的
Strong Count变为 0。 - 访问
weak变量时,运行时检查侧表状态,发现对象已死则返回nil。 - 优点:绝对安全。
Unowned:僵尸内存 (Zombie Memory)
unowned 指针直接指向对象内存。
- 当对象强引用归零时,它会执行
deinit并释放资源,但它的 物理内存不会立即回收。 - 此时对象处于一种“半死不活”的状态,内存里保留着引用计数信息。
- 如果此时你访问
unowned引用,Swift 运行时会检查计数器,发现对象已析构,于是主动触发崩溃以防止野指针非法访问。 - 只有当所有的
unowned引用也消失后,这块物理内存才会被彻底free。
3. 使用场景与设计指南
场景 A:使用 weak (安全优先)
当你无法确定引用的对象什么时候会被销毁,或者该引用在逻辑上允许为 nil 时。
- Delegate 模式:
weak var delegate: MyDelegate? - 闭包捕获:
[weak self]。如果闭包被触发时对象可能已经不在了(如异步网络请求返回)。 - 相互关联但生命周期独立:比如两个 View 互有引用。
场景 B:使用 unowned (性能与逻辑约束)
当你确定被引用的对象一定会和当前对象同时存在,或者比当前对象活得更久。这通常表达了一种“主从关系”。
-
父子关系:比如
CreditCard(信用卡)和Customer(持有者)。没有客户就不可能有信用卡,信用卡的生命周期绝对不会超过客户。Swift
class CreditCard { unowned let customer: Customer init(customer: Customer) { self.customer = customer } } -
闭包中的同步操作:如果你确信闭包执行时
self绝对不会被释放(例如在self的init期间执行的闭包)。
4. 总结:如何选择?
-
如果你不确定,请永远使用
weak。虽然它有微小的性能损耗,但能保证程序不会因为内存释放顺序问题而崩溃。 -
只有在以下三个条件同时满足时才使用
unowned:- 你需要追求极致的性能(避开侧表和 Optional 开销)。
- 逻辑上该引用永远不应该为
nil。 - 你能 100% 保证引用的对象在生命周期上覆盖了当前对象。
英文版
8-9. [Memory Management] What are the differences between unowned and weak? What are their use cases?
In Swift, both unowned and weak references are used to break strong reference cycles. However, they differ significantly in terms of safety, underlying implementation, performance overhead, and business semantics.
1. Core Comparison Table
| Feature | weak | unowned |
|---|---|---|
| Optionality | Must be Optional (T?) | Must be Non-optional (T) |
| Auto-nil-ing | Yes. Automatically becomes nil after the object is destroyed. | No. The pointer persists after the object is destroyed (dangling). |
| Crash Risk | Zero Risk. Accessing a destroyed object returns nil. | High Risk. Accessing a destroyed object triggers a Runtime Trap (Crash) . |
| Implementation | Managed via Side Table. | Managed via Unowned Count and "Zombie Memory." |
| Lifecycle | The referenced object can be destroyed before the reference. | Assumes the referenced object lives longer or exactly as long. |
| Performance | Lower (due to Optional handling and Side Table lookup). | Higher (comparable to strong refs; no unwrapping needed). |
2. Differences in Underlying Mechanisms
Weak: The Side Table Mechanism
A weak pointer does not point directly to the object; it points to a Side Table.
- When the object is destroyed, the
Strong Countin the Side Table becomes 0. - When you access a
weakvariable, the runtime checks the Side Table status. If it finds the object is dead, it returnsnil. - Advantage: Absolute safety.
Unowned: Zombie Memory
An unowned pointer points directly to the object's memory.
- When the strong count reaches zero, the object executes
deinitand releases its resources, but its physical memory is not immediately reclaimed. - The object enters a "Zombie" state, where the memory still holds reference count metadata.
- If you access the
unownedreference at this point, the Swift runtime checks the counter, realizes the object has de-initialized, and triggers a controlled crash to prevent illegal access via a dangling pointer. - The physical memory is only fully
free-ed once allunownedreferences have also vanished.
3. Use Cases and Design Guidelines
Scenario A: Use weak (Safety First)
Use this when you cannot determine when the referenced object will be destroyed, or when the reference is logically allowed to be nil.
- Delegate Pattern:
weak var delegate: MyDelegate? - Closure Captures:
[weak self]. Especially in asynchronous network requests where the object might be gone by the time the response returns. - Independent Lifecycles: Two views that reference each other but can be destroyed independently.
Scenario B: Use unowned (Performance & Logical Constraints)
Use this when you are certain the referenced object will always exist as long as the current object, or will outlive it. This usually expresses a "Master-Slave" or "Parent-Child" relationship.
- Parent-Child Relationships: For example, a
CreditCardand aCustomer. A credit card cannot exist without a customer; logically, the card's lifecycle will never exceed the customer's.
Swift
class CreditCard {
unowned let customer: Customer
init(customer: Customer) { self.customer = customer }
}
- Synchronous Operations in Closures: If you are certain
selfwill not be released while the closure is executing (e.g., a closure executed duringself.init).
4. Summary: How to Choose?
-
If you are unsure, always use
weak. While it has a marginal performance cost, it guarantees your app won't crash due to unexpected memory deallocation orders. -
Use
unownedonly when all three of these conditions are met:- You require peak performance (avoiding Side Table and Optional overhead).
- Logically, the reference should never be
nil. - You can 100% guarantee that the referenced object's lifecycle covers the current object's lifecycle.