【问题标题】:URLSession dataTask execution orderURLSession数据任务执行顺序
【发布时间】:2021-09-01 09:20:16
【问题描述】:

我正在尝试使用 URLSession dataTask 获取图像数据 url 是从包含每个下载路径的 firebase firestore 文档中获取的然后将结果附加到数组 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()
            }
        }
    }

【问题讨论】:

    标签: ios swift iphone firebase nsurlsessiondatatask


    【解决方案1】:

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

    我宁愿在主线程中访问 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 { $0 }
                                self.tableView.reloadData()
                                self.spinner.dismiss()
                            }
                        }.resume()
                        
                    }
    

    【讨论】:

    • 避免为此使用 index 属性,这也可能会在您的脚下发生变化 - 就像表格视图允许用户删除某些内容一样。现在,如果用户在我们获取其图像时删除了一行,则索引将被破坏。一个 url,或者一些固定的标识符是最好的。类似于由模型 ID 键入的有序字典。
    • @Shadowrun 在某种程度上你是对的,但最初没有关于 ui 交互等的任何内容..
    【解决方案2】:

    你有什么:

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

    【讨论】:

    • 我尝试在 dispatchQueue.main.async 中追加,但仍然给我随机的 tableview 单元格
    • 因为:将它们按 1、2、3、4 的顺序排入队列,它们可以按 3、1、4、2 的顺序返回。
    • 像下载文件1、2、3和4一样。也许3是最小的文件,1秒后首先返回。文件 2 位于今天有点慢的服务器上,或者网络出现故障,并在 30 秒后恢复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 2019-12-06
    • 2017-05-13
    • 1970-01-01
    相关资源
    最近更新 更多