3

Why is NSURLSession operation queue empty after creating and resuming an NSURLSessionTask?

Is there a way to tell if an NSURLSession has tasks pending?

The goal is to wait for multiple tasks to complete, but this doesn't work:

NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithStreamedRequest:request];
[uploadTask resume];
// this prints "0"
NSLog(self.session.delegateQueue.operationCount)
// this returns immediately instead of waiting for task to complete
[self.session.delegateQueue waitUntilAllOperationsAreFinished];
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • OK, I may have found a clue at https://github.com/AFNetworking/AFNetworking/issues/1504 and https://stackoverflow.com/q/19414486/1033581 regarding the fact that `NSURLSession` can't be solely used for batching. – Cœur Nov 30 '16 at 08:24
  • 1
    DispatchGroup is what you need :) Found this link on very google result http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/ – Sandeep Bhandari Nov 30 '16 at 09:03

3 Answers3

11

I found a solution that avoids invalidating the session, using the suggested DispatchGroup.

(answer is in Swift, while question is in ObjC ... but it's the same logic)

Note that when using, uploadTaskWithStreamedRequest:, we need to implement an URLSessionTaskDelegate and func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?). So, to simplify the answer, I will demonstrate the use of DispatchGroup with uploadTaskWithRequest:from:completionHandler:.

// strong reference to the dispatch group
let dispatchGroup = DispatchGroup()

func performManyThings() {
    for _ in 1...3 {
        let request = URLRequest(url: URL(string: "http://example.com")!)
        dispatchGroup.enter()
        let uploadTask = self.session.uploadTask(with: request, from: nil) { [weak self] _, _, _ in
            self?.dispatchGroup.leave()
        }
        uploadTask.resume()
    }
    dispatchGroup.notify(queue: .main) {
        // here, all the tasks are completed
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • 1
    For any body else who wants to use DispatchGroup, I found this article helpful in explaining how to use it - https://medium.com/@oleary.audio/simultaneous-asynchronous-calls-in-swift-9c1f5fd3ea32 – N1234 Aug 28 '19 at 08:51
1

It's very easy as long as you don't care about reusing the session and don't mind doing things asynchronously:

  • Call finishTasksAndInvalidate on the session.
  • Implement the URLSession:didBecomeInvalidWithError: method in your session delegate to do whatever work you need to do after the last task finishes.

That said, the problem with your code above is that the session doesn't have any operations until it starts the first fetch, which can't happen as long as your code is blocking the run loop. You generally should not attempt to use NSURLSession synchronously. It is almost always the wrong way to solve things. :-)

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • I've upvoted your answer, because it's true that I didn't specify about reusing the session. Truth is, I needed to reuse it, so I've accepted a different answer. – Cœur Dec 13 '16 at 12:06
1

In addition to @Cœur's solution, if you just need to wait for all request responses before continuing code execution you don't need to use a queue. Instead of:

dispatchGroup.notify(queue: .main) {
    // here, all the tasks are completed
}

you can simply use:

// Waits that all requests finish.
dispatchGroup.wait()

And continue code execution after this point.

Ricardo Barroso
  • 634
  • 9
  • 11