如何基于SwiftUI中ObservableObject的@Published数组可靠更新String?
问题根因
你的代码存在两个核心问题导致更新异常:
- 两个
.onReceive修饰符挂载在if let分支内的Text视图上,当recentEvents数组发生变化时,该Text视图可能被销毁重建,导致状态更新时序异常,且闭包捕获的上下文可能不是最新值。 - 单独维护
timeAgo作为@State变量,额外增加了状态同步的复杂度,很容易出现和DataStore中数据不一致的情况。
修复方案
直接移除冗余的timeAgo状态,将时间字符串改为实时计算的属性,仅用定时器触发视图刷新即可,修改后的视图代码如下:
struct EventsView: View { @EnvironmentObject var store: DataStore private let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() @State private var refreshTrigger = 0 private var timeAgoText: String { guard let latestEvent = store.recentEvents.first else { return "No Event Data" } return relativeTimeFormatter.localizedString(for: latestEvent.timestamp, relativeTo: Date()) } var body: some View { VStack { Text(timeAgoText) .fontWeight(.bold) .font(.system(.largeTitle, design: .rounded)) } .frame(minWidth: 250, maxWidth: 250, minHeight: 200, maxHeight: 200) .onReceive(timer) { _ in refreshTrigger += 1 } } } private let relativeTimeFormatter: RelativeDateTimeFormatter = { let formatter = RelativeDateTimeFormatter() formatter.dateTimeStyle = .numeric return formatter }()
因为DataStore是EnvironmentObject,它的@Published属性变更时会自动触发视图重绘,不需要额外监听store.$recentEvents,从根源上避免了状态不同步的问题。
其他优化建议
- DataStore层优化:
CKQueryOperation的completionBlock默认在后台队列执行,建议在调用promise之前就切换到主队列,避免潜在的线程问题;当前的fetchEvents方法没有错误处理,即使CloudKit请求失败也会返回空数组,建议新增失败状态暴露给视图层,方便做异常提示。 - 可以在
DataStore中新增一个@Published var latestEvent: Event?属性,拉取到数据后直接更新该属性,不需要视图层每次取数组第一个元素,逻辑更清晰。 - 可以监听App的前后台状态,在App进入后台时暂停定时器,回到前台时重新启动,减少不必要的资源消耗。
内容的提问来源于stack exchange,提问作者gohnjanotis
相关产品推荐
相关产品推荐

