iOS 4 allows tasks to complete in the background even after the user closes the app (e.g. hits the Home button or switches to another app). This is called background task completion and is part of the multitasking feature set. Adding background task completion was surprisingly simple. Now, River Level running on any device with iOS 4+ will complete any data syncing tasks in the background if the user closes the app while syncing is active, which is a better experience for the user.
The key to adding background task completion is to wrap any tasks in calls to the UIApplication methods: beginBackgroundTaskWithExpirationHandler: and endBackgroundTask:.
beginBackgroundTaskWithExpirationHandler: signals iOS that your app would like the task to complete even if the app is moved into the background state, before the app becomes suspended. endBackgroundTask: signals iOS that your app's task has completed, allowing it to move your app to the suspended state if it needs to. If the app remains in the foreground the whole time then these calls have no effect, so it doesn't hurt to use them.
beginBackgroundTaskWithExpirationHandler: signals iOS that your app would like the task to complete even if the app is moved into the background state, before the app becomes suspended. endBackgroundTask: signals iOS that your app's task has completed, allowing it to move your app to the suspended state if it needs to. If the app remains in the foreground the whole time then these calls have no effect, so it doesn't hurt to use them.
Note that background tasks don't get free reign and don't get an indefinite period of time to complete. The "expiration handler" part of beginBackgroundTaskWithExpirationHandler: is a block specifying your cleanup code if the task out lives its lifetime. Code executing as a background task must not make any UI updates or openGL calls (as the app is offscreen).
In River Level I use code similar to the example below. If background tasks are supported on the host device (see Determining Whether Multitasking Support is Available) then I call beginBackgroundTaskWithExpirationHandler: to request background task completion before starting the syncing operation. When the syncing completes (and again, if background tasks are supported) and the task has not already been invalidated for some reason, I simply call endBackgroundTask: to tell iOS my task is done. If the app was running in a background state at this time and no other background tasks were active, iOS would move the app to the suspended state.
if ([RiverLevelAppDelegate sharedAppDelegate].backgroundSupported) {
// Register to execute task in background if required
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
// kick off the asynchronous syncing task...
// ... when the syncing task completes:
if ([RiverLevelAppDelegate sharedAppDelegate].backgroundSupported) {
// Flag completion of background task
if (bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
Also note that you are not limited to only one background task, you can register as many as you need although they all must complete before the expiry time. River Level registers a background task for each waterway when batch updates are requested.
Hi Chris, I was thinking this code had to be added to the delegate's applicationDidEnterBackground method. From the looks of your write-up, I can add the beginBackgroundTask... method anywhere I want to perform a long task that I don't want to be interrupted. Is this correct?
ReplyDeleteDale
Hi Dale, yes that is correct. Call it at any time so your background task request is active, ready for if the app gets backgrounded. If it doesn't get backgrounded, it will just be ignored.
DeleteCheers,
Chris
I had no idea this was so simple. Should have done it ages ago. Thanks for the writeup!
ReplyDelete