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

如何在ObservableObject上触发onChange?为何当前代码未生效?

问题:ObservableObject 属性修改时未触发 onChange 回调

假设我们有一个实现了Equatable协议的ObservableObject视图模型,包含多个@Published属性:

class ViewModel: ObservableObject, Equatable {
    static func == (lhs: ViewModel, rhs: ViewModel) -> Bool {
        lhs.value == rhs.value &&
        lhs.anotherProperty1 == rhs.anotherProperty1 &&
        lhs.anotherProperty2 == rhs.anotherProperty2 &&
        lhs.anotherProperty3 == rhs.anotherProperty3
    }
    
    @Published var value = 0
    @Published var anotherProperty1 = "foo"
    @Published var anotherProperty2 = "bar"
    @Published var anotherProperty3 = "baz"
}

我们期望ViewModel的任意属性修改时都能触发onChange回调,但在如下DemoView中,点击按钮修改value属性时,绑定viewModel的onChange并未触发:

struct DemoView: View {
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        Button {
            viewModel.value += 1
        } label: {
            Text("Increment value \(viewModel.value.formatted())")
        }
        .onChange(of: viewModel) { _ in debugPrint("viewModel did change") } // 未触发回调
    }
}

原因分析

  1. onChange(of: viewModel) 监听的是视图模型实例的引用变化,而非实例内部属性的变化。修改viewModel.value时,viewModel本身的内存地址(引用)没有改变,SwiftUI就认为它没有“变化”,不会触发回调。
  2. 虽然你实现了Equatable协议定义了内容相等的规则,但SwiftUI对引用类型的onChange判断,默认是比较引用地址,不会调用==方法来判断内容是否变化。

解决方法

方法一:合并所有属性的变化信号

在ViewModel中创建一个合并所有@Published属性变化的Publisher,然后在视图中用onReceive监听:

修改ViewModel:

class ViewModel: ObservableObject, Equatable {
    static func == (lhs: ViewModel, rhs: ViewModel) -> Bool {
        lhs.value == rhs.value &&
        lhs.anotherProperty1 == rhs.anotherProperty1 &&
        lhs.anotherProperty2 == rhs.anotherProperty2 &&
        lhs.anotherProperty3 == rhs.anotherProperty3
    }
    
    @Published var value = 0
    @Published var anotherProperty1 = "foo"
    @Published var anotherProperty2 = "bar"
    @Published var anotherProperty3 = "baz"
    
    // 合并所有属性的变化信号,任意属性修改都会发送通知
    var anyPropertyChanged: AnyPublisher<Void, Never> {
        Publishers.Merge4(
            $value.map { _ in },
            $anotherProperty1.map { _ in },
            $anotherProperty2.map { _ in },
            $anotherProperty3.map { _ in }
        )
        .eraseToAnyPublisher()
    }
}

修改DemoView:

struct DemoView: View {
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        Button {
            viewModel.value += 1
        } label: {
            Text("Increment value \(viewModel.value.formatted())")
        }
        .onReceive(viewModel.anyPropertyChanged) {
            debugPrint("viewModel did change")
        }
    }
}

方法二:监听所有属性的组合值

直接在onChange中监听ViewModel所有属性的组合元组,只要任意属性变化,组合值就会更新,从而触发回调:

struct DemoView: View {
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        Button {
            viewModel.value += 1
        } label: {
            Text("Increment value \(viewModel.value.formatted())")
        }
        .onChange(of: (viewModel.value, viewModel.anotherProperty1, viewModel.anotherProperty2, viewModel.anotherProperty3)) { _ in
            debugPrint("viewModel did change")
        }
    }
}

这种方法无需修改ViewModel,但属性较多时代码会比较繁琐。

方法三:手动触发对象变化通知

利用ObservableObject的objectWillChange发送器,给每个@Published属性添加didSet钩子,手动发送对象变化通知:

修改ViewModel:

class ViewModel: ObservableObject, Equatable {
    static func == (lhs: ViewModel, rhs: ViewModel) -> Bool {
        lhs.value == rhs.value &&
        lhs.anotherProperty1 == rhs.anotherProperty1 &&
        lhs.anotherProperty2 == rhs.anotherProperty2 &&
        lhs.anotherProperty3 == rhs.anotherProperty3
    }
    
    @Published var value = 0 {
        didSet { objectWillChange.send() }
    }
    @Published var anotherProperty1 = "foo" {
        didSet { objectWillChange.send() }
    }
    @Published var anotherProperty2 = "bar" {
        didSet { objectWillChange.send() }
    }
    @Published var anotherProperty3 = "baz" {
        didSet { objectWillChange.send() }
    }
}

这样修改任何属性时,都会额外发送对象变化通知,SwiftUI会认为整个ViewModel发生了变化,从而触发onChange(of: viewModel)的回调。


内容的提问来源于stack exchange,提问作者parapote

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 17:03:19