SwiftUI 的 diff 系统(Diffing System) 是其核心机制之一,用来判断哪些视图需要更新、哪些不需要更新。这个系统的目标是:
最小化 UI 更新的范围,只重绘那些确实改变了的数据对应的视图部分。
背后原理
SwiftUI 的视图本质上是一棵函数式树。每次 @State / @Binding / @ObservedObject 等数据发生变化,SwiftUI 会:
1、重新调用 body 构建视图树(这是轻量的,不是真正渲染 UI)。
2、将新树与旧树做比较(diff),找出变化的部分。
3、只对比出的变化部分更新渲染(commit)。
举个例子
struct CounterView: View {
@State var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Add") {
count += 1
}
}
}
}
每次点击 Add,body 会重新执行。
SwiftUI 会对比新旧视图树。
发现只有 Text(“Count: \(count)”) 改变了,只更新这一个 Text 视图,而 Button 不变,跳过重绘。

什么时候 diff 系统会「误判」或「卡顿」?
SwiftUI 依赖于“值类型语义”进行对比。如果状态是一个结构体(如数组、字典),它无法只对比某个 key 改变了,而是整个值都变了。
例如:
@State var dict = ["USD": "10", "JPY": "20"]
dict["USD"] = "15" // SwiftUI 会认为整个 dict 变了,相关视图都得重新渲染
导致:
不必要的重建视图(尤其是 ForEach 里绑定到这个状态的 TextField 时)
如果视图很重或数据很多,就会出现卡顿、闪烁、焦点跳转等问题
总结
SwiftUI 的 diff 系统(diffing system)是 SwiftUI 实现高性能 UI 更新的核心机制之一。它的基本原理是比较新旧视图的差异(diff)并只对真正变化的部分进行更新,而不是每次都重建整个视图树。
SwiftUI 内部会进行“diff 运算”,比较新旧视图的结构是否有变化:
1)如果视图类型、标识符(id)、位置相同,SwiftUI 会重用旧的 View 实例和底层 UIKit/AppKit/Metal 组件;
2)如果有差异,就销毁旧的,创建新的。
在ForEach、List等动态列表中,SwiftUI会借助Identifiable或显示提供的.id()实现更精准的diff:
ForEach(items) { item in
Text(item.name)
}
SwiftUI 通过 item 的 id 跟踪每一项是否变更、移动或删除,避免重新构建所有行。
如果视图结构变动但 id 没变,SwiftUI 认为它还是“同一个视图”:
@State private var selectedIndex = 0
Button(action:{
selectedIndex += 1
},label: {
Text("selectedIndex:\(selectedIndex)")
.id(0)
})
更换文本内容但保持 id 不变,SwiftUI 仍认为这是一处轻微变更,不必销毁重建,所以SwiftUI不会销毁旧视图,而是更新它的内部状态(也就是字符串内容。)
但是,如果把id绑定到变动的属性上:
Text("selectedIndex:\(selectedIndex)")
.id(selectedIndex)
这时,每次 selectedIndex 变动,.id() 也变了,SwiftUI 会认为这个 Text 是全新的视图,于是销毁旧的,创建新的,这会触发比如 transition、动画等。
总之,SwiftUI 会根据新旧视图描述结构进行最小差异更新(diffing),只重建真正发生变化的部分,从而实现性能优化。