一、先给终极结论(面试版)
SwiftUI 必须依赖“值语义 + 不可变描述”,
struct 才能让 View 的 diff、重建和性能优化成立。
换句话说:
SwiftUI 的 View 不是“UI 对象”,而是“UI 描述快照”。
二、SwiftUI 的 View 到底是什么?
struct MyView: View {
var body: some View {
Text("Hello")
}
}
⚠️ 关键点:
-
View不是屏幕上的控件 -
不是
UIView -
它只是一个:
描述 UI 结构和状态依赖关系的值
每次状态变化,SwiftUI 会:
- 重新创建一棵 View 值树
- 和上一棵做 diff
- 计算最小 UI 更新
👉 View 的创建成本必须极低
三、为什么 struct 非常适合这件事?
1️⃣ 值语义 = 可预测的 diff
struct 有两个关键特性:
- 不可变(语义上)
- 拷贝即冻结状态
let old = MyView(count: 1)
let new = MyView(count: 2)
-
两个 View 是两个独立值
-
SwiftUI 可以放心比较:
- 字段是否变化
- 结构是否一致
👉 这让 diff 成为“纯函数问题”
2️⃣ class 会破坏 diff 的基本假设 ❌
如果 View 是 class:
let v1 = MyView()
let v2 = v1
v2.count = 2
SwiftUI 会面对灾难级问题:
v1 === v2?- 内部属性是否被偷偷改过?
- 是否需要刷新 UI?
- 谁在什么时候改的?
👉 引用语义 = 不可追踪的副作用
四、struct 与性能的直接关系
1️⃣ View 重建是“高频事件”
- 状态变化
- 父 View 更新
- 环境值变化
SwiftUI 的策略是:
大量、频繁、廉价地重建 View
struct 非常适合:
| 点 | struct | class |
|---|---|---|
| 分配 | 栈 / 内嵌 | 堆 |
| ARC | 无 | 有 |
| 创建成本 | 极低 | 高 |
| 拷贝 | 便宜 / COW | 增减引用计数 |
2️⃣ ARC 开销是 SwiftUI 的天敌
假设用 class:
-
每次 body 计算:
- retain / release
-
diff 阶段:
- 比较引用关系
-
状态传播:
- 难以保证一致性
👉 ARC 抖动会直接体现在掉帧上
五、struct + diff 的核心关系
SwiftUI 的 diff 依赖三件事:
✅ 1. View 是纯值
- 同样输入 → 同样输出
- 无隐藏状态
✅ 2. View 构造是确定性的
var body: some View {
if isOn {
A()
} else {
B()
}
}
- 结构变化可预测
- diff 可以按类型 + 位置匹配
✅ 3. identity 明确
ForEach(id:).id(_:)
👉 这些 都是值语义友好的设计
六、如果 SwiftUI 用 class,会发生什么?
直接说结论:几乎不可实现
问题包括:
- diff 无法信任对象内部是否变化
- 必须深度观察每个属性(KVO 噩梦)
- 状态同步复杂到不可控
- 性能极差(大量堆对象 + ARC)
这正是 UIKit 的模式:
- 对象多
- 生命周期复杂
- 手动管理状态
SwiftUI 的目标正是反过来。
七、SwiftUI 中“真正的对象”在哪里?
关键点:
View 是 struct,但状态和副作用不是
| 角色 | 类型 |
|---|---|
| View | struct |
| 状态 | class / actor |
| 数据源 | ObservableObject |
| 副作用 | Task / Actor |
| 渲染目标 | UIKit / AppKit 对象 |
@StateObject var model = ViewModel()
ViewModel是 class- 生命周期稳定
- View 只是“读它”
👉 值描述 + 引用状态 = 平衡点
八、终极面试总结(建议原话背)
SwiftUI 的 View 必须是 struct,因为它是 UI 的值描述;
值语义让 View 的 diff 可预测、可比较、无副作用;
同时避免 ARC 开销,使得 SwiftUI 能高频重建 View 而不影响性能。