1

I have multiple API calls that will update the uitableview whenever they fetch the results. UI needs to be updated as and when API gives the data. All API calls are async. The data must be populated in the right order. API0 should update section 0, API1 should update section 1 and so on.

I've been able to achieve this with 2 APIs but when I use the 3rd API, I face crashes.

Please find my code below:

    @IBOutlet weak var myTableView: UITableView!
var myDataSource: myTableDataSource!
var initialLoad = true
var tablD = [Int : [Any]]()
let queue = DispatchQueue(
    label: "com.affluvar.multipleAPI.MyQueue", // 1
    attributes: .concurrent)

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    myTableView.tableFooterView = UIView()
    API0()
    API1()
    API2()
}

//MARK: API methods
func API0(){
    queue.async {
        print("queue THREAD0")
        getData(offset: 0,limit: 10){
            (finalArray) in
            print("IN FIRST")
            for rest in finalArray{
                print(rest.name)
            }
            self.queue.sync {
                self.tablD.updateValue(finalArray, forKey: 0)
                self.myDataSource = myTableDataSource(data: self.tablD)
                print("SYNC DATASOURCE UPDATED 0")
                DispatchQueue.main.async {
                    if self.initialLoad == true{
                        self.initialLoad = false
                        self.myTableView.dataSource = self.myDataSource
                        print("TABLE RELOADING INITIAL ")
                        self.myTableView.reloadData()
                    }
                }
                self.saveData(data: finalArray, section: 0)
            }
        }

    }
}

func API1(){
    queue.async{
        print("queue THREAD1 ")
        getData(offset: 10,limit: 12){
            (finalArray1) in
            print("IN SECOND ")
            for rest in finalArray1{
                print(rest.name)
            }
            self.queue.sync{
                self.tablD.updateValue(finalArray1, forKey: 1)
                self.myDataSource = myTableDataSource(data: self.tablD)
                print("SYNC DATASOURCE UPDATED 1")
                DispatchQueue.main.async {
                    if self.initialLoad == true{
                        self.initialLoad = false
                        self.myTableView.dataSource = self.myDataSource
                        print("TABLE RELOADING INITIAL ")
                        self.myTableView.reloadData()
                    }
                }
                self.saveData(data: finalArray1, section: 1)
            }

        }
    }
}

func API2(){
    queue.async{
        print("queue THREAD2")
        getData(offset: 20,limit: 12){
            (finalArray1) in
            print("IN third")
            for rest in finalArray1{
                print(rest.name)
            }
            self.queue.sync{
                self.tablD.updateValue(finalArray1, forKey: 2)
                self.myDataSource = myTableDataSource(data: self.tablD)
                print("SYNC DATASOURCE UPDATED 2")
                DispatchQueue.main.async {
                    if self.initialLoad == true{
                        self.initialLoad = false
                        self.myTableView.dataSource = self.myDataSource
                        print("TABLE RELOADING INITIAL ")
                        self.myTableView.reloadData()
                    }
                }
                self.saveData(data: finalArray1, section: 2)
            }
        }
    }
}

func saveData(data:[userModel], section: Int){
        print("queue THREAD barrier ", section)
        //print("table datasrc entry:", self.myTableView.dataSource ?? "nil")
        DispatchQueue.main.sync {
                print("In else ", section)
                    print("sections before IN ELSE",self.myTableView.numberOfSections)
                    self.myTableView.dataSource = self.myDataSource
                    //self.myTableView.reloadData()
                    //self.myTableView.beginUpdates()
                    print("reloading section number:", section)
                    self.myTableView.reloadSections([section], with: .automatic)
                    print("sections AFTER in ELSE",self.myTableView.numberOfSections)
                    //self.myTableView.endUpdates()
        }
        print("CONTINUING QUEUE WORK")
}

Above is my viewController code.

Error here is ->

2018-08-01 16:13:14.381174+0530 docAnywhere[4299:118229] *** Assertion failure in -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.33.6/UITableView.m:13456 Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource is not set'

Table datasource class ->>

class myTableDataSource: NSObject, UITableViewDataSource{
var userData = [Any]()
var tableData = [[Any]]()
var tablD = [Int : [Any]]()
init(data: [Int : [Any]]) {
    tablD = data
}
//MARK: Table methods
func numberOfSections(in tableView: UITableView) -> Int {
    //print("sections:", tablD.count)
    return 3
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    print("reload in progress")
    if let rows = tablD[section] as? [Any]{
        return rows.count
    }
    return 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "usersCell") as! usersCell
    let rowData = tablD[indexPath.section] as! [Any]
    let thisUser = rowData[indexPath.row] as? userModel// userData[indexPath.row] as? userModel
    cell.userName?.text = thisUser?.name ?? ""
    print("cell no", thisUser?.name ?? "", "at", indexPath.section, indexPath.row)
    if (indexPath.row == (rowData.count - 1)) {
        print("LAST CELL RELOAD COMPLETE HERE", indexPath.section)
    }
    return cell
}

}

Sometimes the error is this :-

2018-08-01 16:47:53.467219+0530 docAnywhere[6084:147469] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete section 0, but there are only 0 sections before the update'

NOTE:: It works fine in case of 2 API calls and dispatch group can't be used as the UI needs to be updated as soon as any API call finishes and data is available. Multiple array or datasource can't be used for multiple APIs.

1 Answers1

0

You're crashing because you're changing your tableView datasource constantly. I'm missing a piece of code in your code listing... If you can redesign you datasource so you don't have to change it like that you'll be fine. For example, you can reload section '1' with api1, and when it finishes you simply append the items you received from that api call to section 1... All this might sound confusing :) My point is, don't change the data source...

  • Actually the trouble starts in saveData method. Sometimes the print(“sections before”) command prints 0. Don’t know how number os sections become 0. This results in crash. One more thing I noticed is, the relodsections method doesn’t complete and returns prematurely. For ex- sometimes it only reloads 3-4 cells in the section and returns control to background thread. Why is it happening? I have used Sync method so it should complete before returning control. Also, this solution works for 2 APIs but crashes with 3 and more. – Ishan Baboota Aug 02 '18 at 02:39