How do I use NSOperationQueue with NSURLSession?

Doug Smith picture Doug Smith · Feb 20, 2014 · Viewed 33.8k times · Source

I'm trying to build a bulk image downloader, where images can be added to a queue on the fly to be downloaded, and I can find out the progress and when they're done downloading.

Through my reading it seems like NSOperationQueue for the queue functionality and NSURLSession for the network functionality seems like my best bet, but I'm confused as to how to use the two in tandem.

I know I add instances of NSOperation to the NSOperationQueue and they get queued. And it seems I create a download task with NSURLSessionDownloadTask, and multiple if I need multiple tasks, but I'm not sure how I put the two together.

NSURLSessionDownloadTaskDelegate seems to have all the information I need for download progress and completion notifications, but I also need to be able to stop a specific download, stop all the downloads, and deal with the data I get back from the download.

Answer

Rob picture Rob · May 23, 2014

Your intuition here is correct. If issuing many requests, having an NSOperationQueue with maxConcurrentOperationCount of 4 or 5 can be very useful. In the absence of that, if you issue many requests (say, 50 large images), you can suffer timeout problems when working on a slow network connection (e.g. some cellular connections). Operation queues have other advantages, too (e.g. dependencies, assigning priorities, etc.), but controlling the degree of concurrency is the key benefit, IMHO.

If you are using completionHandler based requests, implementing operation-based solution is pretty trivial (it's the typical concurrent NSOperation subclass implementation; see the Configuring Operations for Concurrent Execution section of the Operation Queues chapter of the Concurrency Programming Guide for more information).

If you are using the delegate based implementation, things start to get pretty hairy pretty quickly, though. This is because of an understandable (but incredibly annoying) feature of NSURLSession whereby the task-level delegates are implemented at the session-level. (Think about that: Two different requests that require different handling are calling the same delegate method on the shared session object. Egad!)

Wrapping a delegate-based NSURLSessionTask in an operation can be done (I, and others I'm sure, have done it), but it involves an unwieldy process of having the session object maintain a dictionary cross referencing task identifiers with task operation objects, have it pass these task delegate methods passed to the task object, and then have the task objects conform to the various NSURLSessionTask delegate protocols. It's a pretty significant amount of work required because NSURLSession doesn't provide a maxConcurrentOperationCount-style feature on the session (to say nothing of other NSOperationQueue goodness, like dependencies, completion blocks, etc.).

And it's worth pointing out that operation-based implementation is a bit of a non-starter with background sessions, though. Your upload/download tasks will continue to operate well after the app has been terminated (which is a good thing, that's fairly essential behavior in a background request), but when your app is restarted, the operation queue and all of its operations are gone. So you have to use a pure delegate-based NSURLSession implementation for background sessions.