SwiftUI中Combine订阅器早于Firebase异步返回结果如何解决
问题解决
核心原因
你遇到的问题确实是Firebase异步调用导致的:map是同步转换操作符,不会等待异步回调执行完成,就会直接返回初始的false值,回调里修改valid的操作是在map已经返回结果之后才执行的,自然不会生效。
解决方案
你需要用flatMap操作符替代map,把上游的邮箱输入事件转换为异步请求的Publisher,Combine会自动等待异步请求完成后再向下游传递结果。
首先封装Firestore查询为Combine Publisher,我们用Future来包装回调式API:
func checkEmailIsValid(_ email: String) -> AnyPublisher<Bool, Never> { return Future { promise in Firestore.firestore() .collection("users") .whereField("email", isEqualTo: email) .getDocuments { snapshot, err in guard err == nil, let snapshot = snapshot else { print("查询出错") promise(.success(false)) return } let isValid = snapshot.documents.isEmpty print("This email is \(isValid ? "valid" : "invalid")") promise(.success(isValid)) } } .eraseToAnyPublisher() }
修改后的订阅逻辑:
func addEmailSubscriber() { $email .debounce(for: .seconds(1.0), scheduler: DispatchQueue.main) // 过滤空输入,减少不必要的请求 .filter { !$0.isEmpty } // 用flatMap把输入的邮箱转换为异步查询的Publisher .flatMap { [weak self] email -> AnyPublisher<Bool, Never> in guard let self = self else { return Just(false).eraseToAnyPublisher() } return self.checkEmailIsValid(email) } .sink { [weak self] isValid in self?.emailIsValid = isValid } .store(in: &cancellabes) }
方案优化建议
你当前直接查询Firestore的users集合校验邮箱的方案存在安全风险:需要把users集合的email字段读权限开放给所有未登录用户,很容易被恶意遍历爬取所有注册邮箱。如果你的应用使用了Firebase Auth做登录能力,更优的方案是用Firebase Auth自带的fetchSignInMethods(forEmail:)接口校验邮箱是否已注册,不需要暴露用户集合的权限,示例如下:
func checkEmailRegistered(_ email: String) -> AnyPublisher<Bool, Never> { return Future { promise in Auth.auth().fetchSignInMethods(forEmail: email) { methods, err in guard err == nil, let methods = methods else { promise(.success(false)) return } // methods为空说明邮箱未注册 promise(.success(methods.isEmpty)) } } .eraseToAnyPublisher() }
内容的提问来源于stack exchange,提问作者Hackinator
相关产品推荐
相关产品推荐

