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

如何将现有"did change"数据模型与SwiftUI的ObservableObject桥接?

问题:将“did change”风格的模型桥接到SwiftUI的首选方式

现有模型定义

我有一个如下所示的现有模型:

struct Data { ... }

struct Parameters { ... }

final class Model {
    let dataDidChange: AnyPublisher<Void, Never> { ... }

    private(set) var data: Data
    var parameters: Parameters {
        didSet { ... }
    }
    
    ...
}

在该模型中,每当data或parameters已发生变化时,dataDidChange发布者就会触发。此外,parameters可能会被其他模型修改,进而触发更新。

我的实现方案

我正尝试创建一个SwiftUI视图,以镜像模型的状态,既能在屏幕上展示data,又能修改parameters。为此,我创建了供视图绑定的视图模型:

final class ViewModel: ObservableObject {
    private let model: Model

    var parameters: Parameters {
        get { self.model.parameters }
        set { self.model.parameters = newValue }
    }

    var data: Data { self.model.data }

    init(model: Model) {
        self.model = model

        model.dataDidChange
            .sink { self.objectWillChange.send() } // <-- 此处
            .store(in: ...)
    }
}

对应的视图代码:

struct MyView: View {
    @ObservedObject var vm: ViewModel

    var body: some View {
        VStack {
            ParametersView(parameters: self.$vm.parameters)
            DataView(self.vm.data)
        }
    }
}

我的疑问

我对这种方法不满意的点在于,objectWillChange发布者实际上是在数据已变化后才触发的。虽然它能正常工作,但我想知道这种方式是否存在性能或正确性问题?ObservableObject的文档明确指出:

一种带有发布者的对象类型,该发布者会在对象发生变化前发送通知。

将"did change"风格的模型桥接到SwiftUI的首选方式是什么?


解决方案

1. 核心问题解析

SwiftUI设计ObservableObject时,要求objectWillChange在状态变更前发送信号,目的是让视图提前准备更新,避免读取到半更新的中间状态。实际开发中,变更后发送信号大多能正常运行,但严格来说不符合设计规范,在并发状态变更等极端场景下可能引发不一致问题。

2. 首选实现方式

针对你的“did change”风格模型,有两种可靠的桥接方案:

方式一:优化ViewModel的信号发送时机

对于视图主动修改的parameters,我们可以在ViewModel的setter中提前发送objectWillChange,符合SwiftUI的设计预期;对于模型内部或外部触发的变更,继续订阅dataDidChange并发送信号(此时状态已稳定,视图读取的是最新值,不会有逻辑错误)。

优化后的ViewModel代码:

final class ViewModel: ObservableObject {
    private let model: Model
    private var cancellables = Set<AnyCancellable>()

    var parameters: Parameters {
        get { model.parameters }
        set {
            objectWillChange.send() // 变更前发送信号
            model.parameters = newValue
        }
    }

    var data: Data { model.data }

    init(model: Model) {
        self.model = model
        // 处理模型内部/外部触发的变更
        model.dataDidChange
            .sink { [weak self] in
                self?.objectWillChange.send()
            }
            .store(in: &cancellables)
    }
}

方式二:直接改造原模型(若允许)

如果原模型可以修改,直接让它遵循ObservableObject,在状态变更前发送objectWillChange,这样就不需要额外的ViewModel:

final class Model: ObservableObject {
    private(set) var data: Data {
        willSet { objectWillChange.send() }
    }
    var parameters: Parameters {
        willSet { objectWillChange.send() }
        didSet { ... } // 保留原有业务逻辑
    }
    
    // 移除原有的dataDidChange,直接使用ObservableObject自带的objectWillChange
    ...
}

视图可直接绑定模型:

struct MyView: View {
    @ObservedObject var model: Model

    var body: some View {
        VStack {
            ParametersView(parameters: self.$model.parameters)
            DataView(self.model.data)
        }
    }
}

3. 性能与正确性说明

  • 正确性:手动在parameters的setter中提前发送信号,完全符合SwiftUI的设计逻辑;对于模型主动触发的变更,虽然是“did”后发送,但状态已稳定,视图读取的是最新值,不会出现逻辑错误,仅不符合文档的严格描述。
  • 性能:这种桥接方式不会带来额外性能损耗,objectWillChange的发送是轻量操作,SwiftUI会批量处理视图更新,不会因发送时机的微小差异影响性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 22:27:11