handleEventsForBackgroundURLSession with 2 different NSURLSessions and respective delegates

I'm running Xcode 8.2, ios 10.2, iphone 7.



I have 2 NSURLSessions with background session configs. Each session has their own seperate delegate. The first session (downloadSession) is used to download large video files, the 2nd session (dataTaskSession) for a dataTask. The dataTask is auto-created only when a downloaded file completes.



I have managed to get this working successfully, however am not seeing what Id expect to see via logging.



1. I manually start a download of the video content, which goes and gets a small mpd file, and then it starts downlloading a single (large) video file and a smaller audio file. I put the app into background and watch the logs come in, I also proxy through Charles to see the files are all downloading.



2. As the files complete downloading, I get the 1st log, the call to 'handleEventsForBackgroundURLSession' with the identifier for the 1st session.



3. I then see 2 logs for the 1st session delegate's 'didCompleteWithError', that the downloads were successful.



4. I also see some logs indicating 2 dataTasks have been created, each for the audio and video files.



5. I then see the log for the 1st Session delegate's 'URLSessionDidFinishEventsForBackgroundURLSession'



Now heres where I do stuff that is only a guess cos I cant find any documentation on it.



I'll outline what I thought the docs are saying to do, however this solution ends up failing:

1. In the downloadSession delegate 'URLSessionDidFinishEventsForBackgroundURLSession', I assign the completionHandler from the AppDelegate handleEventsForBackgroundURLSession function

2. I nullify the AppDelegate's hold on the completionHandler

3. I call the completionHandler if the number of tasks for the downloadSession is 0



This resulted in the 2 dataTasks being logged ast starting, but not completing in the background. Only after I brought the app to foreground, the dataTaskSession's delegate didCompleteWithError function reports an error "Lost connection to background transfer service\134"



Also, in my wifi proxy app, the 2 dataTasks do not run.



My solution so far, is to check in the downloadSession delegate 'URLSessionDidFinishEventsForBackgroundURLSession' that the downloadSession tasks count as well as the dataTaskSession tasks count is 0 before calling the completionHandler. What I see logged is that the completionHandler is not called because the dataTaskSession tasks count is 2. This results in no errors that I can see and the dataTasks complete successfully.



But what I dont see is another call to the AppDelegate 'handleEventsForBackgroundURLSession' with the identifier for the dataTaskSession's identifier, after the dataTasks have completed. Nor do I see a call to the dataTaskSession's delegate's 'URLSessionDidFinishEventsForBackgroundURLSession'. And I donot see any further calls to the downloadSession delegate's 'URLSessionDidFinishEventsForBackgroundURLSession'.



Which then suggests the completionHandlers for each background session never gets called.



So I wanted to ask, is this a bad thing? Is there some sort of cleanup that needs to be done that Im not doing? Or maybe all my logging is just not making it to output? Am I right in thinking that 2 different sessions and their different delegates should have their completionHandlers called seperately and shouldnt affect the other or the nsurlsessiond?



Cheers,



Ash

Replies

I have 2 NSURLSessions with background session configs. Each session has their own seperate delegate. The first session (downloadSession) is used to download large video files, the 2nd session (dataTaskSession) for a dataTask.

It’s pretty unusual to create data tasks in a background session. There are circumstances under which it makes sense, but in most cases you want to run data tasks like this in a standard session. For some background (hey hey) to this, see this post.

Here’s what I think is going on:

  1. We start with your app suspended in the background, with the system running your download tasks in session A.

  2. Those complete, and the system resumes your app and calls

    -application:handleEventsForBackgroundURLSession:completionHandler:
    .
  3. The URL session then starts calling completion events; your handling of those events schedules data tasks in session B.

  4. Eventually you get the

    -URLSessionDidFinishEventsForBackgroundURLSession
    delegate callback, at which point you call the completion handler you got in step 2.
  5. The system does two things in that completion handler:

    • It snapshots your UI for the benefit of the multitasking switcher

    • It releases the assertion that was preventing your app from being suspended

  6. Shortly thereafter your app suspends; this is bad because it causes the data tasks in session B to fail.

My solution so far, is to check in the downloadSession delegate 'URLSessionDidFinishEventsForBackgroundURLSession' that the downloadSession tasks count as well as the dataTaskSession tasks count is 0 before calling the completionHandler. What I see logged is that the completionHandler is not called because the dataTaskSession tasks count is 2. This results in no errors that I can see and the dataTasks complete successfully.

Right. If you defer calling the completion handler the system is still holding the assertion that prevents your app from suspending, and thus the data tasks can run to completion (presuming the network is functioning).

However, this isn’t the approach I’d take. Rather, I’d run these data task in a standard session and use a UIApplication background task to prevent my app from suspending while they are in flight. This yields two possible results:

  • The data tasks all complete, at which point you can end your UIApplication background task and your app will suspend shortly thereafter.

  • The UIApplication background task expires (presumably because the network has stalled), at which point you have to cancel your data tasks and then end the background task.

In the latter case you have two options:

  • You can record the failure in persistent storage so that you know you have to go back and do this cleanup when you next get a significant amount of execution time.

  • You can schedule replacements download tasks in a background session, which will run while your app is suspended.

The advantage of this approach over your current approach is that get notified when you run out of background execution time. If you run out of background execution time in your current approach, your app will be killed by the watchdog, which generates an unnecessary crash log and doesn’t give you a chance to implement the above-mentioned fallback strategies.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"