引言:为什么需要 @Observable
?
在 SwiftUI 的早期版本中,我们使用 @StateObject
和 @ObservedObject
来管理状态,但这些方式依赖于 ObservableObject
协议,需要手动标记 @Published
属性,并且存在性能瓶颈和样板代码。
Apple 在 iOS 17 引入了全新的 Observation 框架,通过 @Observable
宏(macro)简化了状态管理,无需继承 ObservableObject
,也无需手动标记 @Published
,性能更优,代码更清晰。
核心概念解析
@Observable
是什么?
@Observable
是一个宏(macro),用于修饰类(class),使其属性具备可观察性。
被 @Observable
修饰的类,其所有存储属性(stored properties)默认都是可观察的,无需 @Published
。
swift
import Observation
@Observable
class Counter {
var value: Int = 0
var name: String = "Counter"
}
✅ 注意:@Observable
只能用于 class,不能用于 struct。
@Bindable
是什么?
@Bindable
是一个属性包装器(property wrapper),用于在视图中创建对 @Observable
类属性的绑定(Binding
)。
它通常用于子视图中,将父视图传入的可观察对象中的某个属性绑定到 UI 控件(如 TextField
、Toggle
等)。
swift
struct CounterView: View {
@Bindable var counter: Counter
var body: some View {
VStack {
Text("Current value: \(counter.value)")
Stepper("Increment", value: $counter.value) // ✅ 使用 $ 创建绑定
TextField("Name", text: $counter.name)
}
}
}
✅ 关键点:@Bindable
让你可以在子视图中对 @Observable
对象的属性进行 双向绑定。
完整代码示例:从模型到视图
定义数据模型(Model)
swift
import Foundation
import Observation
@Observable
class Counter {
var value: Int = 0
var name: String = "My Counter"
}
主视图(ContentView)
swift
import SwiftUI
struct ContentView: View {
// ✅ 使用 State 持有可观察对象
@State private var counter = Counter()
var body: some View {
NavigationStack {
VStack(spacing: 20) {
Text("Counter Name: \(counter.name)")
.font(.headline)
Text("Value: \(counter.value)")
.font(.largeTitle)
// ✅ 传入子视图,使用 Bindable 进行绑定
CounterView(counter: counter)
Button("Reset") {
counter.value = 0
counter.name = "Reset Counter"
}
}
.padding()
.navigationTitle("Observation Demo")
}
}
}
子视图(CounterView)使用 @Bindable
swift
import SwiftUI
struct CounterView: View {
@Bindable var counter: Counter // ✅ 使用 Bindable 包装,支持绑定
var body: some View {
VStack(spacing: 15) {
Stepper("Increment", value: \(counter.value)) // ✅ 绑定 value
.padding()
TextField("Enter counter name", text: \(counter.name)) // ✅ 绑定 name
.textFieldStyle(.roundedBorder)
.padding(.horizontal)
}
}
}
预览(Preview)
swift
#Preview {
ContentView()
}
运行效果与行为说明
操作 | 结果 |
---|---|
点击 Stepper | counter.value 改变,主视图自动更新 |
修改 TextField | counter.name 改变,主视图标题同步更新 |
点击 Reset 按钮 | 重置 value 和 name ,视图刷新 |
✅ 所有更新都是响应式的,无需手动调用 objectWillChange.send()
常见误区与注意事项
错误用法 | 正确做法 |
---|---|
在 struct 上使用 @Observable |
❌ 只能用于 class |
用 @ObservedObject 接收 @Observable 对象 |
❌ 应使用 @State 或 @Bindable |
忘记在子视图中用 @Bindable |
❌ 无法创建绑定,编译器报错:_counter.value is not a Binding |
使用 @Published |
❌ 不需要,所有属性默认可观察 |
总结与见解
✅ 优点
- 简化代码:无需继承
ObservableObject
,无需@Published
- 性能更好:Observation 框架使用更高效的依赖追踪机制
- 类型安全:宏展开在编译期完成,减少运行时错误
- 支持绑定:
@Bindable
让子视图轻松实现双向绑定
⚠️ 限制与思考
- 仅支持 iOS 17+ / macOS 14+ / Xcode 15+
- 只能用于 class,不支持 struct(但 struct 本身值语义,可用
@State
) - 不支持 嵌套观察:如果属性是另一个自定义类型,需单独标记为
@Observable
扩展应用场景
- 表单编辑(Form Editing)
swift
@Observable
class UserProfile {
var name: String = ""
var email: String = ""
var isSubscribed: Bool = false
}
// 在视图中使用 Form + Section + TextField + Toggle
- 多视图共享状态(Shared State)
swift
@main
struct MyApp: App {
@State private var user = UserProfile()
var body: some Scene {
WindowGroup {
ContentView()
.environment(user) // ✅ 使用 environment 注入
}
}
}
// 子视图中使用:
@Environment(UserProfile.self) private var user
- 与
@Query
结合(SwiftData)
swift
@Query var items: [Item]
@State private var selectedItem: Item?
未来趋势:@Observable
将成为 SwiftUI 状态管理的主流方式,逐步替代 ObservableObject
结语:拥抱 Observation,告别样板代码
SwiftUI 的 @Observable
和 @Bindable
标志着 Apple 在状态管理上的一次重大飞跃。它不仅让代码更简洁、响应更快,还降低了学习门槛。对于新项目,强烈推荐使用 Observation 框架;对于旧项目,也可逐步迁移,享受现代化开发的便利。
🚀 未来已来,是时候告别 @Published
和 ObservableObject
了!