7-2.【高级特性】struct、class、enum 在栈和堆中的分配差异是什么?

4 阅读2分钟

一、先给结论版(重要)

Swift 中是否分配在栈或堆,取决于“值的生命周期和逃逸情况”,
而不是仅由 struct / class / enum 决定。

但在绝大多数常见场景下:

类型本体通常在哪里变量里存的是什么
struct栈 / 内嵌在宿主对象中实际数据
enum栈 / 内嵌实际数据(含 tag)
class指向堆对象的引用

下面我们拆开说「为什么」。


二、struct 的分配特点

1️⃣ struct 是值类型

  • 存的是 数据本身
  • 不需要引用计数
  • 编译器可以自由决定放哪

2️⃣ 常见情况:栈分配

struct Point {
    var x: Int
    var y: Int
}

func foo() {
    let p = Point(x: 1, y: 2)
}
  • p 是局部变量
  • 生命周期明确
  • 👉 通常分配在栈上

内存布局(简化):

Stack:
[x][y]

3️⃣ struct 也可能“看起来在堆上”

class Box {
    var point: Point
}
  • Box 在堆上
  • point 作为成员内嵌在 Box 的堆内存中
  • 并不是 struct 自己申请了堆

⚠️ 注意:

struct 不会“单独”被 ARC 分配到堆
只是被包含在一个堆对象里


三、class 的分配特点

1️⃣ class 是引用类型

class Person {
    var name: String
}

2️⃣ 对象本体:一定在堆上

let p = Person()

内存关系是:

Stack:        Heap:
[p] ──────▶ [Person object]
  • 栈里存的是 引用(指针)

  • 堆里是:

    • isa / metadata
    • 属性
    • ARC 引用计数相关信息

3️⃣ 为什么必须在堆上?

  • 多个变量可共享
  • 生命周期不受作用域限制
  • 需要 ARC 管理

👉 这三个条件栈都做不到


四、enum 的分配特点(很多人忽略)

1️⃣ enum 也是值类型

  • 本质和 struct 一样
  • 不参与 ARC
enum Result {
    case success(Int)
    case failure(Error)
}

2️⃣ 常见情况:栈 / 内嵌

func test() {
    let r = Result.success(10)
}
  • r 通常在栈上

  • 内存结构大致是:

    • tag(当前 case)
    • 关联值(最大 case 的空间)
[tag][payload...]

3️⃣ enum 什么时候会“涉及堆”?

不是 enum 自己上堆,而是:

enum E {
    case value(String)
}
  • String 内部是 COW + 堆存储
  • enum 里只是 嵌了一个 String 值
  • 堆是 String 用的,不是 enum

五、关键点:逃逸分析(面试加分)

Swift 编译器会做 Escape Analysis

func makePoint() -> Point {
    Point(x: 1, y: 2)
}
  • Point 被返回
  • 生命周期超出函数栈帧
  • 👉 可能被提升到堆上(heap promotion)

⚠️ 但这是编译器优化细节

  • 对开发者语义无影响
  • 仍然是值类型、无 ARC

六、一张终极对照表

类型值 / 引用本体是否必须在堆是否 ARC常见分配
struct栈 / 内嵌
enum栈 / 内嵌
class引用

七、面试用一句话版本

struct 和 enum 是值类型,通常栈分配或内嵌;是否上堆由逃逸分析决定;
class 是引用类型,对象本体必须在堆上,由 ARC 管理。