ObservableObject 协议是 SwiftUI 早期的观察机制,Swift 5.9 引入 @Observable 宏作为 Swift 观察的"现代"解决方案。本文讲解从 ObservableObject 迁移到 @Observable。
@Observable 宏方案适配 iOS 17、iPadOS 17、macOS 14、tvOS 17和watchOS 10 及以上版本,在 App 支持 OS 版本允许的的情况下,应该优先使用 @Observable。
定义:
import Combine
class UserProfile: ObservableObject {
@Published var name: String = "Chuan"
@Published var age: Int = 25
var displayName: String {
"\(name) (\(age))"
}
}
使用:
struct ProfileView: View {
@StateObject private var profile = UserProfile()
// 或 @ObservedObject var profile: UserProfile
var body: some View {
VStack {
Text(profile.name)
Text("\(profile.age)")
}
}
}
使用时需要 @StateObject 或 @ObservedObject 修饰 ObservableObject 对象。
定义:
import Observation
@Observable
class UserProfile {
var name: String = "Chuan"
var age: Int = 25
var displayName: String {
"\(name) (\(age))"
}
}
使用:
struct ProfileView: View {
let profile: UserProfile // 不再需要 @ObservedObject 或 @StateObject 包装器
var body: some View {
VStack {
Text(profile.name) // 自动观察 name
Text("\(profile.age)") // 自动观察 age
}
}
}
很明显可以看到:@Observable 方案大大简化了设计过程,不再需要 @Published、ObservableObject、@ObservedObject 了。
另外从性能上看,@Observable 也更具优势。@Observable 能精确至对象属性的变化来更新 view,而 ObservableObject 则是整个对象变化都会通知。
追踪机制对比:
class Counter: ObservableObject {
@Published var count = 0
@Published var name = "Counter"
}
let counter = Counter()
// 修改任何一个 @Published 属性都会触发整个对象的更新
counter.count += 1 // 触发更新
counter.name = "New Name" // 也触发更新,即使视图只使用 count
而 @Observable 方案:
@Observable class Counter {
var count = 0
var name = "Counter"
}
let counter = Counter()
// SwiftUI 只会更新实际使用特定属性的视图
// 如果视图只使用 count,修改 name 不会触发视图更新
将 ObservableObject 对象添加到 environment 中必须使用 environmentObject:
@main
struct BookApp: App {
@StateObject private var profile = UserProfile()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(profile)
}
}
}
@Observable 方案中不再需要 environmentObject:
@main
struct BookApp: App {
@State private var profile = UserProfile()
var body: some Scene {
WindowGroup {
LibraryView()
.environment(profile)
}
}
}
对应的读取环境变量也从原来的:
struct LibraryView: View {
@EnvironmentObject var profile: UserProfile
var body: some View {
UserView(profile: profile)
}
}
变为:
struct LibraryView: View {
@Environment(UserProfile.self) private var profile
var body: some View {
UserView(profile: profile)
}
}
@Bindable 是与 @Observable 宏配合使用的属性包装器,它允许将 @Observable 类型的属性转换为可绑定引用,以便在绑定的视图中可以更改可观察属性的值。
// 父视图
struct ParentView: View {
@State private profile = UserProfile()
var body: some View {
UserForm(profile: profile) // 这里 profile 自动变为 @Bindable
}
}
// 子视图在参数位置接收 @Bindable
struct UserForm: View {
@Bindable var profile: UserProfile
var body: some View {
Form {
TextField("Name", text: $profile.name) // $profile.name 将同步到 ParentView
TextField("Age", text: $profile.age)
}
}
}
参考:
Migrating from the Observable Object protocol to the Observable macro
ObservableObject 协议是 SwiftUI 早期的观察机制,Swift 5.9 引入 @Observable 宏作为 Swift 观察的"现代"解决方案。本文讲解从 ObservableObject 迁移到 @Observable。
@Observable 宏方案适配 iOS 17、iPadOS 17、macOS 14、tvOS 17和watchOS 10 及以上版本,在 App 支持 OS 版本允许的的情况下,应该优先使用 @Observable。
定义:
import Combine
class UserProfile: ObservableObject {
@Published var name: String = "Chuan"
@Published var age: Int = 25
var displayName: String {
"\(name) (\(age))"
}
}
使用:
struct ProfileView: View {
@StateObject private var profile = UserProfile()
// 或 @ObservedObject var profile: UserProfile
var body: some View {
VStack {
Text(profile.name)
Text("\(profile.age)")
}
}
}
使用时需要 @StateObject 或 @ObservedObject 修饰 ObservableObject 对象。
定义:
import Observation
@Observable
class UserProfile {
var name: String = "Chuan"
var age: Int = 25
var displayName: String {
"\(name) (\(age))"
}
}
使用:
struct ProfileView: View {
let profile: UserProfile // 不再需要 @ObservedObject 或 @StateObject 包装器
var body: some View {
VStack {
Text(profile.name) // 自动观察 name
Text("\(profile.age)") // 自动观察 age
}
}
}
很明显可以看到:@Observable 方案大大简化了设计过程,不再需要 @Published、ObservableObject、@ObservedObject 了。
另外从性能上看,@Observable 也更具优势。@Observable 能精确至对象属性的变化来更新 view,而 ObservableObject 则是整个对象变化都会通知。
追踪机制对比:
class Counter: ObservableObject {
@Published var count = 0
@Published var name = "Counter"
}
let counter = Counter()
// 修改任何一个 @Published 属性都会触发整个对象的更新
counter.count += 1 // 触发更新
counter.name = "New Name" // 也触发更新,即使视图只使用 count
而 @Observable 方案:
@Observable class Counter {
var count = 0
var name = "Counter"
}
let counter = Counter()
// SwiftUI 只会更新实际使用特定属性的视图
// 如果视图只使用 count,修改 name 不会触发视图更新
将 ObservableObject 对象添加到 environment 中必须使用 environmentObject:
@main
struct BookApp: App {
@StateObject private var profile = UserProfile()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(profile)
}
}
}
@Observable 方案中不再需要 environmentObject:
@main
struct BookApp: App {
@State private var profile = UserProfile()
var body: some Scene {
WindowGroup {
LibraryView()
.environment(profile)
}
}
}
对应的读取环境变量也从原来的:
struct LibraryView: View {
@EnvironmentObject var profile: UserProfile
var body: some View {
UserView(profile: profile)
}
}
变为:
struct LibraryView: View {
@Environment(UserProfile.self) private var profile
var body: some View {
UserView(profile: profile)
}
}
@Bindable 是与 @Observable 宏配合使用的属性包装器,它允许将 @Observable 类型的属性转换为可绑定引用,以便在绑定的视图中可以更改可观察属性的值。
// 父视图
struct ParentView: View {
@State private profile = UserProfile()
var body: some View {
UserForm(profile: profile) // 这里 profile 自动变为 @Bindable
}
}
// 子视图在参数位置接收 @Bindable
struct UserForm: View {
@Bindable var profile: UserProfile
var body: some View {
Form {
TextField("Name", text: $profile.name) // $profile.name 将同步到 ParentView
TextField("Age", text: $profile.age)
}
}
}
参考:
Migrating from the Observable Object protocol to the Observable macro