Scheduled NSTimer when app is in background?

Solution 1:

You shouldn't solve this problem by setting a timer, because you're not allowed to execute any code in the background. Imagine what will happen if the user restarts his iPhone in the meantime or with some other edge cases.

Use the applicationDidEnterBackground: and applicationWillEnterForeground: methods of your AppDelegate to get the behavior you want. It's way more robust, because it will also work when your App is completely killed because of a reboot or memory pressure.

You can save the time the timer will fire next when your App is going to the background and check if you should take action when the App comes back to the foreground. Also stop and start the timer in this methods. While your App is running you could use a timer to trigger the update at the right moment.

Solution 2:

You can have a timer fire while in background execution mode. There are a couple of tricks:

  • You need to opt into background execution with beginBackgroundTaskWithExpirationHandler.
  • Either create the NSTimer on the main thread, OR you will need to add it to the mainRunLoop manually with [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]

If you are on the main thread:

{
    // 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.backgroundTaskIdentifier];
    }];

    // Make sure you end the background task when you no longer need background execution:
    // [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];

    [NSTimer scheduledTimerWithTimeInterval:0.5
                                     target:self
                                   selector:@selector(timerDidFire:)
                                   userInfo:nil
                                    repeats:YES];
}

- (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
}

Notes

  • Apps only get ~ 10 mins (~3 mins as of iOS 7) of background execution - after this the timer will stop firing.
  • As of iOS 7 when the device is locked it will suspend the foreground app almost instantly. The timer will not fire after an iOS 7 app is locked.

Solution 3:

In case you or someone else is looking for how to run the NSTimer in the background in Swift, add the following to your App Delegate:

var backgroundUpdateTask: UIBackgroundTaskIdentifier = 0


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    return true
}

func applicationWillResignActive(application: UIApplication) {
    self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
        self.endBackgroundUpdateTask()
    })
}

func endBackgroundUpdateTask() {
    UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
    self.backgroundUpdateTask = UIBackgroundTaskInvalid
}

func applicationWillEnterForeground(application: UIApplication) {
    self.endBackgroundUpdateTask()
}

Cheers!