Swift中异步修改@Published属性后视图未更新问题
问题描述
我是Swift/SwiftUI新手,查了不少同类问题帖,没发现自己操作和别人有差异,所以来提问。
背景:我想创建一个供UI全局使用的AppState应用状态对象,初始化后加入环境并传递给根视图ContentView。在ContentView中注入appState,通过switch语句根据ViewRouting类的currentRoute变量切换登录、登出、无网络等主视图。ViewRouting代码如下:
class ViewRouting: ObservableObject, Equatable { static let shared = ViewRouting() @Published var currentRoute: MainRoutes { willSet { print("Setting current route to \(newValue)") UserDefaults.standard.set(newValue.rawValue, forKey: "currentRoute") } } static func == (lhs: ViewRouting, rhs: ViewRouting) -> Bool { return lhs.currentRoute == rhs.currentRoute } private init() { currentRoute = .loggedOut } }
问题:检测网络状态变化时首次发现问题,异步修改currentRoute后,UI没有响应也没有重新绘制。哪里操作错了?
问题根源及解决办法
单例与环境对象的冲突
你同时使用了单例static let shared和环境对象注入,大概率出现了实例不统一的问题:比如App中初始化了一个新的ViewRouting实例放入环境,但异步修改的是单例的currentRoute,导致UI监听的实例和被修改的实例不是同一个,UI自然不会更新。
解决:要么全程用单例(ContentView直接引用ViewRouting.shared,不用环境对象);要么去掉单例,确保整个App只使用同一个实例注入环境,异步修改时操作该实例。异步操作未在主线程执行
SwiftUI的状态更新必须在主线程触发。如果网络状态检测是在后台线程触发的修改操作,会导致@Published的通知无法正确传递到UI层。
解决:用DispatchQueue.main.async包裹修改代码:
DispatchQueue.main.async { self.currentRoute = .yourTargetRoute }
Equatable实现的干扰
ObservableObject的更新机制依赖@Published的变化通知,而你实现的Equatable可能在某些场景下(比如结合@EnvironmentObject使用时)影响视图的刷新判断。
解决:先移除Equatable的实现,测试UI是否恢复更新;如果确实需要保留Equatable,要确保其逻辑不会阻碍状态变化的通知传递。初始化逻辑的漏洞(非直接原因,但需修复)
当前init方法没有读取UserDefaults中保存的路由值,直接默认设为.loggedOut,属于逻辑缺陷,建议补上:
private init() { if let savedRaw = UserDefaults.standard.string(forKey: "currentRoute"), let savedRoute = MainRoutes(rawValue: savedRaw) { currentRoute = savedRoute } else { currentRoute = .loggedOut } }
内容的提问来源于stack exchange,提问作者ALex

