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

Swift中Firebase循环加载多图失败问题求助

问题描述

需求:

  • 从Firestore数据库加载每个itemcode对应的图片URL(部分itemcode的URL以数组形式存储)
  • 加载每个itemcode对应的所有图片(按数组反向顺序)
  • 将itemcode及其对应图片以列表形式展示

问题:调用mrPicCall函数时,仅单张图片的itemcode能正常加载,多图的itemcode加载失败。怀疑是计数器i、j的使用问题,但无法定位修复。

相关代码

func mrPicCall(MRCode: [MRCode]) {
    self.mrCodeArray = MRCode.map { $0.code }
    self.imageurls = []
    self.selectedImages = [:]
    self.i = 0
    self.j = 0
    
    mrPicLoad() { (complete) in
        print(complete)
        self.booMRPicLoad = true
    }
}

func mrPicLoad(completion: @escaping (String) -> Void) {
    self.j = self.mrCodeArray.count
    
    for index in 0...(j - 1) {
        mrPicLoop(index: index) { (complete) in
            print(complete)
            
            if self.mrPicResponse.count == (self.j) {
                completion("5. MR list loaded!")
            }
        }
    }
}

func mrPicLoop(index: Int, completion: @escaping (String) -> Void) {
    let itemcode = mrCodeArray[index]
    
    loadPicDataFromStroage(itemcode: itemcode) { (complete) in
        print(complete)
        self.mrPicResponse.updateValue(MRPicResponse(selectedImages: self.selectedImages), forKey: index)
        completion("4. \(itemcode): Item \(index + 1) loaded.")
    }
}

func loadPicDataFromStroage(itemcode: String, completion: @escaping (String) -> Void) {
    // Get data from img database
    let db = Firestore.firestore()
    let docRef = db.collection("img_database").document(itemcode)
    
    self.imageurls = []
    self.selectedImages = [:]
    
    docRef.getDocument { snapshot, error in
        if error == nil && snapshot != nil {
            self.imageurls = snapshot!["url"] as! [String]
            self.imageurls.reverse()
            
            self.loadMRImageFromURL(itemcode: itemcode) { (complete) in
                print(complete)
                completion("3. \(itemcode): All image data loaded!")
            }
        } else {
            self.errMRPic = true
            print(error as Any)
        }
    }
}

func loadMRImageFromURL(itemcode: String, completion: @escaping (String) -> Void) {
    self.i = imageurls.count
    
    for index in 0...(i - 1) {
        loopThroughImageLoad(index: index, itemcode: itemcode) { (complete) in
            print(complete)
            
            if self.selectedImages.count == (self.i) {
                completion("2. \(itemcode): All images loaded!")
            }
        }
    }
}

func loopThroughImageLoad(index: Int, itemcode: String, completion: @escaping (String) -> Void) {
    let value = imageurls[index]
    
    loadImageFromStorage(picurl: value) { (imageLoaded) in
        self.selectedImages.updateValue(imageLoaded, forKey: index)
        completion("1. \(itemcode): Image \(index + 1) loaded.")
    }
}

func loadImageFromStorage(picurl: String, completion: @escaping (UIImage) -> Void) {
    let storageRef = storage.reference()
    let fileRef = storageRef.child(picurl)
    fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
        if error == nil && data != nil {
            if let image = UIImage(data: data!) {
                completion(image)
            }
        } else {
            self.errMRPic = true
            print(error as Any)
        }
    }
}
问题根源与修复方案

核心问题

  1. 全局变量被并发请求覆盖:imageurls、selectedImages、i、j均为全局变量,多个itemcode并行处理时,后一个请求会直接覆盖前一个请求的变量值,导致多图加载逻辑完全混乱。
  2. 完成判断逻辑不可靠:依赖selectedImages.count == i判断图片加载完成,但并发场景下变量被覆盖后,计数判断完全失效。
  3. 数据存储逻辑错误:mrPicResponse存储的是全局的selectedImages,而非当前itemcode对应的独立图片集合,最终导致所有itemcode的图片数据混淆。

修复步骤

1. 替换全局变量为局部变量/独立数据结构

取消全局的imageurls、selectedImages、i、j,改为在每个函数内使用局部变量,为每个itemcode维护独立的图片集合。

2. 重构loadPicDataFromStroage函数

func loadPicDataFromStroage(itemcode: String, completion: @escaping (String, [UIImage]?) -> Void) {
    let db = Firestore.firestore()
    let docRef = db.collection("img_database").document(itemcode)
    
    docRef.getDocument { snapshot, error in
        guard let snapshot = snapshot, error == nil else {
            self.errMRPic = true
            print(error as Any)
            completion("3. \(itemcode): Failed to load URL data", nil)
            return
        }
        
        guard let urls = snapshot["url"] as? [String] else {
            completion("3. \(itemcode): No URL data found", nil)
            return
        }
        let reversedUrls = urls.reversed()
        
        self.loadMRImages(from: reversedUrls, itemcode: itemcode) { images in
            completion("3. \(itemcode): All image data loaded!", images)
        }
    }
}

3. 用DispatchGroup处理图片并发加载

替代计数器,用DispatchGroup精准追踪所有图片加载完成状态:

func loadMRImages(from urls: [String], itemcode: String, completion: @escaping ([UIImage]) -> Void) {
    var images: [UIImage] = []
    let group = DispatchGroup()
    
    for url in urls {
        group.enter()
        loadImageFromStorage(picurl: url) { image in
            images.append(image)
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        completion(images)
    }
}

func loadImageFromStorage(picurl: String, completion: @escaping (UIImage) -> Void) {
    let storageRef = storage.reference()
    let fileRef = storageRef.child(picurl)
    fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
        if let error = error {
            self.errMRPic = true
            print(error)
            // 加载失败返回占位图,避免阻塞流程
            completion(UIImage())
            return
        }
        guard let data = data, let image = UIImage(data: data) else {
            completion(UIImage())
            return
        }
        completion(image)
    }
}

4. 重构mrPicLoop和mrPicLoad,确保数据独立

func mrPicLoad(completion: @escaping (String) -> Void) {
    let group = DispatchGroup()
    self.mrPicResponse.removeAll()
    
    for (index, itemcode) in mrCodeArray.enumerated() {
        group.enter()
        mrPicLoop(index: index, itemcode: itemcode) {
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        completion("5. MR list loaded!")
        self.booMRPicLoad = true
    }
}

func mrPicLoop(index: Int, itemcode: String, completion: @escaping () -> Void) {
    loadPicDataFromStroage(itemcode: itemcode) { message, images in
        print(message)
        if let images = images {
            // 存储当前itemcode对应的独立图片集合
            self.mrPicResponse[index] = MRPicResponse(selectedImages: images.enumerated().reduce(into: [:]) { $0[$1.offset] = $1.element })
        }
        completion()
    }
}

5. 简化mrPicCall函数

func mrPicCall(MRCode: [MRCode]) {
    self.mrCodeArray = MRCode.map { $0.code }
    self.mrPicResponse.removeAll()
    self.errMRPic = false
    
    mrPicLoad() { complete in
        print(complete)
    }
}

关键改进点

  • 用DispatchGroup替代全局计数器,精准追踪并发任务完成状态
  • 每个itemcode的图片数据独立存储,彻底避免全局变量覆盖问题
  • 完善错误处理,加载失败时返回占位图,保证流程不中断
  • 数据结构更清晰,确保mrPicResponse中存储的是对应itemcode的正确图片集合

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 01:47:06