I'm a bit confused about how and when to use beginBackgroundTaskWithExpirationHandler
.
Apple shows in their examples to use it in applicationDidEnterBackground
delegate, to get more time to complete some important task, usually a network transaction.
When looking on my app, it seems like most of my network stuff is important, and when one is started I would like to complete it if the user pressed the home button.
So is it accepted/good practice to wrap every network transaction (and I'm not talking about downloading big chunk of data, it mostly some short xml) with beginBackgroundTaskWithExpirationHandler
to be on the safe side?
Tasks are under a strict time limit, and typically get about 600 seconds (10 minutes) of processing time after an application has moved to the background on iOS 6, and less than 10 minutes on iOS 7+.
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 task NSUInteger 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]]; } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With