SwiftUI中使用Class与Struct时@Published属性不更新视图问题
问题现象
在你的代码中:
- 修改
SampleTimeCounterStruct(值类型)的showerTimeMinutes属性时,视图会正常刷新 - 修改
SampleTimeCounter(普通引用类型)的属性时,视图无任何变化 - 即使
SampleTimeCounterPublihser遵循了ObservableObject且内部属性用@Published修饰,修改它的属性时视图依然不更新
原因分析
1. Struct(值类型)的更新逻辑
@Published修饰值类型时,只要属性内容发生修改,就会触发整个值的替换操作(值类型的修改本质是创建新实例)。@Published会检测到这个实例替换,自动发送更新通知,驱动视图刷新。
2. 普通Class(引用类型)的更新逻辑
@Published修饰引用类型时,只会监听引用本身的变化(比如给timeCounter重新赋值一个新的SampleTimeCounter实例)。仅仅修改Class内部的属性,引用指向的内存地址没有改变,@Published不会感知到变化,自然不会触发视图更新。
3. 嵌套ObservableObject的更新逻辑
SampleTimeCounterPublihser自身是ObservableObject,内部的@Published会发送更新通知,但这个通知无法自动传递到外层的SampleViewModel。外层@Published依然只监听引用的变化,所以内部属性修改时,外层ViewModel不会发送更新信号给视图。
解决方案
针对普通Class的解决办法
方案1:将Class改为Struct(优先推荐,若无需引用语义)
如果不需要共享实例或引用语义,直接把SampleTimeCounter改成Struct,就能像SampleTimeCounterStruct一样自动触发视图更新:
struct SampleTimeCounter { var showerTimeMinutes = "00" }
方案2:手动触发ViewModel的更新
如果必须保留Class类型,修改内部属性后手动调用ViewModel的objectWillChange.send(),强制发送更新通知:
// 在SampleViewModel中添加方法 class SampleViewModel: ObservableObject { @Published var timeCounter = SampleTimeCounter() // ... 其他属性 ... func updateClassMinutes(_ value: String) { timeCounter.showerTimeMinutes = value objectWillChange.send() } } // 在视图中调用这个方法 Button("Update Minutes Class") { viewModel.updateClassMinutes("15") }
针对嵌套ObservableObject的解决办法
方案1:转发内部ObservableObject的更新通知
在SampleViewModel的初始化方法中,订阅嵌套对象的objectWillChange事件,然后转发给自己的objectWillChange:
class SampleViewModel: ObservableObject { @Published var timeCounter = SampleTimeCounter() @Published var timeCounterStruct = SampleTimeCounterStruct() @Published var timerCounterPublisher = SampleTimeCounterPublihser() init() { // 订阅嵌套对象的更新事件并转发 _ = timerCounterPublisher.objectWillChange .sink(receiveValue: { [weak self] _ in self?.objectWillChange.send() }) } }
方案2:在视图中直接持有嵌套ObservableObject
在视图中用@ObservedObject直接持有嵌套的实例,跳过外层ViewModel的@Published,让视图直接监听内部对象的更新:
struct SimpleView: View { @StateObject private var viewModel = SampleViewModel() // 直接持有嵌套的ObservableObject @ObservedObject private var nestedCounter: SampleTimeCounterPublihser { viewModel.timerCounterPublisher } var body: some View { VStack { // ... 其他代码 ... VStack { Text(nestedCounter.showerTimeMinutes) Button("Update Minutes Class with Publisher") { nestedCounter.showerTimeMinutes = "15" } } } } }
总结
SwiftUI的视图更新依赖于ObservableObject发送的objectWillChange通知,而@Published对值类型和引用类型的监听逻辑不同:
- 值类型:修改内容=实例替换,自动触发更新
- 引用类型:仅当引用本身变化时触发更新,内部属性修改需手动转发或直接监听
内容的提问来源于stack exchange,提问作者Qazi Ammar

