订阅者在 Firebase returns 之前返回值 SwiftUI 中的值

Subscriber returning value before Firebase returns a value in SwiftUI

我订阅了@Published 文本字段。我通过 class.

的 init() 函数添加订阅者

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

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

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

如何解决此问题,以便 return 值 return 成为 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)
        
    }

日志:

Combine 是处理这类事情的好工具。在这种情况下,您希望将 $email Publisher 转换为新的 Publisher,该 Publisher returns 来自异步 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)
    }
}

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

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
}