Firebase/Firestore完成处理函数失效问题排查
Firestore异步加载导致标签提前更新的问题排查
问题描述
在HomeViewController的viewDidLoad中调用Firestore获取用户数据,希望将标签更新为用户名,但标签在数据接收完成前就已完成更新。已尝试使用Swift完成处理函数,但未生效。登录后已将Firestore的DocumentReference赋值给userProfileDoc,供HomeViewController使用。
原代码
HomeViewController
class HomeViewController: UIViewController { var userProfileDoc: DocumentReference? private var currentUser: UserProfile? @IBOutlet weak var userFirstName: UILabel! @IBOutlet weak var logoutButton: UIButton! override func viewDidLoad() { super.viewDidLoad() let myCompletionHandler: (UserProfile) -> Void = { theUser in self.userFirstName.text = "Welcome, " + theUser.userFirstName! self.userFirstName.sizeToFit() } initializeUserProfile(using: myCompletionHandler) } func initializeUserProfile(using completionHandler: (UserProfile) -> Void) { self.currentUser = UserProfile(userProfileDoc!) completionHandler(self.currentUser!) } }
UserProfile构造函数
class UserProfile { var userFirstName: String? var userLastName: String? var profilePicURL: String? var UID: String? init(_ documentRef: DocumentReference) { documentRef.getDocument { [self] document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let data = document.data() userFirstName = data?["firstName"] as? String ?? "" userLastName = data?["lastName"] as? String ?? "" profilePicURL = data?["profilePicURL"] as? String ?? "" UID = data?["uid"] as? String ?? "" } } } } }
问题根源
Firestore的getDocument是异步操作,它会在后台线程发起网络请求,请求完成后才会触发闭包内的代码。但当前代码中:
- 创建
UserProfile实例时,init方法会立即返回,此时Firestore请求还未完成,userFirstName等属性仍为nil或默认值。 initializeUserProfile方法在创建UserProfile实例后立刻调用了完成回调,导致标签更新时使用的是未加载完成的空数据。
解决方案
改造代码,让完成回调仅在Firestore数据加载并解析完成后触发,确保UI更新时数据已就绪。
修改后的UserProfile
将初始化逻辑改为异步加载方法,确保数据加载完成后再返回实例:
class UserProfile { var userFirstName: String? var userLastName: String? var profilePicURL: String? var UID: String? // 私有化默认初始化,强制外部通过异步方法创建实例 private init() {} /// 从Firestore加载用户数据并创建UserProfile实例 static func load(from documentRef: DocumentReference, completion: @escaping (UserProfile?, Error?) -> Void) { documentRef.getDocument { document, error in // 处理请求错误 if let error = error { print("Error getting document: \(error.localizedDescription)") completion(nil, error) return } // 检查文档是否存在且有数据 guard let document = document, let data = document.data() else { let error = NSError(domain: "UserProfileError", code: -1, userInfo: [NSLocalizedDescriptionKey: "用户文档不存在或为空"]) completion(nil, error) return } // 解析数据并创建实例 let userProfile = UserProfile() userProfile.userFirstName = data["firstName"] as? String ?? "" userProfile.userLastName = data["lastName"] as? String ?? "" userProfile.profilePicURL = data["profilePicURL"] as? String ?? "" userProfile.UID = data["uid"] as? String ?? "" // 完成回调,返回加载好的实例 completion(userProfile, nil) } } }
修改后的HomeViewController
调整初始化逻辑,等待UserProfile数据加载完成后再更新UI:
class HomeViewController: UIViewController { var userProfileDoc: DocumentReference? private var currentUser: UserProfile? @IBOutlet weak var userFirstName: UILabel! @IBOutlet weak var logoutButton: UIButton! override func viewDidLoad() { super.viewDidLoad() initializeUserProfile { [weak self] user in guard let self = self, let user = user else { return } // 确保UI操作在主线程执行 DispatchQueue.main.async { self.userFirstName.text = "Welcome, \(user.userFirstName ?? "")" self.userFirstName.sizeToFit() } } } func initializeUserProfile(completion: @escaping (UserProfile?) -> Void) { // 检查DocumentReference是否有效 guard let docRef = userProfileDoc else { print("User profile document reference is nil") completion(nil) return } // 调用异步加载方法 UserProfile.load(from: docRef) { user, error in if let error = error { print("加载用户数据失败: \(error.localizedDescription)") completion(nil) return } self.currentUser = user completion(user) } } }
关键修改点说明
- 异步加载逻辑:将UserProfile的初始化改为异步静态方法,确保只有在Firestore数据加载完成后才通过回调返回实例。
- 主线程更新UI:显式使用
DispatchQueue.main.async执行UI更新操作(Firestore回调默认在主线程,但显式声明更安全,避免后续API变更导致问题)。 - 避免循环引用:使用
[weak self]捕获self,防止闭包导致的内存泄漏。 - 健壮性处理:增加了
userProfileDoc为nil、文档不存在或为空的错误处理,避免崩溃。
内容的提问来源于stack exchange,提问作者BiggRojo
相关产品推荐
相关产品推荐

