NSURLSession’s background session support on iOS includes a resume rate limiter. This limiter exists to prevent apps from abusing the background session support in order to run continuously in the background. It works as follows:
(the daemon that does all the background session work) maintains a delay value for your app.nsurlsessiond
It doubles that delay every time it resumes (or relaunches) your app.
It resets that delay to 0 when the user brings your app to the front.
It also resets the delay to 0 if the delay period elapses without it having resumed your app.
When your app creates a new task while it is in the background, the task does not start until that delay has expired.
To understand the impact of this, consider what happens when you download 10 resources. If you pass them to the background session all at once, you see something like this:
Your app creates tasks 1 through 10 in the background session.
starts working on the first few tasks.nsurlsessiond
As tasks complete,
starts working on subsequent ones.nsurlsessiond
Eventually all the tasks complete and
resumes your app.nsurlsessiond
Now consider what happens if you only schedule one task at a time:
Your app creates task 1.
starts working on it.nsurlsessiond
When it completes,
resumes your app.nsurlsessiond
Your app creates task 2.
delays the start of task 2 a little bit.nsurlsessiond
starts working on task 2.nsurlsessiond
When it completes,
resumes your app.nsurlsessiond
Your app creates task 3.
delays the start of task 3 by double the previous amount.nsurlsessiond
starts working on task 3.nsurlsessiond
When it completes,
resumes your app.nsurlsessiond
Steps 8 through 11 repeat, and each time the delay doubles. Eventually the delay gets so large that it looks like your app has stopped making progress.
If you have a lot of tasks to run then you can mitigate this problem by starting tasks in batches. That is, rather than start just one task in step 1, you would start 100. This only helps up to a point. If you have thousands of tasks to run, you will eventually start seeing serious delays. In that case it’s much better to change your design to use fewer, larger transfers.
Note All of the above applies to iOS 8 and later. Things worked differently in iOS 7. There’s a post on DevForums that explains the older approach.
Finally, keep in mind that there may be other reasons for your task not starting. Specifically, if the task is flagged as discretionary (because you set the
discretionary
flag when creating the task’s session or because the task was started while your app was in the background), the task may be delayed for other reasons (low power, lack of Wi-Fi, and so on).(r. 22323366)