【问题标题】:Subscriber returning value before Firebase returns a value in SwiftUIFirebase 在 SwiftUI 中返回值之前的订阅者返回值
【发布时间】:2021-09-06 05:30:41
【问题描述】:

我有一个@Published 文本字段的订阅者。我通过类的 init() 函数添加订阅者。

此订阅者的地图会扫描 Firebase 中的所有用户并检查“电子邮件”字段。如果用户输入的电子邮件在这些字段之一中,那么我将 valid 设置为 false(因为该电子邮件已在使用中)。

但是,当我运行此代码时,将首先执行第二个打印行,并使用默认值。然后,一段时间后,运行 print("This email is valid") 行。

我认为这与 Firebase 调用是异步任务这一事实有关。

如何解决这个问题,以便返回值返回 Firebase 调用的结果?我使用的是 Xcode 版本 12.5.1。

另外,我不确定这是否是检查电子邮件是否已被使用的最佳方法,但这是我能找到的唯一解决方案:)

代码:

func addEmailSubscriber() {
        
        $email
            .debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
            .map { email -> Bool in
                
                var valid: Bool = false
                
                Firestore.firestore()
                    .collection("users")
                    .whereField("email", isEqualTo: email)
                    .getDocuments { (snapshot, err) in
                        
                        guard let snapshot = snapshot else { print("Error getting snapshot"); return }
                        
                        if err != nil {
                            print("Error occured")
                            return
                        }
                        
                        if snapshot.documents.count == 0 {
                            print("This email is valid") // This is successfully printed after some time
                            valid = true
                            return
                        }
                    }
                
                print(valid) // This gets printed first, with the value of "false"
                return valid
                
            }
            .sink { [weak self] isValid in
                self?.emailIsValid = isValid
                
            }
            .store(in: &cancellabes)
        
    }

日志:

【问题讨论】:

    标签: swift firebase google-cloud-firestore swiftui combine


    【解决方案1】:

    Combine 是处理此类事情的绝佳工具。在这种情况下,您希望将您的 $email Publisher 转换为一个新的 Publisher,该 Publisher 从您的异步 Firebase 调用中返回值。您可以使用mapswitchToLatestFuture 来执行此操作。

    这是一个显示概念的基本示例(不使用您的代码):

    class ViewModel : ObservableObject {
        @Published var email = ""
        
        private var cancellable : AnyCancellable?
        
        init() {
            cancellable = $email
                .debounce(for: .seconds(1), scheduler: RunLoop.main)
                .map { val in
                    Future<Bool,Never> { promise in
                        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                            promise(.success(true))
                        }
                    }
                }
                .switchToLatest()
                .sink(receiveValue: { val in
                    print("Valid?",val)
                })
        }
    }
    
    struct ContentView : View {
        @StateObject private var viewModel = ViewModel()
        
        var body: some View {
            TextField("", text: $viewModel.email)
        }
    }
    

    翻译成你的代码,它看起来像(见内联 cmets):

    func addEmailSubscriber() {
        
        $email
            .debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
            .map { email -> Bool in
                Future<Bool,Never> { promise in
                    Firestore.firestore()
                        .collection("users")
                        .whereField("email", isEqualTo: email)
                        .getDocuments { (snapshot, err) in
                            
                            guard let snapshot = snapshot else {
                                print("Error getting snapshot")
                                promise(.success(false)) //you may want to consider using `.failure` here, but keep in mind that that'll cancel the Publisher chain unless you erase the errors later on
                                return
                            }
                            
                            if err != nil {
                                print("Error occured")
                                promise(.success(false)) //see above comment
                                return
                            }
                            
                            if snapshot.documents.count == 0 {
                                print("This email is valid") // This is successfully printed after some time
                                promise(.success(true))
                            }
                        }
                }
            }
            .switchToLatest()
            .sink { [weak self] isValid in
                self?.emailIsValid = isValid
            }
            .store(in: &cancellabes) //I kept the spelling the same, but this looks like a typo
    }
    

    【讨论】:

    • 最好使用switchToLatest 而不是flatMap。前者会在发出新请求时取消先前的请求,而后者会让先前的请求继续进行。事实上,我相信这也有可能导致竞争条件 - 即先前值的 isValid 可能会在电子邮件的当前值之后到达。
    猜你喜欢
    • 2017-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-23
    • 1970-01-01
    • 2019-12-09
    • 1970-01-01
    • 2018-09-04
    相关资源
    最近更新 更多