如何通过MVVM结合@ObservedObject触发SwiftUI自动更新
SwiftUI与MVVM结合完全可行,你的代码问题出在ViewModel对Model层变化的监听逻辑上,以下是具体分析和修复方案:
问题根源
你的ViewModel中使用@Published var number: NumberStorage,但NumberStorage是引用类型(ObservableObject类),@Published只会在number的引用发生改变时(比如重新赋值一个新的NumberStorage实例)才会发送更新通知。而你调用increaseNumber()时,只是修改了NumberStorage内部numberObject的属性,并没有改变number的引用,因此ViewModel不会通知View更新。
而直接在View中使用@ObservedObject var numberStorage = NumberStorage()能正常工作,是因为NumberStorage内部的@Published var numberObject会在结构体实例更新时发送通知,View能直接接收到。
修复方案
方案1:ViewModel转发Model层的更新通知
让ViewModel订阅NumberStorage的变化,当Model内部属性更新时,手动触发ViewModel的objectWillChange通知:
class ViewModel: ObservableObject { private let numberStorage = NumberStorage() private var cancellables = Set<AnyCancellable>() init() { // 订阅NumberStorage的numberObject变化,触发ViewModel的更新 numberStorage.$numberObject .sink { [weak self] _ in self?.objectWillChange.send() } .store(in: &cancellables) } func increaseNumber() { numberStorage.increaseNumber() } func getNumberObject() -> NumberObject { numberStorage.numberObject } }
View中使用ViewModel的代码恢复注释部分即可:
struct ContentView: View { @ObservedObject var viewModel = ViewModel() var body: some View { VStack { Text("\(viewModel.getNumberObject().number)") .padding() Button("IncreaseNumber") { viewModel.increaseNumber() } } } }
方案2:简化ViewModel,直接暴露可观察的数值
更符合MVVM的实践是让ViewModel只暴露View需要的状态,而不是传递整个Model对象,这样能降低耦合:
class ViewModel: ObservableObject { private let numberStorage = NumberStorage() // 直接暴露当前数值,View无需关心Model结构 @Published private(set) var currentNumber: Int = 0 init() { // 同步NumberStorage的数值变化到currentNumber numberStorage.$numberObject .map { $0.number } .assign(to: &$currentNumber) } func increaseNumber() { numberStorage.increaseNumber() } }
View中直接使用currentNumber即可:
struct ContentView: View { @ObservedObject var viewModel = ViewModel() var body: some View { VStack { Text("\(viewModel.currentNumber)") .padding() Button("IncreaseNumber") { viewModel.increaseNumber() } } } }
方案3:调整Model层结构(可选)
如果你的业务逻辑允许,可以简化Model层,直接让ViewModel持有可观察的数值,去掉NumberStorage层级:
// ViewModel class ViewModel: ObservableObject { @Published private(set) var number = 0 func increaseNumber() { number += 1 } } // View struct ContentView: View { @ObservedObject var viewModel = ViewModel() var body: some View { VStack { Text("\(viewModel.number)") .padding() Button("IncreaseNumber") { viewModel.increaseNumber() } } } }
这种方式更简洁,适合简单的状态管理场景。
内容的提问来源于stack exchange,提问作者Jonas Bäumer

