Search code examples
xcodenstimerbackground-process

NSTimer doesn't work in background on device


I'm trying to run some nstimer in background and actually I achieved it on simulator with this code

- (void)start {

    if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {

        if ([[UIDevice currentDevice] isMultitaskingSupported]) {
            UIApplication *application = [UIApplication sharedApplication];

            __block UIBackgroundTaskIdentifier background_task;

            background_task = [application beginBackgroundTaskWithExpirationHandler: ^{
                [application endBackgroundTask:background_task];
                background_task = UIBackgroundTaskInvalid;

            }];

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

                [application endBackgroundTask: background_task];
                background_task = UIBackgroundTaskInvalid;

                if (self.timer == nil) self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(countdown) userInfo:nil repeats:YES];
                [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
                [[NSRunLoop currentRunLoop] run];
            });
        }
    }

}

The problem is that on real device it doesn't work, maybe should I enable some background modes?


Solution

  • A couple of issues:

    1. NSTimer needs a run loop, cannot be scheduled from a background global worker thread (unless you schedule it on mainRunLoop, or manually create a run loop on some background thread).

      If you need your countdown method to be run in the background thread, you can use dispatch timers. Or, easier, just schedule the timer on the main run loop.

    2. You're creating background task, but immediately terminating it when you create your countdown repeating timer. You presumably do not want to terminate the background task until your finite length task is done.

      Note, if you run the app through the debugger in Xcode, that can affect the behavior of background tasks, app termination, etc. (the process of being linked to the debugger keeps the app alive). Make sure you test the app on a device, not by pressing the "go" button in Xcode, but make sure the app is terminated and then manually tap on the app icon on the device's "Home" screen.

    3. How long is your countdown timer? This background task mechanism is designed for finite-length tasks (e.g. I think it currently gives you 3 minutes or so).

    4. If your background timer is potentially of some longer duration, you might just want to schedule a local notification to let the user know when the specified timer interval has passed.

      Note, this may be a more judicious model for running a "timer" in the background, regardless. Rather than having app spin in the background (taking up CPU cycles and battery), just specify when the local notification should be delivered to the user and bypass the background task pattern altogether.