Proper use of beginBackgroundTaskWithExpirationHandler Proper use of beginBackgroundTaskWithExpirationHandler ios ios

Proper use of beginBackgroundTaskWithExpirationHandler


If you want your network transaction to continue in the background, then you'll need to wrap it in a background task. It's also very important that you call endBackgroundTask when you're finished - otherwise the app will be killed after its allotted time has expired.

Mine tend look something like this:

- (void) doUpdate {    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{        [self beginBackgroundUpdateTask];        NSURLResponse * response = nil;        NSError  * error = nil;        NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];        // Do something with the result        [self endBackgroundUpdateTask];    });}- (void) beginBackgroundUpdateTask{    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{        [self endBackgroundUpdateTask];    }];}- (void) endBackgroundUpdateTask{    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];    self.backgroundUpdateTask = UIBackgroundTaskInvalid;}

I have a UIBackgroundTaskIdentifier property for each background task


Equivalent code in Swift

func doUpdate () {    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {        let taskID = beginBackgroundUpdateTask()        var response: URLResponse?, error: NSError?, request: NSURLRequest?        let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)        // Do something with the result        endBackgroundUpdateTask(taskID)        })}func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {    return UIApplication.shared.beginBackgroundTask(expirationHandler: ({}))}func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {    UIApplication.shared.endBackgroundTask(taskID)}


The accepted answer is very helpful and should be fine in most cases, however two things bothered me about it:

  1. As a number of people have noted, storing the task identifier as a property means that it can be overwritten if the method is called multiple times, leading to a task that will never be gracefully ended until forced to end by the OS at the time expiration.

  2. This pattern requires a unique property for every call to beginBackgroundTaskWithExpirationHandler which seems cumbersome if you have a larger app with lots of network methods.

To solve these issues, I wrote a singleton that takes care of all the plumbing and tracks active tasks in a dictionary. No properties needed to keep track of task identifiers. Seems to work well. Usage is simplified to:

//start the taskNSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask];//do stuff//end the task[[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey];

Optionally, if you want to provide a completion block that does something beyond ending the task (which is built in) you can call:

NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTaskWithCompletionHandler:^{    //do stuff}];

Relevant source code available below (singleton stuff excluded for brevity). Comments/feedback welcome.

- (id)init{    self = [super init];    if (self) {        [self setTaskKeyCounter:0];        [self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];        [self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];    }    return self;}- (NSUInteger)beginTask{    return [self beginTaskWithCompletionHandler:nil];}- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;{    //read the counter and increment it    NSUInteger taskKey;    @synchronized(self) {        taskKey = self.taskKeyCounter;        self.taskKeyCounter++;    }    //tell the OS to start a task that should continue in the background if needed    NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{        [self endTaskWithKey:taskKey];    }];    //add this task identifier to the active task dictionary    [self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];    //store the completion block (if any)    if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];    //return the dictionary key    return taskKey;}- (void)endTaskWithKey:(NSUInteger)_key{    @synchronized(self.dictTaskCompletionBlocks) {        //see if this task has a completion block        CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];        if (completion) {            //run the completion block and remove it from the completion block dictionary            completion();            [self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];        }    }    @synchronized(self.dictTaskIdentifiers) {        //see if this task has been ended yet        NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];        if (taskId) {            //end the task and remove it from the active task dictionary            [[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];            [self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];        }    }}


Here is a Swift class that encapsulates running a background task:

class BackgroundTask {    private let application: UIApplication    private var identifier = UIBackgroundTaskInvalid    init(application: UIApplication) {        self.application = application    }    class func run(application: UIApplication, handler: (BackgroundTask) -> ()) {        // NOTE: The handler must call end() when it is done        let backgroundTask = BackgroundTask(application: application)        backgroundTask.begin()        handler(backgroundTask)    }    func begin() {        self.identifier = application.beginBackgroundTaskWithExpirationHandler {            self.end()        }    }    func end() {        if (identifier != UIBackgroundTaskInvalid) {            application.endBackgroundTask(identifier)        }        identifier = UIBackgroundTaskInvalid    }}

The simplest way to use it:

BackgroundTask.run(application) { backgroundTask in   // Do something   backgroundTask.end()}

If you need to wait for a delegate callback before you end, then use something like this:

class MyClass {    backgroundTask: BackgroundTask?    func doSomething() {        backgroundTask = BackgroundTask(application)        backgroundTask!.begin()        // Do something that waits for callback    }    func callback() {        backgroundTask?.end()        backgroundTask = nil    } }