SwiftUI:如何将一个ObservableObject的Published变量嵌入另一个?
嘿,这个问题提得很到位!确实有比手动订阅sink更简洁的方式来同步Auth的状态到你的SampleViewModel里,不用写那些繁琐的订阅代码,下面给你介绍几种实用方案:
方案1:直接通过计算属性访问单例状态
你完全不需要在SampleViewModel里维护自己的authState变量,直接通过计算属性引用Auth.shared的状态就行。因为SwiftUI会自动追踪ObservableObject的@Published属性变化,哪怕是通过单例访问的:
class SampleViewModel: ObservableObject { // 用计算属性直接返回单例的状态,不用自己存副本 var authState: Auth.AuthState { Auth.shared.currentState } }
使用场景:如果你的ViewModel只需要读取Auth状态,不需要基于这个状态做额外的本地逻辑处理,这种方式最省事。甚至在SwiftUI视图里,你可以直接观察Auth.shared,连ViewModel的这个属性都省了:
struct SampleView: View { @ObservedObject private var auth = Auth.shared var body: some View { switch auth.currentState { case .notAuthenticated: Text("请先登录") case .authenticated: Text("欢迎回来!") } } }
方案2:将Auth单例作为ViewModel的@ObservedObject
如果你的ViewModel需要基于Auth状态做一些业务逻辑(比如根据登录状态加载不同数据),可以把Auth.shared作为ViewModel的@ObservedObject,这样ViewModel会自动感知到Auth状态的变化,无需手动订阅:
class SampleViewModel: ObservableObject { // 把Auth单例作为ObservedObject,自动追踪它的Published属性变化 @ObservedObject private var auth = Auth.shared // 同样可以用计算属性简化访问 var authState: Auth.AuthState { auth.currentState } // 示例:根据Auth状态加载数据 func loadData() { if authState == .authenticated { // 加载已登录用户的数据 } else { // 加载游客数据 } } }
这样当Auth的currentState变化时,@ObservedObject会自动触发ViewModel的objectWillChange通知,订阅这个ViewModel的视图也会随之更新,完全不用手动写sink或者管理订阅的生命周期。
补充:原来的写法有个隐藏坑
你之前用sink的方式其实存在一个问题:订阅没有被存储,会在init结束后立即取消!因为sink返回的AnyCancellable如果不被保留,会被销毁,订阅就失效了。哪怕你想保留那个写法,也需要把订阅存在ViewModel的一个Set<AnyCancellable>里:
class SampleViewModel: ObservableObject { @Published var authState = Auth.shared.currentState private var cancellables = Set<AnyCancellable>() init() { Auth.shared.$currentState .assign(to: \.authState, on: self) .store(in: &cancellables) } }
但显然上面两种方案比这个要简洁得多,也更符合SwiftUI的响应式设计理念。
内容的提问来源于stack exchange,提问作者Aswath

