SwiftUI使用定时器更新Model后UI未同步刷新问题排查
问题根因
你的问题和定时器无关,核心是**Model为值类型(struct)引发的拷贝问题**:
- 你在
ViewModel的init中执行model.heartbeatEmitter.delegate = model时,因为struct是值类型,赋值时会自动产生一个独立的副本,所以delegate持有的并不是ViewModel内部存储的那个model实例,而是它的拷贝。 - 后续定时器触发
beat()方法时,修改的是这个副本的number属性,ViewModel中持有的原model实例没有发生任何变化,自然不会触发@Published的变更通知,UI也就不会更新。
另外补充:就算你把Model改为引用类型(class),@Published默认也只会在引用本身发生替换时才发送通知,修改引用对象的内部属性不会触发变更,也需要额外处理。
修复方案
这里提供两种常用的适配方案,你可以根据业务需求选择:
方案1:将委托接收方改为ViewModel(推荐,改动最小)
由ViewModel实现Heartbeat协议,直接修改自身持有的model实例,@Published会自动触发变更通知:
import Combine // 按需引入 class ViewModel: ObservableObject, Heartbeat { @Published private var model: Model private let emitter: HeartbeatEmitter init() { let emitter = HeartbeatEmitter() self.emitter = emitter model = Model(emitter: emitter) emitter.delegate = self // 委托指向ViewModel自身 } var number: String { "\(model.number)" } // 实现Heartbeat协议 func beat() { model.number += 1 print(model.number) } } // 原Model中的beat方法可直接删除 struct Model { var heartbeatEmitter: HeartbeatEmitter var number: Int = 0 init(emitter: HeartbeatEmitter){ self.heartbeatEmitter = emitter } }
方案2:将Model改为引用类型
如果你需要保留Model处理心跳逻辑的设计,可以把Model改为class,同时监听内部属性变更:
import Combine class Model: Heartbeat, ObservableObject { let heartbeatEmitter: HeartbeatEmitter @Published var number: Int = 0 init(emitter: HeartbeatEmitter){ self.heartbeatEmitter = emitter } func beat() { number += 1 print(number) } } class ViewModel: ObservableObject{ @Published var number: String = "" private var model: Model private var cancellables = Set<AnyCancellable>() init() { model = Model(emitter: HeartbeatEmitter()) model.heartbeatEmitter.delegate = model // 监听Model的number属性变更,同步更新UI用属性 model.$number .map { "\($0)" } .assign(to: &$number) } }
内容的提问来源于stack exchange,提问作者netshark1000
相关产品推荐
相关产品推荐

