在不相关的 ViewModel 中从我的身份验证 class 订阅用户变量 - Swift/Combine

Subscribing to a user variable from my authentication class in an unrelated ViewModel - Swift/Combine

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

但是当 UserProfileViewModel 被实例化时,loggedInUser 似乎仍然被视为 nil,我不确定是否有一种方法可以像这样使用来自另一个 class 的 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)
        }
    }
}

我还有另一个 class,我想获取 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: "")
        }
    }

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

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
        }
    }
    
}

那么,您是正确的,因为您可以使用 Combine 在 AuthenticationState 实例和 UserProfileViewModel 实例之间形成数据管道。您可以使用 Combine 的 sink(receiveValue:) 方法将 UserProfileViewModelloggedInUser 属性 绑定到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()
}

使用 $loggedInUser 访问 @Published 提供的内置发布者。在这里您可以下沉并创建订阅。另请注意,sink(receiveValue:) 返回的 AnyCancellable 的存储。无论 UserProfileViewModel 需要多长时间,都要对此进行强烈引用。