iosswiftiphonefirebasensurlsessiondatatask

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


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

Solution

  • 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. here I reload cells in batch. index is used for setting position of the cell in final array.

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