探索 SwiftUI 中的 Observation
Discover Observation in SwiftUI
2023年6月5日
一句话判断
@Observable 宏彻底改变了 SwiftUI 的数据流——不需要 @Published、不需要 ObservableObject、不需要 @ObservedObject,Swift 编译器自动追踪属性访问,SwiftUI 只在用到的属性变化时才更新视图。这是今年对 SwiftUI 开发体验影响最大的改动。
这场 Session 讲了什么
Session 介绍了 Swift 5.9 中的 Observation 框架及其与 SwiftUI 的深度集成。
@Observable 宏:
- 在类声明上加
@Observable,编译器自动生成属性观察代码。 - SwiftUI 在执行视图 body 时追踪所有访问的 Observable 属性。
- 只有被追踪的属性变化时才触发视图更新——精确到实例级别。
属性包装器简化为三种:
- @State:视图拥有的状态。当模型需要由视图生命周期管理时使用。
- @Environment:全局环境值。当模型需要在多个视图间共享时使用。
- @Bindable:创建绑定的轻量包装。需要 Binding 时使用(如 TextField)。
- 不需要包装器:大多数情况下直接使用 Observable 模型实例即可。
高级用法:
- Observable 模型可以放在数组中——SwiftUI 追踪数组中每个实例的属性访问。
- Observable 模型可以包含其他 Observable 模型。
- 计算属性如果由存储属性组成,自动支持观察。如果依赖外部数据源,需要手动调用 Observation 框架的 access/mutate 方法。
从 ObservableObject 迁移:
ObservableObject+@Published→@Observable@ObservedObject→ 直接使用属性@EnvironmentObject→@Environment@StateObject→@State
值得深挖的点
**“只有被访问的属性变化才更新视图”**是性能提升的核心。之前 ObservableObject 的 @Published 属性变化会通知所有订阅者,即使某个视图没有用到变化的属性。Observation 让 SwiftUI 知道每个视图具体访问了哪些属性,只在这些属性变化时才重新计算 body。这对大型视图树来说性能提升非常显著。
不需要属性包装器的场景是最让人意外的设计。在大多数情况下,你只需要一个普通的属性引用 Observable 实例,不需要任何包装器。SwiftUI 的属性访问追踪机制会自动工作。只有在需要 State 生命周期管理、Environment 全局共享、或 Binding 绑定时才需要包装器。
数组中的 Observable 实例也是按实例追踪的。如果你有一个 [Donut] 数组,修改第三个 donut 的名字只会更新访问了那个实例的那个属性的视图。这种精确度在列表和集合视图中特别有价值。
代码片段
使用 @Observable 宏:
import Observation
@Observable // 一个宏搞定——不需要 @Published
class FoodTruckModel {
var donuts: [Donut] = []
var orders: [Order] = []
var userName: String = ""
// 计算属性自动支持观察(如果由存储属性组成)
var orderCount: Int {
orders.count // orders 变化时,访问 orderCount 的视图会更新
}
}
在 SwiftUI 中使用(不需要属性包装器):
struct DonutMenuView: View {
// 直接使用——不需要 @ObservedObject 或 @EnvironmentObject
var model: FoodTruckModel
var body: some View {
List(model.donuts) { donut in
Text(donut.name)
}
// SwiftUI 自动追踪:这个视图访问了 model.donuts
// 只有 donuts 变化时才会重新计算 body
// orders 变化不会触发更新
}
}
三种属性包装器的使用场景:
struct DonutEditorView: View {
// 场景1:视图拥有的状态
@State private var donutToAdd = Donut()
// 场景2:全局环境
@Environment var account: AccountModel
// 场景3:需要 Binding
@Bindable var donut: Donut
var body: some View {
Form {
// 使用 @Bindable 创建绑定
TextField("名称", text: $donut.name)
// 使用 @State 管理生命周期
TextField("新名称", text: $donutToAdd.name)
}
}
}
最佳实践
- 新项目直接用
@Observable,不要再用ObservableObject。 - 先不用任何属性包装器,只在需要 State/Environment/Binding 时才添加。
@State用于视图拥有的状态,@Environment用于全局共享,@Bindable只在需要 Binding 时用。- 利用属性级追踪减少不必要的视图更新——大列表的性能会明显改善。
- 迁移现有代码时,
@StateObject替换为@State,@ObservedObject替换为普通属性,@EnvironmentObject替换为@Environment。 - 计算属性如果依赖外部数据源(非存储属性),需要手动调用 Observation 的
withObservationTracking。
还有什么值得关注
- “Write Swift macros”和”Expand on Swift macros” Session 介绍了宏的底层机制。
- Observation 框架是 Swift 标准库的一部分,不依赖 SwiftUI——可以在非 UI 场景中使用。
@Observable只能用于 class(不能用于 struct),因为它依赖引用语义来追踪实例。- 从 ObservableObject 迁移到 @Observable 通常是机械性的替换,风险较低。
- 性能测试表明,Observation 在大型视图树中的更新效率比 ObservableObject 有显著提升。