URLSession 数据任务执行顺序

URLSession dataTask execution order

我正在尝试使用 URLSession dataTask 获取图像数据 url 是从包含每个下载路径的 firebase firestore 文档中获取的,在 snapShotDocuments 中使用 for 循环以升序排列,之后 url 被传递到检索​​的 URLSession dataTask数据然后将结果附加到数组 tableCells[] 中以更新 tableview,问题是更新的 tableview 中单元格的顺序与 tableCells 数组中对象的顺序不同,我希望它与我在这里不知道的并发是我的代码


public func fetchCells() {
        
        guard (UserDefaults.standard.value(forKeyPath: "email") as? String) != nil else {
            return
        }
        
        spinner.textLabel.text = "Loading"
        spinner.position = .center
        spinner.show(in: tableView)
        
        db.collection("ads").order(by: "timeStamp").addSnapshotListener { snapshot, error in
            
            self.tableCells = []
            
            guard error == nil , let snapShotDocuments = snapshot?.documents else {
                return
            }
            guard !snapShotDocuments.isEmpty else {
                print("snapshot is empty ")
                DispatchQueue.main.async {
                    self.tableView.isHidden = true
                    self.spinner.dismiss()
                }
                return
            }
            
            for i in snapShotDocuments {
                
                let documentData = i.data()
                
                guard let imageURL = documentData["imageurl"] as? String , let imageStringURL = URL(string: imageURL) else {
                    print("no url ")
                    return
                }
                
                guard let descriptionLabel = documentData["adDescription"] as? String , let titleLabel = documentData["adTitle"] as? String , let timeStamp = documentData["timeStamp"] as? Double else {
                    print("error")
                    return
                }
                 
                URLSession.shared.dataTask(with: imageStringURL) { data , _ , error  in
                    guard error == nil , let data = data else {
                        return
                    }
                    
                    let image = UIImage(data: data)
                    let newCell = adoptionCell(cellImage: image, descriptionLabel: descriptionLabel, titleLabel: titleLabel, timePosted: timeStamp, imageUrl: nil)
                    self.tableCells.append(newCell)
                    
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                        self.spinner.dismiss()
                    }
                }.resume()
            }
        }
    }

你有什么:

for i in snapShotDocuments {
   dataTask { 
     mutate tableCells (append) on background thread <- don't do that, A) not thread safe, and B) append won't happen in order they were dispatched, but the order they came back
     dispatch back to main {
        reload data  <- don't do that, reload the individual rows if needed, or reload everything at the end
     } 
}

您正在排队一些异步操作,这些操作可能需要不同的时间才能完成。例如,按 1、2、3、4 的顺序排列它们,然后它们可以按 3、1、4、2 的顺序返回。

你想要什么:

你的模型,排列好的数据实例,比方说一个结构数组,而不是 UITableViewCell 的。

for i in snapShotDocuments {
   dataTask {
     process on background thread, but then
     dispatch back to main {
        look up in the model, the object for which we have the new data
        mutate the model array
        then reload row at index path for the row involved
     } 
}

是的正确一些图像可能加载得更快,另一个加载得更慢。因此最终数组中的位置发生了变化。

我宁愿在主线程中访问 tableCells。在这里,我批量重新加载单元格。 index 用于设置单元格在最终数组中的位置。

var tableCells = Array<TableCell?>(repeating: nil, count: snapShotDocuments.count) //preserve space for cells...
                var count: Int32 = 0 // actual number of real load tasks
                
                for tuple in snapShotDocuments.enumerated() {
                    
                    let i = tuple.element
                    let index = tuple.offset //offset of cell in final array.
                    
                    let documentData = i.data()
                    
                    guard let imageURL = documentData["imageurl"] as? String , let imageStringURL = URL(string: imageURL) else {
                        print("no url ")
                        return
                    }
                    
                    guard let descriptionLabel = documentData["adDescription"] as? String , let titleLabel = documentData["adTitle"] as? String , let timeStamp = documentData["timeStamp"] as? Double else {
                        print("error")
                        return
                    }
                    count += 1 //increment count as there is new task..
                    URLSession.shared.dataTask(with: imageStringURL) { data , _ , error  in
                        if error == nil, let data = data {
                            let image = UIImage(data: data)
                            let newCell = adoptionCell(cellImage: image, descriptionLabel: descriptionLabel, titleLabel: titleLabel, timePosted: timeStamp, imageUrl: nil)
                            //self.tableCells.append(newCell)
                            tableCells[index] = newCell //because array has predefined capacity, thread safe...
                        }
                        
                        guard OSAtomicDecrement32(&count) == 0 else { return }
                        //last task, then batch reload..

                        DispatchQueue.main.async { [weak self] in
                            guard let self = self else { return }
                            self.tableCells = tableCells.compactMap { [=10=] }
                            self.tableView.reloadData()
                            self.spinner.dismiss()
                        }
                    }.resume()
                    
                }