3

I am trying to update the cells in a UITableView by calling .reloadData() but it reloads the data before the process finishes therefore not really updating anything since the array that it gets data from is still empty. The way I have the code structured is that it makes a bunch of API calls that are each a Swift Operation. A OperationQueue executes these operations. What needs to happen now is when all the tasks are completed it such call self.tableView.reloadData() . I've tried putting it in a operation's completion block however it is not the main thread and therefore does not execute.

Any help would be appreciated.

EDIT: I've tried changing the thread of the OperationQueue to the main thread using OperationQueue.main and adding a final operation that is run after all other operations are done that contains self.tableView.reloadData(). Although this allows me to reload the UITable it causes the UI to lag. (I have a pull to refresh in the UITableView in which makes the main thread use obvious.)

Sample Code:

let operation1 = BlockOperation {
//Gets Data from server & waits until task is complete before ending
}

let operation2 = BlockOperation {
//Parse JSON
}

let updateOperation = BlockOperation {
    self.tableView.reloadData()
    //Throws a "UITableView.reloadData() must be used from main thread only"
}

operation2.addDependency(operation1)
updateOperation.addDependency(operation2)

let queue = OperationQueue()
queue.addOperation(operation1)
queue.addOperation(operation2)
queue.addOperation(completionOperation)
  • Are these operations initiating tasks that are, themselves, asynchronous? If so, have you done the necessary subclassing of `Operation`, and implemented everything necessary for "asynchronous" operations? – Rob Mar 25 '18 at 07:18
  • Yes, I have. The first `Operation` is a HTML Request, the second is parsing the request. The second `Operation` has a dependance on the first. I've simplified this for the sake of reproducing the issue. – D. Budziwojski Mar 25 '18 at 16:11
  • You've added no dependencies for that completion operation. Clearly you want it dependent upon the second operation. And you said something about "using the main queue". Only the completion operation should go on `OperationQueue.main`. – Rob Mar 25 '18 at 18:05

4 Answers4

1

There are two possible approaches:

  1. Create a new operation that you want to execute when all the others are done, which I'll call the "completion operation". Then every time you create one of the existing individual operations, add that operation as a dependency to your "completion operation". Then, when you're done adding all of your individual operations (and adding each as a dependency to your "completion operation), then add your completion operation to the main queue. It won't fire until all of the other, individual operations are finished.

    For example:

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 4
    
    let completionOperation = BlockOperation {
        print("done")
    }
    
    for _ in 0 ..< 10 {
        let operation = ...
        completionOperation.addDependency(operation)
        queue.addOperation(operation)
    }
    
    OperationQueue.main.addOperation(completionOperation)
    

    Note, this assumes that you've created your operations carefully so that the operations don't complete until the task inside the individual operations are done. Notably, if your operation is starting a task that is, itself, an asynchronous task (e.g. a network request), make sure that you define the Operation subclass to be an "asynchronous" operation (isAsynchronous returns true) and make sure that it does the isFinished/isExecuting KVO correctly upon completion.

  2. The other approach is to use a dispatch group. Every time you create one of your individual operations, enter the group (not in the operation itself, but as you create those operations and add them to your operation queue). Then, as the last task in your individual operations, leave the group. Then, when you're done adding all of your individual operations, you can make a dispatch group notify, which you can schedule on the main queue. Then, when all of the individual operations, your dispatch group notify block will fire.

    For example:

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 4
    
    let group = DispatchGroup()
    
    for _ in 0 ..< 10 {
        group.enter()
        let operation = BlockOperation {
            ...
            group.leave()
        }
        queue.addOperation(operation)
    }
    
    group.notify(queue: .main) {
        print("done")
    }
    

I'd lean towards option 1 (staying within the operation queue paradigm), but both approaches would work.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you for the information, as per my update, I had changed the thread of the `OperationQueue` to be in the main thread: `OperationQueue.main` although this works it does causes the UI to freeze and this is obvious when a user uses a 'PullToRefresh' controller. Is there anyway to access the main thread from a third completion operation to reload the data? – D. Budziwojski Mar 25 '18 at 16:15
  • No, leave your operation queue alone. Only the completion operation goes on `OperationQueue.main`. – Rob Mar 25 '18 at 17:59
0

From the completion block you can use Grand Central Dispatch to execute another block on the main thread. Use

DispatchQueue.main.async {
    self.tableView.reloadData()
    // your other code here
}
davecom
  • 1,499
  • 10
  • 30
-1

Can you use a completion handler example:

func reloadData(completion: @scaping (completed: Bool)->Void) {
     //your code
     completion(true)
}

Call the func

reloadData() {(completed : Bool) in
    if completed {
      tableView.reloadData()
    }    
}
luistejadaa
  • 89
  • 11
-2

Use DispatchGroup.

let dispatchGroup = DispatchGroup()

dispatchGroup.enter()

asyncTask1() { dispatchGroup.leave() }

asyncTask2() { dispatchGroup.leave() }

asyncTask3() { dispatchGroup.leave() }

dispatchGroup.notify(queue: .main) {
//reload table }

sachin_kvk
  • 77
  • 1
  • 9