SwiftUI的diff系统(Diffing System)
SwiftUI的diff系统(Diffing System)

SwiftUI的diff系统(Diffing System)

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),只重建真正发生变化的部分,从而实现性能优化。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注