What is the best practice for persisting state associated with background Session Up/Download Tasks across app termination/relaunch?
For example, background upload Data task isn't supported. So Data content has to be copied to a temp file for background upload. When the upload completes, the temp file should be deleted. So the temp file URL has to be "remembered".
If iOS terminates the app (e.g. memory pressure) or the app crashes, when the upload completes the app is relaunched in background.
Task state info can't be maintained by the URLSession object because it is created anew when the app delegate receives 'handleEventsForBackgroundURLSession'.
Nor can I store state in the Session Task as an associated object because the Task is also "new" in the sense that when notified the Task has completed, it is not the same object the app created.
Similarly for a Download Task, Data accumulates as received. How best to "save" incremental Data so that if terminated and relaunched, all Data received thus far is available?
The obvious answer is UserDefaults, but that seems less than optimal as a temporary backing store for accumulating
download Data.
If I overlooked this in the documentation or the Dev forum archive, a RTFM gratefully accepted.
Cheers...
You're right that this is a challenge. It's one I've dealt with myself but, alas, I have not had time to publish my code as sample code.
Task state info can't be maintained by the URLSession object because it is created anew when the app delegate receives 'handleEventsForBackgroundURLSession'.
Nor can I store state in the Session Task as an associated object because the Task is also "new" in the sense that when notified the Task has completed, it is not the same object the app created.
While the task object is re-created, much of the data in that object is the same as in the previous object. The data items of interest include:
the URL itself (via
)task.originalRequest.URL
the task identifier (via
)task.taskIdentifier
You can use these to match the task object to your persistent private state.
Another much-less-obvious option is to store a custom property on the request via
+[NSURLProtocol setProperty:forKey:inRequest:]
. I used this to associate a UUID with the task, and I used that UUID as the key to match against my database.
And to be clear, my database wasn't anything complex, but rather a layout of files on the file system. My brain has paged out the exact details but the basic gist of things was:
There's a directory, named by the UUID, that holds all the transfer state.
Inside the directory there's a property list file that holds immutable data about the transfer. I write this file when I create the transfer and I expect it to always be valid from then on. If it's not I **** the transfer entirely. This file holds the critical data needed to restart the transfer from scratch if necessary.
There's a separate property list file that holds mutable state about the transfer. This gets written to frequently, so there's a greater chance that it might be corrupt (because, say, I crash while updating the file). However, if this file is corrupt I can re-create the transfer based on the immutable data.
For uploads, the directory contains a copy of the data being uploaded. If the file I'm uploading is immutable, I use a hard link to avoid making a copy.
IMPORTANT When you try to reconnect with your NSURLSession background session, there are four potential states, of which you have to deal with three:
no NSURLSession task, no private state record — This is the one state you can ignore.
NSURLSession task but no private state record — You've lost track of the transfer but NSURLSession still knows about it. In this case it's probably best to just cancel the task because you don't have any of your private state for it.
private state record but no NSURLSession task — NSURLSession has lost track of the transfer but you still know about it. In this case it's probably best to recreate the NSURLSession task and continue with the transfer.
NSURLSession task and private state record — This is the expected case.
Share and Enjoy
—
Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"