首页 > 解决方案 > 在不相关的 ViewModel 中从我的身份验证类订阅用户变量 - Swift/Combine

问题描述

我正在尝试根据我的 AuthenticationState 类中的登录用户实例化用户配置文件(使用 Firebase Auth)。此用户配置文件是我的 UserProfileViewModel 的一部分,它应该为编辑用户配置文件的视图提供动力。

但是,当 UserProfileViewModel 被实例化时,loggedInUser 似乎仍然被视为 nil,我不确定是否有办法可以使用来自另一个类的 Combine 订阅,以确保我订阅了 loggedInUser 发布的变量身份验证状态的特定实例。

身份验证状态由我的主应用程序文件调用,作为环境对象 - 一旦用户登录,loggedInUser 将设置为该 Firebase Auth 用户:

class AuthenticationState: NSObject, ObservableObject {
    
    // The firebase logged in user, and the userProfile associated with the users collection
    @Published var loggedInUser: User?
    
    
    @Published var isAuthenticating = false
    @Published var error: NSError?
    
    static let shared = AuthenticationState()
    
    private let auth = Auth.auth()
    fileprivate var currentNonce: String?

我在我的主应用程序文件中初始化 AuthenticationState:

@main
struct GoalTogetherApp: App {

    let authState: AuthenticationState
    
    init() {
        FirebaseApp.configure()
        self.authState = AuthenticationState.shared
        
        setupFirebase()
    }
 
 
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(authState)
        }
    }
}

我有另一个类,我想获取loggedInUser,然后使用该用户的uid从Cloud Firestore创建或查找userProfile:

class UserProfileViewModel: ObservableObject {
    
    @Published var loggedInUser: User?
    @Published var userProfile: UserProfile?
    
    private let auth = Auth.auth()
    private let db = Firestore.firestore()
    
    init() {
        self.loggedInUser = AuthenticationState.shared.loggedInUser
        
        if self.loggedInUser != nil {
            self.userProfile = self.loadUser()
        }
    }

个人资料页面应该抓住它并从 UserProfile 中提取电子邮件,但它一直显示为空白:

struct ProfilePage: View {
    @ObservedObject var userProfileVM = UserProfileViewModel()
    
    @State var email: String = ""
    
    init() {
        print("User Profile VM equals: \(String(describing: userProfileVM.userProfile))")
        if userProfileVM.userProfile?.email != nil {
            _email = State(initialValue: userProfileVM.userProfile!.email!)
        } else {
            _email = State(initialValue: "")
        }
    }

标签: swiftfirebase-authenticationcombine

解决方案


您可以使用addStateDidChangeListener(_:)Firebase 的 Auth 类的实例方法,并将User通过完成处理程序传入的实例分配给您自己的loggedInUserin属性AuthenticationState。这样,每当用户登录或注销时,您都会收到通知 - 保持同步。像这样:

class AuthenticationState: NSObject, ObservableObject {
    
    @Published var loggedInUser: User?
    
    //...other properties...

    static let shared = AuthenticationState()
    private let auth = Auth.auth()
    
    override init() {
        super.init()
        auth.addStateDidChangeListener { [weak self] (_, user) in
            self?.loggedInUser = user
        }
    }
    
}

AuthenticationState然后,您是正确的,因为您可以使用 Combine 在您的实例和实例之间形成数据管道UserProfileViewModel。您可以使用Combineinit()sink(receiveValue:)方法将UserProfileViewModel' 的属性loggedInUser绑定到AuthenticationState

class UserProfileViewModel: ObservableObject {
    @Published var loggedInUser: User?
    init() {
        AuthenticationState.shared.$loggedInUser
            .sink { [weak self] user in
                self?.loggedInUser = user
            }
            .store(in: &subs)
    }
    private var subs: Set<AnyCancellable> = .init()
}

Using$loggedInUser访问由@Published. 在这里您可以下沉并创建订阅。另请注意,AnyCancellablesink(receiveValue:). 无论需要多长时间,都要对此保持强烈的参考UserProfileViewModel


推荐阅读