一句话结论(先给直觉)
SwiftUI 的
View不是 UI 本身,而是“如何构建 UI 的说明书(blueprint)”;
真正“持续存在”的,是 SwiftUI 在幕后维护的一棵 UI 状态树(渲染树)。
所以:
View是 值类型、随时可以被丢弃- UI 的持续性来自 State + Identity + Diff 算法,而不是 View 实例
一、先把最大的误解拆掉
❌ 错误直觉(UIKit 思维)
View struct = 屏幕上的那个 UI
✅ SwiftUI 的真实模型
View struct = 描述 UI 的函数 / 配方
UI 实体 = SwiftUI Runtime 管理的 Render Tree
你写的:
struct ContentView: View {
var body: some View {
Text("Hello")
}
}
并不是在“创建一个 Text 对象”,而是在说:
“当前状态下,UI 应该长这样。”
二、SwiftUI 真正“持续存在”的是什么?
不是 View,而是 三样东西:
1️⃣ State(状态)
@State var count: Int
@StateObject var viewModel
- State 不存放在 View struct 里
- SwiftUI Runtime 把它们存到一个 独立的状态存储(State Store)
即使 View struct 被销毁、重建:
View struct ❌ 重建
State Store ✅ 继续存在
2️⃣ Identity(身份)
SwiftUI 通过 identity 知道:
“这是同一个 UI 元素,还是一个新的?”
来源包括:
- View 在树中的位置
.id(...)ForEach的 id@StateObject的创建点
这解决了一个关键问题:
“我这次生成的 View,是不是上一次那个?”
3️⃣ Render Tree(渲染树)
SwiftUI 在幕后维护一棵 引用类型的 UI 树:
RenderNode (Text)
RenderNode (Button)
RenderNode (VStack)
- 类似 UIKit 的
UIView树 - 但你 永远不直接接触
- SwiftUI 负责 diff & 更新
三、状态变化时发生了什么?(关键流程)
假设你有:
struct CounterView: View {
@State var count = 0
var body: some View {
Text("(count)")
Button("+") { count += 1 }
}
}
当你点按钮:
① State 改变
State Store: count = 0 → 1
② SwiftUI 重新“调用 body”
重新生成 CounterView(新的 struct 值)
⚠️ 注意:
不是“更新 View”,而是“重新计算 View 描述”
③ Diff(差分)
SwiftUI 比较:
上一次 View 描述
vs
这一次 View 描述
发现:
- Text 的内容变了
- Button 没变
④ 最小化更新 Render Tree
只更新 Text 对应的 RenderNode
👉 UI 看起来“持续存在”,但其实是 精准替换
四、为什么用值类型反而是优势?
1️⃣ View = 纯函数
View = f(State)
- 没有隐藏副作用
- 没有“半更新”状态
- 可以无限次重建
这直接解决了 UIKit 中最痛苦的问题:
❌ “现在 UI 是不是和状态一致?”
2️⃣ 没有生命周期陷阱
在 SwiftUI 中:
- 没有
viewDidLoad - 没有
viewWillAppear - 没有“这个 VC 现在活没活着”
因为:
View 本身不承载生命周期,State 才承载
3️⃣ 极强的可预测性(TCA 的根基)
这也是为什么 TCA / Redux 能在 SwiftUI 中如此自然:
Action → State → View
如果 View 是引用类型、可变对象:
- 单一 State 就失效
- 时间旅行就做不到
五、那 @StateObject 为什么“只创建一次”?
这正是 identity 在起作用。
@StateObject var vm = ViewModel()
SwiftUI 规则是:
在相同 View identity 位置,
@StateObject只初始化一次
- View struct 重建 ❌
- ViewModel 实例保留 ✅
这也是为什么你 不能 在子 View 中随便 new ViewModel —— identity 会变。
六、一个类比(非常重要)
把 SwiftUI 想成这样:
View= React / Flutter 里的 Widgetbody= render 函数- State = 外部 Store
- Render Tree = UIKit / AppKit 对象树
你写的不是 UI 本体,而是:
“状态 → UI 的映射关系”
七、最终总结(一句话版)
SwiftUI 中 UI 的“持续存在”,不是因为 View 活着,而是因为 State + Identity + Render Tree 活着;
View 只是一个随时可被丢弃、随时可重建的描述值。
如果你愿意,下一步我可以帮你:
- 用 SwiftUI vs UIKit 生命周期对照表,一眼打通直觉
- 解释 为什么 SwiftUI 里不能在 View 里做副作用
- 或从 TCA / 单向数据流 角度再推导一遍这个模型