简体   繁体   English

URLSession 数据任务执行顺序

[英]URLSession dataTask execution order

i am trying to fetch images data using URLSession dataTask the urls are fetched from a firebase firestore document that contains each download path using for loop in snapShotDocuments in ascending order, after that the urls are passed into the URLSession dataTask that retrieves the data then appending the result in an array tableCells[] to update a tableview, the problem is the order of the cells in the updated tableview is not the same order of the objects in tableCells array, i am expecting it has something to do with concurrency that i am not aware of here is my code我正在尝试使用 URLSession dataTask 从包含每个下载路径的 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()
            }
        }
    }

What you have:你有什么:

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
     } 
}

You're enqueuing a number of asynchronous operations that can take varying amount of time to complete.您正在排队许多异步操作,这些操作可能需要不同的时间才能完成。 Enqueue them in order 1, 2, 3, 4 and they could come back in order 3, 1, 4, 2, for example.例如,将它们按 1、2、3、4 的顺序排入队列,然后它们可以按 3、1、4、2 的顺序返回。

What you want:你想要什么:

Your model, arranged data instances, let's say an array, of structs, not UITableViewCell's.你的模型,排列的数据实例,比方说一个结构数组,而不是 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
     } 
}

yes correct some image might be loaded faster another is loaded slower.是的,正确一些图像可能加载得更快,另一个加载得更慢。 therefore position in final array is changed.因此最终数组中的位置发生了变化。

I would rather access tableCells in main thread.我宁愿在主线程中访问 tableCells。 here I reload cells in batch.在这里,我批量重新加载单元格。 index is used for setting position of the cell in final array. 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 { $0 }
                            self.tableView.reloadData()
                            self.spinner.dismiss()
                        }
                    }.resume()
                    
                }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM