在 Swift 的底层实现中,这三种引用的区别远不止是“自动置空”那么简单。它们实际上是通过操作 HeapObject 的位域(Bitfields)和引入 Side Table(侧表) 机制来管理的。
1. Strong(强引用):内联计数
强引用是 Swift 最基础的内存管理方式。
- 存储位置:直接存储在对象头部的
RefCounts字段中。 - 底层机制:当产生强引用时,运行时调用
swift_retain(),它利用原子操作递增 64 位复合字中的 Strong Extra RefCount 部分。 - 性能:极高。它是一次原子指令操作,不涉及额外的内存分配。
2. Weak(弱引用):侧表(Side Table)
弱引用是最复杂的。为了实现“对象销毁后自动置空(Zeroing)”,Swift 引入了侧表。
-
存储位置:弱引用指针并不直接指向对象,而是指向该对象的 Side Table。
-
侧表的诞生:当一个对象第一次被
weak引用时,系统会在堆上分配一个HeapSideTableEntry。原对象头部的RefCounts会被修改,其中一个特定的位(Bit)会被标记,其余位存储侧表的内存地址。 -
工作流程:
- 对象销毁(强引用归零)后,对象内存被释放,但侧表不会立即消失。
- 当你在代码中访问弱引用时,程序先跳转到侧表,检查侧表里的“强引用计数”。
- 如果发现强引用已归零,则返回
nil。 - 只有当所有弱引用也都消失后,侧表才会被回收。
3. Unowned(无主引用):半销毁状态
unowned 是强引用和弱引用的折中方案,它在安全性和性能之间做了平衡。
-
存储位置:同样存储在对象头部的
RefCounts位域中(Unowned RefCount)。 -
底层机制:它不增加强引用计数,但会增加无主引用计数。
-
生命周期(Zombie 状态) :
- 当强引用归零时,对象会执行
deinit并释放其持有的资源,但对象的物理内存(HeapObject 结构)不会被立即回收。 - 此时对象处于“僵尸”状态。如果此时访问
unowned引用,运行时会检查计数器并发现对象已析构,随后直接触发 Runtime Trap(崩溃) 。 - 只有当无主引用计数也归零时,这块物理内存才会被真正
free。
- 当强引用归零时,对象会执行
4. 深度对比表
| 特性 | Strong | Weak | Unowned |
|---|---|---|---|
| 引用计数位置 | 对象头部 (Inline) | Side Table (侧表) | 对象头部 (Inline) |
| 对象销毁后 | 内存立即回收 | 侧表保留,指针变 nil | 内存保留,直到无主计数归零 |
| 访问安全性 | 绝对安全 | 返回可选值 (nil) | 触发崩溃 (Runtime Trap) |
| 性能开销 | 极低 (原子操作) | 较高 (多层间接寻址) | 低 (原子操作,优于 weak) |
| 内存溢出风险 | 循环强引用导致泄漏 | 无 | 悬垂指针访问导致崩溃 |
5. 什么时候该选哪种?
- Strong:默认选择,用于表达“拥有”关系。
- Weak:用于可能在生命周期内变为
nil的引用。典型的如delegate、IBOutlet以及闭包中的[weak self]。它最安全,但略慢。 - Unowned:用于确定引用对象生命周期等于或长于当前对象的场景。例如,一个
CreditCard实例引用它的User。它性能更好,且不需要处理可选值(Optional)。
英文版
8-7. [Memory Management] How are Strong, Weak, and Unowned references stored and managed at the low level?
In Swift's underlying implementation, the difference between these references goes far beyond "automatic nil-ing." They are managed through sophisticated manipulations of the HeapObject bitfields and the introduction of the Side Table mechanism.
1. Strong Reference: Inline Counting
Strong references are the most fundamental way Swift manages memory.
- Storage Location: Stored directly within the
RefCountsfield of the object's header. - Low-level Mechanism: When a strong reference is created, the runtime calls
swift_retain(). This uses atomic operations to increment the Strong Extra RefCount portion of a 64-bit composite word in the header. - Performance: Extremely high. it is a single atomic instruction and involves no extra memory allocation.
2. Weak Reference: The Side Table
Weak references are the most complex. To implement "automatic zeroing" after an object is destroyed, Swift introduces the Side Table.
-
Storage Location: A weak pointer does not point directly to the object. Instead, it points to the object's Side Table (
HeapSideTableEntry). -
Birth of a Side Table: When an object is first referenced by a
weakpointer, the system allocates aHeapSideTableEntryon the heap. The original object'sRefCountsheader is modified: a specific bit is toggled to indicate a side table exists, and the remaining bits store the memory address of that side table. -
Workflow:
- After the object is destroyed (strong count reaches zero), the object's memory is deallocated, but the Side Table remains.
- When you access the weak reference in your code, the program jumps to the Side Table to check the "Strong Reference Count."
- If the strong count is zero, it returns
nil. - The Side Table is only reclaimed once all weak references to it have also vanished.
3. Unowned Reference: The "Zombie" State
unowned is a middle ground between strong and weak references, balancing performance and safety.
-
Storage Location: Also stored within the object's header bitfields (Unowned RefCount).
-
Low-level Mechanism: It does not increment the strong reference count, but it increments the unowned reference count.
-
Lifecycle (The Zombie State) :
- When the strong count reaches zero, the object executes
deinitand releases its held resources, but the physical memory of the object (the HeapObject structure) is not immediately freed. - The object is now in a "Zombie" state. If you access the
unownedreference now, the runtime checks the counter, sees the object has been de-initialized, and triggers a Runtime Trap (Crash) . - The physical memory is only truly
free-ed once the unowned reference count also reaches zero.
- When the strong count reaches zero, the object executes
4. Deep Comparison Table
| Feature | Strong | Weak | Unowned |
|---|---|---|---|
| Count Location | Object Header (Inline) | Side Table (External) | Object Header (Inline) |
| After Deallocation | Memory freed immediately | Side Table remains; pointer becomes nil | Memory remains until unowned count is 0 |
| Access Safety | Guaranteed safe | Returns Optional (nil) | Triggers Runtime Trap (Crash) |
| Performance Cost | Minimal (Atomic op) | Higher (Multiple indirections) | Low (Atomic op, better than weak) |
| Risk | Strong cycles lead to leaks | None | Dangling pointer access leads to crash |
5. When to choose which?
- Strong: The default choice, used to express an "ownership" relationship.
- Weak: Use for references that may become
nilduring their lifecycle. Typical cases includedelegates,IBOutlets, and[weak self]in closures. It is the safest but slightly slower. - Unowned: Use when you are certain the referenced object's lifecycle is equal to or longer than the current object. For example, a
CreditCardinstance referencing itsUser. It offers better performance and avoids the need to handle Optionals.