You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用单个监听方法捕获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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:30:28