一句话结论(工程直觉版)
SwiftUI 的 diff 本质是在比较“前后两次 View 描述树”,
diff 成本 ≈ 失效的 View 数量 × 每个 View 的 body 复杂度。
你要做的不是“让 SwiftUI 不 diff”,
而是 缩小参与 diff 的那棵子树。
一、SwiftUI 的 diff 到底在 diff 什么?
1️⃣ diff 的对象不是 UIView
SwiftUI 比较的是:
旧的 View 描述树(值)
vs
新的 View 描述树(值)
不是在 diff:
- UIView
- CALayer
- 实际渲染结果
2️⃣ SwiftUI 用三件事判断“是不是同一个 View”
Type + Position + Identity
① Type
Text("A") vs Text("B") → 同类型
Text vs Image → 不同类型
② Position(结构位置)
VStack {
A
B
}
A 永远是第 0 个子节点。
③ Identity(显式/隐式)
.id(...)ForEach(id:)@StateObject的创建点
identity 决定:
“这次生成的 View,是不是上一次那个?”
3️⃣ diff 的基本流程(简化)
State 改变
→ 找到依赖该 State 的 View
→ 重新计算 body
→ 生成新的 View 树
→ 深度优先 diff(旧 vs 新)
→ 更新最小 Render Tree
二、什么时候 diff 成本会变得很高?
1️⃣ 状态挂在“过高层级”的 View
struct BigScreen: View {
@State var count = 0 // ❌
...
}
- count 改变
- 整个 BigScreen 子树全部参与 diff
👉 失效半径过大
2️⃣ body 中包含昂贵计算
var body: some View {
let sorted = data.sorted() // ❌ 每次 diff 都重算
return List(sorted) { ... }
}
- diff 之前,body 必须先算完
- 算法复杂度直接乘上 diff 次数
3️⃣ identity 不稳定(最隐蔽)
❌ 错误示例
ForEach(items) { item in
Row(item: item)
}
但 item.id 实际上:
- 每次刷新都会变
- 或者你用的是 index
👉 SwiftUI 认为:
“全部是新 View”
结果:
- diff 退化为 全量销毁 + 重建
4️⃣ .id(UUID()) 滥用(自杀式写法)
VStack {
Content()
}
.id(UUID()) // ❌
这等于告诉 SwiftUI:
“每次 State 变,这个 View 都是全新的”
直接禁用 diff。
5️⃣ List / ForEach 中 Row body 很重
即使 diff 正确:
- 每个 Row 的 body 都会被重新计算
- 数量一大就卡
三、什么时候你需要“主动帮 SwiftUI 减少 diff 成本”?
✅ 情况一:高频变化状态
- 搜索输入
- 拖动
- 动画
- 手势
👉 把 State 下沉到 最小 View
✅ 情况二:大列表 / 树状 UI
List/LazyVStack- 多级 ForEach
👉 确保:
- 稳定 id
- Row 内部状态独立
- 重计算移出 body
✅ 情况三:昂贵派生数据
let filtered = users.filter(...)
👉 放到:
- ViewModel
@State- memoized 计算
✅ 情况四:你知道某个子树“绝不会变”
使用 EquatableView
EquatableView {
StaticBanner()
}
或:
struct Banner: View, Equatable {
static func == (...) -> Bool { true }
}
👉 SwiftUI 可以直接跳过 diff。
✅ 情况五:动画或视觉层级复杂
- 渐变
- 模糊
- 遮罩
- 大量 overlay
👉 把动画状态局部化,避免整个 View 树参与动画 diff。
四、工程级“减 diff 成本”清单
你能做的事(从高收益到低收益)
- 状态下沉(最重要)
- 稳定 identity(ForEach / id)
- 避免 body 中重计算
- 拆 View(缩小 diff 子树)
- EquatableView / equatable()
- 避免 .id(UUID())
- Row 级 ViewModel / State
五、一个真实“性能翻倍”的改动
改前
struct ListView: View {
@State var searchText = ""
@State var users: [User]
var body: some View {
List(users.filter { $0.name.contains(searchText) }) {
UserRow(user: $0)
}
}
}
改后
struct UserList: View {
let users: [User]
let searchText: String
var filtered: [User] { ... }
}
👉 diff 参与节点数直接减少一个数量级。
六、最后一句话(工程直觉)
SwiftUI 的 diff 不是“慢”,而是“你告诉它要比较的东西太多了”。
真正的优化不是 hack diff,而是缩小失效子树。