NSURLSession background sessions are optimised for transferring a small number of large resources. Moreover, for download transfers its best if the transfer is resumable. This design makes the best use of client device resources and the available network bandwidth.
If your app runs a lot of tasks in a background session, you should rethink its design. Below you'll find a number of options you might consider.
These options all require server-side changes. If you're unable to change the server—perhaps you're writing a client app for some fourth-party server—you won't be able to implement them directly. In that case you might consider creating your own server that sits between your app and the final server and implements the necessary smarts required to optimise your app's network usage.
The basic strategy here is to have the sender (the server for a download, your app for an upload) pack the data into some sort of archive, transfer that archive over the network, and then have the receiver unpack it. There are, however, a number of complications, as described in the subsequent sections.
The obvious choices for an archive format are zip and tar. OS X has lots of options for handling these formats but none of that support is present on iOS (r. 22151959). OTOH, it's easy to find fourth-party libraries to fill in this gap.
It's common to have a corpus of data at one end of the connection that you need to replicate at the other. If the data is large, you don't want to transfer the whole thing every time there's an update. There are a couple of common strategies for dealing with this:
catalogue diff — In this approach the receiver first downloads a catalogue from the sender, then diffs its current state against that catalogue, then requests all the things that are missing. Alternatively, the receiver can pass a catalogue of what it has to the sender, at which point the sender does the diff and returns the things that are missing. The critical part is that, once the diff has been done, all of the missing resources are transferred in a single archive.
The biggest drawback here is resume. If the sender is working with lots of different clients, each of which has their own unique needs, the sender may need to keep a lot of unique archives around so that it can resume a failed transfer. This can be a serious headache.
versions — In this approach you manage changes to the data as separate versions. The receiver can simply pass the version number it has to the sender, at which point the sender knows exactly what data the receiver needs.
This approach requires a bit more structure but it does avoid the above-mentioned problem with resume. The sender only needs to maintain a limited number of version diffs. In fact, you can balance the number of diffs against your desire to reduce network usage: maintaining a lot of diffs means that you only have to transfer exactly what the receiver needs, while maintaining fewer diffs makes for a simpler server at the cost of a less efficient use of the network.
Download versus Upload
The discussion so far has applied equally to both downloads and uploads. There is, however, one key difference: NSURLSession does not support resumable uploads. When doing an upload you have to balance the number of tasks you submit to the session against the negative effects of a transfer failure. For example, if you do a single large upload then it's annoying if the transfer fails when it's 99% complete. On the other hand, if you do lots of tiny uploads, you're working against the NSURLSession background session design.
Keep in mind that it is possible to support resumable uploads with sufficient server-side support. For example, you could implement an algorithm like this:
Run an initial request to allocate an upload ID.
Start the upload with that upload ID.
If it completes successfully, you're done.
If it fails, make a request with the upload ID to find out how much the server received.
Start a new upload for the remaining data.