@Observable 可以作用于 class 和 struct,不能作用于 Actor。Actor 类型的底层实现与 @Observable 的状态观察逻辑存在冲突,比如 Actor 的状态访问需要异步传递,而 @Observable 依赖同步的属性访问来触发观察。Swift 编译器明确禁止将 @Observable 应用在 actor 声明的类型上。比如下方代码:
@Observable
actor ChuanActor {
}
编译期间就会报错:'@Observable' cannot be applied to actor type 'ChuanActor'。
解决办法一般是将 ChuanActor 中需要被观察的数据抽离到使用 @Observable 标记的结构体中。
在实际开发中,在需要处理 UI 更新等场景时,@MainActor + @Observable 是非常常见的组合。@MainActor 属性包装器确保了被修饰的类型的属性访问、方法执行必须在主线程,而 @Observable 的状态观察本身也需要在主线程执行。两者的结合强化了线程安全:@Observable 负责状态观察,@MainActor 保证状态的读写和观察通知都在主线程执行,避免了多线程下的 UI 刷新问题。
struct UserForm: View {
@Environment(UserProfile.self) private var profile
var body: some View {
Form {
TextField("Name", text: $profile.name)
TextField("Age", text: $profile.age)
}
}
}
@Environment 获取的对象默认不是可绑定的,上述代码 $profile.name 这里就会报错。除了可以在子视图中将 @Observable 属性声明为 @Bindable:
struct UserForm: View {
@Environment(UserProfile.self) private var profile
var body: some View {
UserFormSub(profile: $profile)
}
}
struct UserFormSub: View {
@Bindable var profile: UserProfile
var body: some View {
Form {
TextField("Name", text: $user.name)
TextField("Age", text: $user.eag)
}
}
}
还可以直接在当前视图内定义 @Bindable 变量:
struct UserForm: View {
@Environment(UserProfile.self) private var profile
var body: some View {
// 使用 @Bindable 修饰
@Bindable var bindableprofile = profile
Form {
TextField("Name", text: $bindableprofile.name)
TextField("Age", text: $bindableprofile.age)
}
}
}
@Observable
final class Preference {
@AppStorage("chuan_sort_by") var sort_by: SortBy = .date_created
@AppStorage("chuan_sort_order") var sort_order: SortOrder = .reverse
}
上面这段代码会报错:Invalid redeclaration of synthesized property。
原因:@Observable 宏会自动为所有存储属性生成观察功能,与 @AppStorage 的属性包装器机制存在冲突。
注意:此时不能使用 @ObservationIgnored 修饰 @AppStorage 属性来规避这个问题,会消除报错但会导致 @AppStorage 属性失去可观察性。
在 SwiftUI 中的 UserDefaults 与 Observation:如何实现精准响应 一文中,作者给出了两个解决方案:
1、不使用 @AppStorage,而是手动实现 get 和 set 方法以操作 UserDefaults
@Observable
class Settings {
@ObservationIgnored
var name: String {
get {
access(keyPath: \.name)
return UserDefaults.standard.string(forKey: "name") ?? _nameDefault
}
set {
withMutation(keyPath: \.name) {
UserDefaults.standard.set(newValue, forKey: "name")
}
}
}
}
2、使用作者开源的 ObservableDefaults
Swift Observation 框架同时还提供了一个 withObservationTracking 函数,开发者可以用它来跟踪 Observable 对象的属性是否发生变化。
public func withObservationTracking<T>(_ apply: () -> T, onChange: @autoclosure () -> @Sendable () -> Void) -> T
withObservationTracking 接受两个闭包参数:apply 和 onChange。使用时将需要监听的属性放在 apply 闭包中,如果这些属性发生改变其 onChange 闭包将会被调用。
import Observation
@Observable final class Counter {
var count = 0
}
let counter = Counter()
func startObservation() {
withObservationTracking {
// 在 apply 闭包中访问需要观察的属性
print("当前计数: \(counter.count)")
} onChange: {
// 属性变化时,onChange 会被调用
print("检测到计数变化!")
// 由于 onChange 默认只触发一次,我们递归调用以继续观察
// 有时为了避免 onChange 在实际变化应用前立即运行,可以将其包装在 Task 中
Task {
startObservation()
}
}
}
@Observable 可以作用于 class 和 struct,不能作用于 Actor。Actor 类型的底层实现与 @Observable 的状态观察逻辑存在冲突,比如 Actor 的状态访问需要异步传递,而 @Observable 依赖同步的属性访问来触发观察。Swift 编译器明确禁止将 @Observable 应用在 actor 声明的类型上。比如下方代码:
@Observable
actor ChuanActor {
}
编译期间就会报错:'@Observable' cannot be applied to actor type 'ChuanActor'。
解决办法一般是将 ChuanActor 中需要被观察的数据抽离到使用 @Observable 标记的结构体中。
在实际开发中,在需要处理 UI 更新等场景时,@MainActor + @Observable 是非常常见的组合。@MainActor 属性包装器确保了被修饰的类型的属性访问、方法执行必须在主线程,而 @Observable 的状态观察本身也需要在主线程执行。两者的结合强化了线程安全:@Observable 负责状态观察,@MainActor 保证状态的读写和观察通知都在主线程执行,避免了多线程下的 UI 刷新问题。
struct UserForm: View {
@Environment(UserProfile.self) private var profile
var body: some View {
Form {
TextField("Name", text: $profile.name)
TextField("Age", text: $profile.age)
}
}
}
@Environment 获取的对象默认不是可绑定的,上述代码 $profile.name 这里就会报错。除了可以在子视图中将 @Observable 属性声明为 @Bindable:
struct UserForm: View {
@Environment(UserProfile.self) private var profile
var body: some View {
UserFormSub(profile: $profile)
}
}
struct UserFormSub: View {
@Bindable var profile: UserProfile
var body: some View {
Form {
TextField("Name", text: $user.name)
TextField("Age", text: $user.eag)
}
}
}
还可以直接在当前视图内定义 @Bindable 变量:
struct UserForm: View {
@Environment(UserProfile.self) private var profile
var body: some View {
// 使用 @Bindable 修饰
@Bindable var bindableprofile = profile
Form {
TextField("Name", text: $bindableprofile.name)
TextField("Age", text: $bindableprofile.age)
}
}
}
@Observable
final class Preference {
@AppStorage("chuan_sort_by") var sort_by: SortBy = .date_created
@AppStorage("chuan_sort_order") var sort_order: SortOrder = .reverse
}
上面这段代码会报错:Invalid redeclaration of synthesized property。
原因:@Observable 宏会自动为所有存储属性生成观察功能,与 @AppStorage 的属性包装器机制存在冲突。
注意:此时不能使用 @ObservationIgnored 修饰 @AppStorage 属性来规避这个问题,会消除报错但会导致 @AppStorage 属性失去可观察性。
在 SwiftUI 中的 UserDefaults 与 Observation:如何实现精准响应 一文中,作者给出了两个解决方案:
1、不使用 @AppStorage,而是手动实现 get 和 set 方法以操作 UserDefaults
@Observable
class Settings {
@ObservationIgnored
var name: String {
get {
access(keyPath: \.name)
return UserDefaults.standard.string(forKey: "name") ?? _nameDefault
}
set {
withMutation(keyPath: \.name) {
UserDefaults.standard.set(newValue, forKey: "name")
}
}
}
}
2、使用作者开源的 ObservableDefaults
Swift Observation 框架同时还提供了一个 withObservationTracking 函数,开发者可以用它来跟踪 Observable 对象的属性是否发生变化。
public func withObservationTracking<T>(_ apply: () -> T, onChange: @autoclosure () -> @Sendable () -> Void) -> T
withObservationTracking 接受两个闭包参数:apply 和 onChange。使用时将需要监听的属性放在 apply 闭包中,如果这些属性发生改变其 onChange 闭包将会被调用。
import Observation
@Observable final class Counter {
var count = 0
}
let counter = Counter()
func startObservation() {
withObservationTracking {
// 在 apply 闭包中访问需要观察的属性
print("当前计数: \(counter.count)")
} onChange: {
// 属性变化时,onChange 会被调用
print("检测到计数变化!")
// 由于 onChange 默认只触发一次,我们递归调用以继续观察
// 有时为了避免 onChange 在实际变化应用前立即运行,可以将其包装在 Task 中
Task {
startObservation()
}
}
}