I don't understand it at all but NSTimer
in my app definitely is running in background. I have a NSLog
in method run by the timer and it is logging while it's in background. It's on iPhone 4 with iOS 4.2.1. I have declared location background support in Info.plist.
I read the docs and many discussions here and elsewhere and it shouldn't be possible. Is it an iOS bug? Or undocumented feature? I don't want to use it and find out in near future, for example with coming of iOS 4.3 that Apple silently "fixed" it and the app won't be working.
Does anybody know more about it?
Problem is, NSTimer requires an active run loop which is not always readily available on background queues. The main thread has an active run loop but this defeats the purpose of having our timer run in the background so its a definite no go. So, to get a dedicated background-queue-friendly timer, we use GCD.
At the same time, didReceiveMemoryWarning is invoked for the app. At this point, so that your app continues to run properly, the OS begins terminating apps in the background to free some memory. Once all background apps are terminated, if your app still needs more memory, the OS terminates your app.
NSTimer
is going to fire whenever the main runloop is running. Apple makes no promises that I know of to unschedule timers or to prevent the main runloop from running. It's your responsibility to unschedule your timers and release resources when you move to the background. Apple isn't going to do it for you. They may, however, kill you for running when you are not supposed to or using too many seconds.
There are many holes in the system that will allow an app to run when it isn't authorized to. It would be very expensive for the OS to prevent this. But you cannot rely on it.
You can have a timer fire while in background execution mode. There are a couple of tricks:
beginBackgroundTaskWithExpirationHandler
. - (void)viewDidLoad
{
// Avoid a retain cycle
__weak ViewController * weakSelf = self;
// Declare the start of a background task
// If you do not do this then the mainRunLoop will stop
// firing when the application enters the background
self.backgroundTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundIdentifier];
}];
// Make sure you end the background task when you no longer need background execution:
// [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Since we are not on the main run loop this will NOT work:
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:@selector(timerDidFire:)
userInfo:nil
repeats:YES];
// This is because the |scheduledTimerWithTimeInterval| uses
// [NSRunLoop currentRunLoop] which will return a new background run loop
// which will not be currently running.
// Instead do this:
NSTimer * timer =
[NSTimer timerWithTimeInterval:0.5
target:weakSelf
selector:@selector(timerDidFire:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer
forMode:NSDefaultRunLoopMode];
// or use |NSRunLoopCommonModes| if you want the timer to fire while scrolling
});
}
- (void) timerDidFire:(NSTimer *)timer
{
// This method might be called when the application is in the background.
// Ensure you do not do anything that will trigger the GPU (e.g. animations)
// See: http://developer.apple.com/library/ios/DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW47
NSLog(@"Timer did fire");
}
Notes
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