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

Swift闭包内使用async/await加载Firebase数据失效问题排查

问题原因分析

你的loadData函数虽然标记了async,但内部没有正确处理Firestore的异步操作:

  • Firestore的getDocuments是闭包回调风格的异步API,它会在后台线程执行,而你的函数直接走到return date语句,此时闭包还没执行,date还是初始的nil,自然返回未加载完成的结果。
  • 仅仅标记async只能让函数被await调用,但函数内部没有任何挂起等待逻辑,await根本等不到异步操作完成。
正确实现方式

需要用withCheckedThrowingContinuation将Firestore的闭包API转换为async/await风格,让函数挂起直到异步操作完成,再返回结果。同时修复代码中的不安全操作(比如强制解包)。

修改后的完整代码

struct LoadData {
    let db = Firestore.firestore()
    var delegate: LoadDataDelegate?    
    
    func getData(phoneNumber: String) {
        Task {
            do {
                let dateUser = try await loadData(phoneNumber: phoneNumber)
                print("Делегат получил дату - ", dateUser)
                delegate?.loadData(date: dateUser)
            } catch {
                print("加载数据出错:\(error)")
                delegate?.loadData(date: nil)
            }
        }
    }
    
    func loadData(phoneNumber: String) async throws -> TimeInterval {
        return try await withCheckedThrowingContinuation { continuation in
            db.collection("Users").getDocuments { snapshot, error in
                // 处理请求错误
                if let err = error {
                    continuation.resume(throwing: err)
                    return
                }
                
                guard let snapshot = snapshot else {
                    let emptySnapshotError = NSError(domain: "LoadDataError", code: -1, userInfo: [NSLocalizedDescriptionKey: "获取用户快照为空"])
                    continuation.resume(throwing: emptySnapshotError)
                    return
                }
                
                // 查找是否存在目标用户
                if let userDoc = snapshot.documents.first(where: { $0.documentID == phoneNumber }) {
                    // 用户存在,解析时间戳
                    guard let date = userDoc["dateFirstLaunch"] as? TimeInterval else {
                        let invalidDateError = NSError(domain: "LoadDataError", code: -2, userInfo: [NSLocalizedDescriptionKey: "时间戳格式无效"])
                        continuation.resume(throwing: invalidDateError)
                        return
                    }
                    continuation.resume(returning: date)
                } else {
                    // 用户不存在,创建新用户并返回当前时间戳
                    let currentDate = Date().timeIntervalSince1970
                    db.collection("Users").document(phoneNumber).setData(["dateFirstLaunch": currentDate]) { writeError in
                        if let err = writeError {
                            continuation.resume(throwing: err)
                        } else {
                            continuation.resume(returning: currentDate)
                        }
                    }
                }
            }
        }
    }
}

protocol LoadDataDelegate {
    func loadData(date: TimeInterval?)
}

关键改动说明

  1. 用withCheckedThrowingContinuation包装异步操作:
    这个API可以将闭包风格的异步代码转为async/await,当Firestore的请求完成(成功或失败)时,调用continuation.resume()来恢复函数执行,返回结果或抛出错误。
  2. 修复不安全操作:
    • 去掉QuerySnapshot!的强制解包,改用可选绑定+自定义错误处理
    • 用delegate?.替代delegate!.,避免delegate为nil时崩溃
  3. 完善新用户逻辑:
    新用户创建时,直接返回当前时间戳,确保无论用户是否存在,都能返回有效数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:27:57