将回调方法转变为通过组合进行反应

PaF*_*aFi 0 future publisher firebase swift combine

这就是我正在做的:

-> 使用 FirebaseAuthentification 登录/注册 Firebase

-> 监听 AuthStateDidChangeListenerHandle

-> 我在 Firestore 中存储额外的用户信息,因此我检查 Firestore 中是否存在该用户

-> 如果用户不存在,我创建一个空用户

-> 如果一切成功,我通过回调返回未来发布者(我也想更改它)

这是 checkLoginState 函数:

func checkLoginState(completion: @escaping (AnyPublisher<AccountDetails,Error>) -> Void) {
    self.handler = Auth.auth().addStateDidChangeListener { [weak self] auth, user in
        guard let safeSelf = self else { return }
        completion(Future<AccountDetails,Error> { promise in
            if let user = user {
                print(user)
                print(auth)

                safeSelf.checkIfUserIsInDatabase(user: user.uid) { result in
                    switch result {
                    case .success(let isAvailable):
                        if isAvailable {
                             promise(.success(AccountDetails(userUID: user.uid,name: user.displayName, loggedIn: true, premiumUser: false)))
                        } else {
                            safeSelf.createEmptyUser(user: user.uid,email: user.email) { result in
                                switch result {
                                case .success(_):
                                    promise(.success(AccountDetails(userUID: user.uid,name: user.displayName, loggedIn: true, premiumUser: false)))
                                case .failure(let error):
                                    print(error)
                                }
                            }
                        }
                    case .failure(let error):
                        print(error)
                    }
                }
            } else {
                promise(.success(AccountDetails(userUID: nil, loggedIn: false, premiumUser: false)))
            }
            }.eraseToAnyPublisher()
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

这些是我当前的功能:

private func checkIfUserIsInDatabase(user id: String, completion: @escaping (Result<Bool,Error>) -> Void)

private func createEmptyUser(user id: String, email:String?, completion: @escaping (Result<Bool,Error>) -> Void)
Run Code Online (Sandbox Code Playgroud)

这就是我想要使用的:

private func checkIfUserIsInDatabase(user id: String) -> AnyPublisher<Bool,Error>

private func createEmptyUser(user id: String) -> AnyPublisher<Bool,Error>

func checkLoginState() -> AnyPublisher<AccountDetails,Error>
Run Code Online (Sandbox Code Playgroud)

我有类似的东西,但它不起作用,也看起来很混乱:

func checkLoginState(completion: @escaping (AnyPublisher<AccountDetails,Error>) -> Void) {
    self.handler = Auth.auth().addStateDidChangeListener { [weak self] auth, user in
        guard let safeSelf = self else { return }
        completion(Future<AccountDetails,Error> { promise in
            if let user = user {
                print(user)
                print(auth)

                safeSelf.checkIfUserIsInDatabase(user: user.uid)
                    .sinkToResult { value in
                        switch value {
                        case .success(let isUserInDatabase):
                            if isUserInDatabase {
                                promise(.success(AccountDetails(userUID: user.uid,name: user.displayName, loggedIn: true, premiumUser: false)))
                            } else {
                                safeSelf.createEmptyUser(user: user.uid)
                                    .sinkToResult { value in
                                        switch value {
                                        case .success( _):
                                            promise(.success(AccountDetails(userUID: user.uid,name: user.displayName, loggedIn: true, premiumUser: false)))
                                        case .failure(let error):
                                            print(error)
                                        }
                                }
                            }
                        case .failure(let error):
                            print(error)
                        }

                }
            } else {
                promise(.success(AccountDetails(userUID: nil, loggedIn: false, premiumUser: false)))
            }
        }.eraseToAnyPublisher()
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

rob*_*off 5

所以你有某种AccountDetails类型:

import Combine
import FirebaseAuth

struct AccountDetails {
    var userId: String
    var name: String?
    var isLoggedIn: Bool
    var isPremiumUser: Bool
}
Run Code Online (Sandbox Code Playgroud)

让我们init用带有 a 的an 来扩展它User,因为它稍后会简化事情:

extension AccountDetails {
    init(user: User) {
        self.userId = user.uid
        self.name = user.displayName
        self.isLoggedIn = true
        self.isPremiumUser = false
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为你的最终目标是Publisher发出AccountDetails. 但由于并不总是有登录用户,因此它应该真正发出Optional<AccountDetails>,以便nil在用户注销时发出。

让我们首先将addStateDidChangeListenerAPI 包装在Publisher. 我们不能Future为此使用 a,因为 aFuture最多发出一个输出,但addStateDidChangeListener可以发出多个事件。所以我们将使用 aCurrentValueSubject来代替。这意味着我们需要一个地方来存储主题和AuthStateDidChangeListenerHandle. 您可以将它们存储为全局变量,或者存储在您的 中AppDelegate,或者您认为合适的任何位置。对于这个答案,让我们创建一个Demo类来保存它们:

class Demo {
    static let shared = Demo()

    let userPublisher: AnyPublisher<User?, Error>

    private let userSubject = CurrentValueSubject<User?, Error>(nil)
    private var tickets: [AnyCancellable] = []

    private init() {
        userPublisher = userSubject.eraseToAnyPublisher()
        let handle = Auth.auth().addStateDidChangeListener { [userSubject] (_, user) in
            userSubject.send(user)
        }
        AnyCancellable { Auth.auth().removeStateDidChangeListener(handle) }
            .store(in: &tickets)
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在你可以获取Publisher登录用户的 a (如果没有用户登录则为 nil ),如下所示:

let loggedInUserPublisher: AnyPublisher<User?, Error> = Demo.shared.userPublisher
Run Code Online (Sandbox Code Playgroud)

但你真正想要的是一个AccountDetails?出版商,而不是一个User?出版商,像这样:

let accountDetailsPublisher: AnyPublisher<AccountDetails?, Error> = Demo.shared
    .accountDetailsPublisher()
Run Code Online (Sandbox Code Playgroud)

所以我们需要编写一个accountDetailsPublisher将 映射User?到 的方法AccountDetails?

如果User?为零,我们只想发出nil。但如果User?.some(user),我们需要做更多的异步操作:我们需要检查用户是否在数据库中,如果不在则添加用户。该flatMap运算符允许您链接异步操作,但存在一些复杂性,因为我们需要根据上游发布者的输出采取不同的操作。

我们真的很想隐藏复杂性,只写这样的:

extension Demo {
    func loggedInAccountDetailsPublisher() -> AnyPublisher<AccountDetails?, Error> {
        return userPublisher
            .flatMap(
                ifSome: { $0.accountDetailsPublisher().map { Optional.some($0) } },
                ifNone: { Just(nil).setFailureType(to: Error.self) })
            .eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

但接下来我们需要写flatMap(ifSome:ifNone:)。这里是:

extension Publisher {
    func flatMap<Wrapped, Some: Publisher, None: Publisher>(
        ifSome: @escaping (Wrapped) -> Some,
        ifNone: @escaping () -> None
    ) -> AnyPublisher<Some.Output, Failure>
        where Output == Optional<Wrapped>, Some.Output == None.Output, Some.Failure == Failure, None.Failure == Failure
    {
        return self
            .flatMap { $0.map { ifSome($0).eraseToAnyPublisher() } ?? ifNone().eraseToAnyPublisher() }
            .eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们需要accountDetailsPublisherUser扩展中实现。这个方法需要做什么?它需要检查是否User在数据库中(一个异步操作),如果没有,则添加User(另一个异步操作)。由于我们需要链接异步操作,因此我们再次需要flatMap. 但我们真的想这样写:

extension User {
    func accountDetailsPublisher() -> AnyPublisher<AccountDetails, Error> {
        return isInDatabasePublisher()
            .flatMap(
                ifTrue: { Just(AccountDetails(user: self)).setFailureType(to: Error.self) },
                ifFalse: { self.addToDatabase().map { AccountDetails(user: self) } })
    }
}
Run Code Online (Sandbox Code Playgroud)

这是flatMap(ifTrue:ifFalse:)

extension Publisher where Output == Bool {
    func flatMap<True: Publisher, False: Publisher>(
        ifTrue: @escaping () -> True,
        ifFalse: @escaping () -> False
    ) -> AnyPublisher<True.Output, Failure>
        where True.Output == False.Output, True.Failure == Failure, False.Failure == Failure
    {
        return self
            .flatMap { return $0 ? ifTrue().eraseToAnyPublisher() : ifFalse().eraseToAnyPublisher() }
            .eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们需要在 上编写isInDatabasePublisheraddToDatabase方法User。我没有您的checkIfUserIsInDatabasecreateEmptyUser函数的源代码,因此我无法将它们直接转换为发布商。但我们可以使用以下方法包装它们Future

extension User {
    func isInDatabasePublisher() -> AnyPublisher<Bool, Error> {
        return Future { promise in
            checkIfUserIsInDatabase(user: self.uid, completion: promise)
        }.eraseToAnyPublisher()
    }

    func addToDatabase() -> AnyPublisher<Void, Error> {
        return Future { promise in
            createEmptyUser(user: self.uid, email: self.email, completion: promise)
        } //
            .map { _ in } // convert Bool to Void
            .eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于您的示例代码忽略了Bool的输出createEmptyUser,因此我改为addToDatabase写入输出Void