如何用单个监听方法捕获ObservableObject所有@Published属性变更?
解决方案:监听ObservableObject所有@Published属性变更
核心思路
利用ObservableObject自带的objectWillChange发布者,它会在任意@Published属性变更前触发通知。基于这个特性,我们可以扩展出通用的View修饰符,无需逐个指定属性,完全适配现有及未来新增的属性。
实现1:全局监听所有属性变更
给View添加自定义修饰符,实现无参数的全局监听:
import SwiftUI extension View { func onAnyChangedPublishedProperty<T: ObservableObject>(of observable: T, perform action: @escaping () -> Void) -> some View { self.onReceive(observable.objectWillChange) { _ in action() } } }
使用示例
完全匹配你期望的写法:
class MyObservableObject : ObservableObject { @Published var property1 = "" @Published var property2 = 0 // ... 剩余48个属性 } struct MyView : View { @ObservedObject var myObservableObject : MyObservableObject var body : some View { VStack { TextField("属性1", text: $myObservableObject.property1) Stepper("属性2: \(myObservableObject.property2)", value: $myObservableObject.property2) // ... 其他输入控件 } .onAnyChangedPublishedProperty(of: myObservableObject) { print("任意属性发生了变更") } } }
实现2:带属性过滤的监听
如果需要只监听特定模式的属性,可以通过反射捕获变更的属性名称,再添加过滤逻辑:
第一步:自定义可追踪的ObservableObject基类
import Combine import SwiftUI class TrackedObservableObject: ObservableObject { // 自定义发布者,发送变更的属性名称 let propertyChanged = PassthroughSubject<String, Never>() private var cancellables = Set<AnyCancellable>() init() { objectWillChange .sink { [weak self] _ in guard let self = self else { return } // 通过反射遍历所有存储属性 let mirror = Mirror(reflecting: self) for child in mirror.children { guard let propertyName = child.label else { continue } self.propertyChanged.send(propertyName) } } .store(in: &cancellables) } }
第二步:修改你的ObservableObject继承该基类
class MyObservableObject : TrackedObservableObject { @Published var property1 = "" @Published var property2 = 0 @Published var property5 = 100 // ... 其他属性 }
第三步:扩展带过滤的View修饰符
extension View { func onAnyChangedPublishedProperty<T: TrackedObservableObject>(of observable: T, matching propertyPatterns: [String]? = nil, perform action: @escaping (String) -> Void) -> some View { self.onReceive(observable.propertyChanged) { propertyName in // 无过滤条件时直接触发 guard let patterns = propertyPatterns else { action(propertyName) return } // 正则匹配属性名称(可替换为前缀/后缀匹配等逻辑) if patterns.contains(where: { propertyName.range(of: $0, options: .regularExpression) != nil }) { action(propertyName) } } } }
使用示例
struct MyView : View { @ObservedObject var myObservableObject : MyObservableObject var body : some View { VStack { // ... 控件 } .onAnyChangedPublishedProperty(of: myObservableObject, matching: ["property.5"]) { propertyName in print("匹配的属性\(propertyName)发生了变更") } } }
注意事项
objectWillChange是在属性变更前发送通知,若需要变更后触发,可在action内添加DispatchQueue.main.asyncAfter(deadline: .now() + 0.01)延迟执行。- 反射方式的属性名称捕获在性能敏感场景下可优化,比如缓存Mirror结果、只监听带特定前缀的属性。
- 新增/删除属性时无需修改监听代码,自动适配所有
@Published属性。
内容的提问来源于stack exchange,提问作者andrewz
相关产品推荐
相关产品推荐

