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:
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.
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 } }