一、先给结论版(重要)
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 管理。