Observation 解决什么
SwiftUI 早期用 ObservableObject,整个对象一变,所有观察它的视图都重绘。对象字段一多,刷新就粗放。Observation 框架把订阅粒度细化到属性:视图只读哪个属性,就只在那个属性变时刷新。
目标不是让状态可变,而是让刷新精确到视图真正读到的那一项。
这篇顺着 @Observable 把 SwiftUI 的数据流讲清。
从 ObservableObject 到 @Observable
老写法要手动标 @Published。新写法只要给类加 @Observable,所有存储属性自动变成可观察,不用逐个标注。
@Observable
class Cart {
var count = 0
var total = 0
}
一行注解,整类获得观察能力。
视图如何精确订阅
在视图里把可观察对象直接当属性用,不用 @StateObject、@ObservedObject 那一套包裹。SwiftUI 会自动追踪视图 body 里实际读到了哪些属性,只有那些属性变了才重算。
数据流速查
- @Observable类可观察。
- @State视图私有状态。
- @Environment跨层级注入。
- @Bindable双向绑定对象。
环境注入与共享
全局共享的对象放进环境:父视图注入,后代任意层级取用,省去一层层传。适合主题、登录态这类全局状态。代价是数据流变隐式,看单组件看不出值从哪来。
单向数据流:状态在哪放
把状态放在离它最近、但又需要它的那个视图或外层模型里。视图渲染状态,用户操作改状态,不反向在子视图里偷改父状态。单向流动,才好调试。
避坑:过度刷新与漏刷新
Observation 会帮你减少过度刷新,但前提是视图真的只读它需要的属性。把派生计算写成计算属性,视图读结果就好,不要在视图里硬算大列表。
- 视图只依赖它渲染所需的状态。
- 派生值用计算属性。
- 全局状态才走环境注入。
实战:一个计数器的状态模型
把计数放进可观察类,视图读它,按钮改它。
@Observable
class Counter { var n = 0 }
struct CounterView: View {
@State var c = Counter()
var body: some View {
Button("点一下 \(c.n)") { c.n += 1 }
}
}
点按钮改 n,只依赖 n 的那一行自动刷新。数据流清晰、刷新精确,这就是 Observation 的价值。