SwiftUI中Firebase Auth登录后检查Firestore用户存在性及线程优化问题
Firebase登录后检查用户存在性的异步处理优化
问题描述
我实现了SwiftUI代码用于Firebase Auth登录后,检查用户是否存在于Firestore的users集合中。尝试用DispatchSemaphore让耗时的checkIfTheUserExistsInDataBase函数优先执行,结果导致应用冻结,求更好的实现方式。
当前实现代码
@Published var isNewUser: Bool? init() { self.isNewUser = false } func checkIfTheUserExistsInDataBase( userID: String?, completion: @escaping (_ isNewuser: Bool) -> Void ) { let docRef = db.collection("users").whereField("user_id", isEqualTo: userID!).limit(to: 1) docRef.getDocuments { querySnapshot, error in if error != nil { print(error?.localizedDescription) } else { if let doc = querySnapshot?.documents, doc.isEmpty { completion(true) } else { completion(false) } } } } func login( email: String, password: String, completion: @escaping (_ error: Error?, _ isEmailVerified: Bool) -> Void ) { Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in if error == nil { if authDataResult!.user.isEmailVerified { DispatchQueue.main.async { self.checkIfTheUserExistsInDataBase(userID: authDataResult?.user.uid) { isNewUser in self.isNewUser = isNewUser } } UserDefaults.standard.set(authDataResult?.user.uid, forKey: CurrentUserDefaults.userID) completion(error, true) } else { print("Email not verified") completion(error, false) } } else { completion(error, false) } } }
解决方案
为什么信号量会导致冻结
DispatchSemaphore会阻塞当前线程等待异步操作完成,如果在主线程调用,直接卡住UI线程导致应用冻结。Firebase的API本身就是异步非阻塞设计,完全不需要用信号量强制同步。
优化方案1:调整回调顺序(基于原回调模式)
原代码在用户存在性检查完成前就调用了completion,逻辑顺序混乱。把completion和UserDefaults设置移到检查完成的回调里,确保所有异步操作完成后再通知上层:
@Published var isNewUser: Bool? init() { self.isNewUser = false } func checkIfTheUserExistsInDataBase(userID: String?, completion: @escaping (Bool) -> Void) { guard let userID = userID else { completion(false) return } let docRef = db.collection("users").whereField("user_id", isEqualTo: userID).limit(to: 1) docRef.getDocuments { querySnapshot, error in if let error = error { print(error.localizedDescription) completion(false) return } let isNew = querySnapshot?.documents.isEmpty ?? true completion(isNew) } } func login(email: String, password: String, completion: @escaping (Error?, Bool) -> Void) { Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in guard let authResult = authDataResult, error == nil else { completion(error, false) return } if !authResult.user.isEmailVerified { print("Email not verified") completion(nil, false) return } // 先完成用户存在性检查,再执行后续操作 self.checkIfTheUserExistsInDataBase(userID: authResult.user.uid) { [weak self] isNewUser in guard let self = self else { return } // 主线程更新@Published变量 DispatchQueue.main.async { self.isNewUser = isNewUser } UserDefaults.standard.set(authResult.user.uid, forKey: CurrentUserDefaults.userID) completion(nil, true) } } }
优化方案2:使用async/await(Swift 5.5+推荐)
Firebase从iOS 15+/macOS 12+开始支持async/await API,代码更简洁易读,彻底避免回调嵌套:
import FirebaseAuth import FirebaseFirestore @Published var isNewUser: Bool? init() { self.isNewUser = false } // 转为async函数 func checkIfTheUserExistsInDataBase(userID: String?) async -> Bool { guard let userID = userID else { return false } do { let querySnapshot = try await db.collection("users") .whereField("user_id", isEqualTo: userID) .limit(to: 1) .getDocuments() return querySnapshot.documents.isEmpty } catch { print(error.localizedDescription) return false } } // 转为async函数 func login(email: String, password: String) async throws -> Bool { let authResult = try await Auth.auth().signIn(withEmail: email, password: password) guard authResult.user.isEmailVerified else { print("Email not verified") return false } // 等待用户存在性检查完成 let isNewUser = await checkIfTheUserExistsInDataBase(userID: authResult.user.uid) // 主线程更新@Published变量 await MainActor.run { self.isNewUser = isNewUser } UserDefaults.standard.set(authResult.user.uid, forKey: CurrentUserDefaults.userID) return true }
关键优化点
- 移除
DispatchSemaphore,避免阻塞主线程导致UI冻结 - 修复逻辑顺序:确保用户存在性检查完成后再执行后续操作
- 替换强制解包为可选绑定,提升代码安全性
- 用
async/await简化异步代码,可读性和可维护性更强 - 确保
@Published变量在主线程更新(SwiftUI状态必须在主线程修改)
内容的提问来源于stack exchange,提问作者Giorgi Samkharadze
相关产品推荐
相关产品推荐

