Andrey M. Andrey M. - 3 months ago 46
Swift Question

How to wait until all NSOperations is finished?

I have the following code:

func testFunc(completion: (Bool) -> Void) {
let queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1

for i in 1...3 {
queue.addOperationWithBlock{
Alamofire.request(.GET, "https://httpbin.org/get").responseJSON { response in
switch (response.result){
case .Failure:
print("error")
break;
case .Success:
print("i = \(i)")
}
}
}
//queue.addOperationAfterLast(operation)
}
queue.waitUntilAllOperationsAreFinished()
print("finished")
}


and output is:

finished
i = 3
i = 1
i = 2


but I expect the following:

i = 3
i = 1
i = 2
finished


So, why queue.waitUntilAllOperationsAreFinished() don't wait?

Answer

Each operation you've added into queue is immediately executed because Alamofire.request simply returns without waiting for the response data.

Furthermore, there is a possibility of deadlock there. Since responseJSON block is executed within the main queue by default, blocking the main thread by calling waitUntilAllOperationsAreFinished will prevent it from executing the completion block at all.

First, in order to fix the deadlock issue, you can tell Alamofire to execute the completion block in a different queue, second, you can use dispatch_group_t to group the number of asynchronous HTTP requests and keep the main thread waiting till all those requests in the group finish executing:

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let group = dispatch_group_create()
for i in 1...3 {
  dispatch_group_enter(group)
  Alamofire.request(.GET, "https://httpbin.org/get").responseJSON(queue: queue, options: .AllowFragments) { response in
    print(i)
    dispatch_async(dispatch_get_main_queue()) {
      // Main thread is still blocked. You can update the UI here but it will take effect after all HTTP requests are finished.
    }
    dispatch_group_leave(group)
  }
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
print("finished")