Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper use of beginBackgroundTaskWithExpirationHandler

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?

like image 382
Eyal Avatar asked Apr 25 '12 16:04

Eyal


People also ask

How long will an iOS app run in the background?

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+.


2 Answers

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) } 
like image 108
Ashley Mills Avatar answered Sep 24 '22 10:09

Ashley Mills


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 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]];          }      } } 
like image 30
Joel Avatar answered Sep 26 '22 10:09

Joel