0

I have a use case where I need to download many files using NSURLSession.

To keep the sessiontasks from timing out I need to place them in an operation queue and limit the amount of concurrent downloads so they don't starve.

My idea is that I will load the task resume into an nsoperation and load them into an nsoperationqueue that will limit the number of concurrent activitiy.

The issue is that when I call [task resume] the code will exit and the nsoperation will consider itself complete even though I am waiting for the file to finish downloading.

Here is some code.

NSURLSessionDownloadTask *task = [session downloadTaskWithRequest: request 
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {

     //move the file to a permanent location

     CFRunLoopStop(CFRunLoopGetCurrent());
}
//I have instantiated a class global NSOperation queue.
[imageDownloadQueue addOperationWithBlock:^void() {
     [task resume];
     CFRunLoopRun();
}];

How can I keep the nsoperation thread alive while I wait for the callback? I have also tried using nsrunloop and adding a port to NSMachPort. That didn't seem to help.

Also, setting the HttpMaximumConnectionsPerHost on the NSURLSession doesn't help because the timeout timer starts when I resume a task. Which means I will get more timeouts than before.

mac10688
  • 2,145
  • 2
  • 24
  • 37
  • 2
    See https://developer.apple.com/videos/play/wwdc2015/226/ (WWDC 2015 Advanced NSOperations) which discusses these kinds of issues extensively. A year later, I challenged Dave DeLong about these patterns to see if he still thinks they're working. He says it's continued to be very successful. – Rob Napier Sep 22 '16 at 23:08
  • It was a good video but I don't have time to rewrite the pattern. It's way more code than this involved. I just want a way for the thread to stay alive and the callback to remove itself. – mac10688 Sep 23 '16 at 01:13
  • 1
    This is what asynchronous NSOperations are for. You make a custom `NSOperation`, return `true` for `isAsynchronous`, and override `start()` instead of `main()`. You'll have to set `isExecuting` and `isFinished` by hand at the appropriate times (i.e. when your operation completes). You don't want the "thread to stay alive." You want the operation to mark itself complete at the correct time. That's how asynchronous operations work. It just means you do it by hand rather than "when `main()` returns." – Rob Napier Sep 23 '16 at 02:02

1 Answers1

1

Sadly, I didn't have time for a proper fix and I needed the timeout issues fixed ASAP. I found something that did work.

    //I have instantiated a class global NSOperation queue.
[imageDownloadQueue addOperationWithBlock:^void() {
     [task resume];
     while(task.state == NSURLSessionTaskStateRunning)
    {
         [NSThread sleepForTimeInterval:.1];
    }
    }];

Since the NSOperation works on a background thread, I was able to sleep that thread while the task is running. This keeps the thread from dying but the task will run on it's own thread and isn't blocked.

mac10688
  • 2,145
  • 2
  • 24
  • 37