So on this page there is an example about background execution: https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW1, here is the example:
- (void)applicationDidEnterBackground:(UIApplication *)application {
bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
It is said that bgTask
is defined in class as variable. So there is one bgTask
property for every instance of class (object). If applicationDidEnterBackground
were to be called multiple times before async block finishes, isn't here danger of race condition? I mean bgTask
would change its value, and endBackgroundTask
would be called on new task value, instead of old value?
Isn't here a better solution to do this:
__block UIBackgroundTaskIdentifier bgTask;
before calling beginBackgroundTaskWithName
?
There is one instance of of bgTask
for each object, but this is on the AppDelegate
, not some general VC or object. So there will technically only ever be one bgTask
instance at work.
But this still creates problems. Since if this method gets called twice it will override bgTask
's value. My first thought was that upon exiting the app, more than once, all previous tasks would expire. But after testing realized this was not the case (which is a good thing IMO). What did happen is that bgTask
was overwritten (as expected) and the new value was passed to the first endBackgroundTask:
call. Immediate afterward bgTask
is set to UIBackgroundTaskInvalid
which clears it out and the cleared value is passed to any subsequent calls to endBackgroundTask:
. This obviously resulted in an unclean termination since not all the unique id's would have been ended, leading to the expiration
handler executing on any left over background tasks.
That being said, I believe your assumption about using a local variable is correct. If you try out this code (placed in the AppDelegate
applicationDidEnterBackground:
):
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
NSLog(@"Expired");
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
NSLog(@"Backgrounded: %@", @(bgTask));
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Done! %@", @(bgTask));
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
});
You will see that each local bgTask
is assigned a unique value and properly completed after 10 seconds (as per the dispatch_after
call).