基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

值类型(如 Struct、Enum)会出现循环引用吗?

知识点图片

简单直接的回答是:值类型(Struct、Enum)本身不会产生循环引用,但在特定场景下,它们可以成为循环引用链条中的一部分。

为了深入理解这个问题,我们需要分三个层面来看:

1. 为什么值类型本身不会循环引用?

循环引用(Reference Cycle / Retain Cycle)是引用计数(Reference Counting)内存管理机制下的产物。它的核心在于两个对象互相持有对方的“强引用”,导致引用计数无法归零。

  • 没有引用计数: 值类型(Struct, Enum)通常分配在栈(Stack)上(或者内联在其他对象中),它们不使用 ARC(自动引用计数)来管理内存。当它们超出作用域时,内存会自动释放。
  • 复制语义(Copy Semantics): 当你把一个 Struct 赋值给另一个变量,或者传参给函数时,发生的是拷贝(Copy)。
    • 如果 Struct A 想要持有 Struct B,它持有的是 B 的拷贝。
    • 如果 Struct B 想要反过来持有 Struct A,它持有的是 A 的拷贝。
    • 结果: 你无法让两个值类型实例互相“指向”对方的内存地址,因此物理上无法形成环。

2. 编译器的限制(递归定义)

如果你尝试定义一个包含自身的 Struct,编译器会直接报错:

plaintext
struct Node {
    var next: Node // ❌ 编译错误:Value type 'Node' cannot have a stored property that recursively contains it
}

原因: 值类型的大小必须在编译时确定。如果 Struct A 包含 Struct A,那么它的大小就是无穷大,这是不可能的。虽然 indirect enum 允许递归定义,但那是通过内部装箱(Box)实现的,逻辑上依然是树状结构而非环状图,因为赋值依然是拷贝。


3. 陷阱:值类型作为“容器”导致循环引用

虽然 Struct 本身不引用别人,但 Struct 可以持有引用类型(如 Class 或 Closure)。如果 Struct 被一个 Class 持有,而 Struct 内部的引用类型又指回了这个 Class,就会形成循环引用。

这是面试和开发中最容易被忽视的场景。

典型案例:Struct 持有 Closure

plaintext
class MyClass {
    var myStruct: MyStruct?
    
    func setup() {
        // 1. 创建 Struct
        var s = MyStruct()
        
        // 2. Struct 持有一个闭包,闭包捕获了 self (MyClass)
        s.action = { [action_capture_self = self] in
            print(action_capture_self.description)
        }
        
        // 3. MyClass 持有 Struct
        self.myStruct = s
    }
}

struct MyStruct {
    // Struct 持有引用类型(闭包)
    var action: (() -> Void)?
}

引用链分析:
MyClass (强引用) -> MyStruct (值类型) -> Closure (引用类型) -> MyClass (强引用)

虽然 MyStruct 是值类型,但它充当了中间的“容器”。

  • MyClass 活着,MyStruct 就活着。
  • MyStruct 活着,Closure 就活着。
  • Closure 强捕获了 MyClass
  • 结果: 内存泄漏。

解决方法:
在闭包中使用 [weak self][unowned self]

典型案例:Struct 持有 Class

plaintext
class Person {
    var name: String = "Bob"
}

struct Wrapper {
    var person: Person // Struct 持有一个 Class 实例
}

class Container {
    var wrapper: Wrapper?
}

let c = Container()
let p = Person()
// 假设 Person 类里也有个属性指向 Container,那就形成了循环
// Container -> Wrapper(Struct) -> Person(Class) -> Container

在这种情况下,虽然中间夹着一个 Struct,但本质上还是对象之间的循环引用。Struct 只是作为一个载体,传递了引用的所有权。

总结

  1. 纯粹的值类型之间绝对不会发生循环引用(因为是拷贝,且无引用计数)。
  2. 值类型内部:如果值类型持有了引用类型(Class 实例、Closure),那么这个值类型就可能成为循环引用链条中的一环。

一句话口诀: 值类型本身不背锅,但它肚子里装的引用类型可能会闯祸。

00:00
00:00