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

下载Firebase Storage数据时DispatchGroup执行异常,photoImage始终为nil怎么解决?

问题排查及修复方案

核心问题点

  • 下载图片的错误分支未调用group.leave():当图片下载出错时直接return,没有触发离开组的逻辑,导致group.wait()无限等待,后续赋值和登录逻辑永远不会执行
  • DispatchGroup定义为全局实例属性:如果多次调用fetchUserData会导致组计数混乱,应该改为方法内的局部变量
  • 下载成功后未保证在主队列赋值UI相关属性:UIImage赋值属于UI相关操作,Firebase Storage的回调默认不是主队列,直接赋值可能引发异常甚至赋值失败
  • photoImage未标记为@Published:如果需要在SwiftUI视图中监听这个属性的变化,没有@Published修饰的话视图不会收到更新通知,也会出现看起来赋值失败的假象

修复后的代码

class AppViewModel: ObservableObject {
    private var db = Firestore.firestore()
    
    @Published var userInfo: User?
    @Published var signedIn: Bool = false
    @Published var photoImage: UIImage? // 新增@Published修饰
    
    var handle: AuthStateDidChangeListenerHandle?
    let authRef = Auth.auth()
    
    var authHandle : AuthStateDidChangeListenerHandle?
    var rootInfoCollection : CollectionReference!
    var userIdRef = ""

    var downloadImageTask: StorageReference?
    // 移除全局的DispatchGroup,改为方法内局部变量
    
    func fetchUserData(){
        db.collection("Users").document("\(userIdRef)").getDocument { [self] document, error in
            // 合并空值判断逻辑简化代码
            if error == nil, let document = document, document.exists {
                self.userInfo = document.map { (documentSnapshot) -> User in
                    let data = documentSnapshot.data()
                    
                    let uid = data?["uid"] as? UUID ?? UUID()
                    let company = data?["company"] as? String ?? ""
                    let name = data?["name"] as? String ?? ""
                    let admin = data?["admin"] as? Bool ?? false
                    let photoRef = data?["photoRef"] as? String ?? ""
                    
                    self.downloadImageTask = Storage.storage().reference().child(photoRef)
                    
                    return User(uid: uid, company: company, name: name, admin: admin, photoRef: photoRef, photoImage: nil)
                }
                
                let group = DispatchGroup() // 局部DispatchGroup,避免多次调用冲突
                guard let downloadTask = downloadImageTask else {
                    // 没有下载任务直接走登录逻辑
                    DispatchQueue.main.async {
                        withAnimation {
                            self.signedIn = true
                        }
                    }
                    return
                }
                
                group.enter()
                downloadTask.getData(maxSize: 6 * 1024 * 1024) { data, error in
                    defer { group.leave() } // 用defer保证不管是否出错都调用leave
                    if let error = error {
                        print("Got an error fetching data: \(error.localizedDescription)")
                        return
                    }
                    guard let data = data, let image = UIImage(data: data) else {
                        print("图片数据无效")
                        return
                    }
                    DispatchQueue.main.async { // 主队列赋值UI属性保证生效
                        self.photoImage = image
                    }
                }
                
                group.notify(queue: .main) { // 用notify替代wait,不会阻塞线程
                    if self.photoImage != nil {
                        print("Photo Image Is Set")
                    }
                    withAnimation {
                        self.signedIn = true
                    }
                }
            }
        }
    }
}

额外优化说明

  • 用group.notify替代group.wait:wait会阻塞当前线程,notify是任务全部完成后自动回调到指定队列,性能更好也不会有死锁风险
  • 新增空值判断避免强制解包 crash:去掉了data!的强制解包,增加有效性校验
  • 用defer统一管理group.leave:避免漏写离开逻辑的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 05:42:03