You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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是异步操作,它会在后台线程发起网络请求,请求完成后才会触发闭包内的代码。但当前代码中:

  1. 创建UserProfile实例时,init方法会立即返回,此时Firestore请求还未完成,userFirstName等属性仍为nil或默认值。
  2. 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)
        }
    }
}

关键修改点说明

  1. 异步加载逻辑:将UserProfile的初始化改为异步静态方法,确保只有在Firestore数据加载完成后才通过回调返回实例。
  2. 主线程更新UI:显式使用DispatchQueue.main.async执行UI更新操作(Firestore回调默认在主线程,但显式声明更安全,避免后续API变更导致问题)。
  3. 避免循环引用:使用[weak self]捕获self,防止闭包导致的内存泄漏。
  4. 健壮性处理:增加了userProfileDoc为nil、文档不存在或为空的错误处理,避免崩溃。

内容的提问来源于stack exchange,提问作者BiggRojo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 11:40:24