A safer failsafe for beginBackgroundTaskWithExpirationHandler

As my brother succinctly put it “iOS likes to crash”. One such crash that can easily happen when you use background tasks is for your app to be killed for running the background task longer than the allotted time (generally 10 minutes).

The standard practice for avoiding this issue is to end the task you started with beginBackgroundTaskWithExpirationHandler in the expiration handler block of the same call. That works well, but there’s one catch. The UIBackgroundTaskIdentifier isn’t passed into that block, so you need to pass it through yourself. But what if some code modifies the variable you passed-in erroneously? If you are using a local variable, this isn’t a problem1 – but if your background task extends beyond one method it’s common to use a member variable, which could be accidentally modified.

And therein lies the danger. What if, despite all your very careful checking, that variable is modified. Now your failsafe won’t work correctly, and the app will crash. What’s worse, is it will crash in a totally random place. The crash logs give a very specific reason “App has active assertions beyond permitted time”, but don’t tell you exactly which LOC created the background task.

Here’s a better pattern. A safer failsafe if you will. Always declare the UIBackgroundTaskIdentifier as a local variable. End the task in the expiration handler using that local variable. After the task is created, set your member variable to equal the local variable for any additional additional processing (such ending the task naturally when it completes). Now the expiration handler is guaranteed to end the correct task, regardless of what else happens.

This pattern generates a bit of boilerplate code, and lets face it the more you copy and paste the less maintainable things get, and the more chance there is for error. So I’ve wrapped up this pattern into a simple utility class. The method is beginBackgroundTaskWithSafeExpirationHandler and it is designed to never crash with “active assertions beyond permitted time” no matter what you do. You can download the code from the gist.

To use it you simply call (where backgroundTask is the member variable):

backgroundTask = [WDBackgroundTask beginBackgroundTaskWithSafeExpirationHandler:^{
	backgroundTask = UIBackgroundTaskInvalid;

You’ll notice that the user’s expiration handler no longer ends the task. That’s because the wrapper takes care of that. You can still do any cleanup that you need (in this case, resetting the member variable). Your block is executed first, then the failsafe. Probably best to just read the code for the utility, and it should make sense.

If you use this wrapper every time you begin a background task instead of calling it directly then you then the “active assertions beyond permitted time” crash should never happen.

When you’ve rolled it out, search for beginBackgroundTaskWithExpirationHandler. The only instances should be inside WDBackgroundTask.h/.m.

  1. blocks copy local variables on invocation, see the docs []

Comments are closed.