如何将现有"did change"数据模型与SwiftUI的ObservableObject桥接?
现有模型定义
我有一个如下所示的现有模型:
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

