Robert Constantinescu Robert Constantinescu - 1 month ago 27x
iOS Question

iOS Swift - Reload location function with NSTimer for app background doesn't work

I've got a problem with location services. I can't set up a function that updates my location coordinates in the background by a NSTimer. Here is my code from appDelegate:

var locationManager = CLLocationManager()

func applicationDidEnterBackground(application: UIApplication) {

self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest

self.theTimer = NSTimer(fireDate: NSDate(), interval: 40, target: self, selector: "handleTimer", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.theTimer, forMode: NSDefaultRunLoopMode)


func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locValue:CLLocationCoordinate2D = manager.location.coordinate
println("dinBack = \(locValue.latitude) \(locValue.longitude)")

func handleTimer(){



PS. - Of course that i've imported corelocation.
- When I get back into the app, the console prints what should have printed in the background.


You can not make an NSTimer work like this while your application is in the background. NSTimer's are not "real-time mechanisms". From the official documentation:

Timers work in conjunction with run loops. To use a timer effectively, you should be aware of how run loops operate—see NSRunLoop and Threading Programming Guide. Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.

A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time.

Emphasis mine.

The important take away from this is that while your application is in the background, any run loop that your timer would have been scheduled on is not actively running.

As soon as your app returns to the foreground, this run loop fires back up, sees that your timer is overdue, and sends the message to the selector.

With iOS 7 and forward, if you want to perform operations in the background, you can tell the OS that you want to perform "background fetches".

To set this up, we must first tell the OS how frequently we want to fetch data, so in didFinishLaunching..., add the following method:

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

We can pass any time interval here (for example, if we only want to check once a day). The value we pass in is only defining a minimum amount of time that should pass between checks, however. There is no way to tell the OS a maximum amount of time between checks.

Now, we must implement the method that actually gets called when the OS gives us an opportunity to do background work:

func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
    // do background work

We can do whatever we want within this method. There are two catches, however.

  1. This method is called while our app is in the background. The OS limits us to (I believe) thirty seconds. After thirty seconds, our time is up.
  2. We must call the completionHandler() (or the OS will think we used all of our time).

The completionHandler that gets passed in takes an enum, UIBackgroundFetchResult. We should pass it either .Failed, .NewData, or .NoData, depending upon what our actual results were (this approach is typically used for checking a server for fresh data).

So, our method might look like this:

func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
    // do stuff
    if let _ = error {
    } else if results.count > 0 {
    } else {

Keep in mind, we have absolutely zero control over how frequently the OS will actually let us run this code in the background. The OS uses several metrics in order to optimize the user's experience.

I think if your app reports .Failed to the completion handler, the OS might give you a second chance soon, however if you're abusing .Failed, the OS could probably blacklist your application from using background fetches (and Apple could deny your app).

If your app isn't reporting .NewData, the OS will let your app do background work less often. I'm not saying this because I recommend that you just always report .NewData. You should definitely report accurately. The OS is very smart about scheduling work. If you're passing .NewData when there isn't new data, the OS will let your app work more often than it may need to, which will drain the user's battery quicker (and may lead to them uninstalling your app altogether).

There are other metrics involved in when your app gets to do background work however. The OS is very unlikely to let any app do background work while the user is actively using their device, and it is more likely to let apps do background work while the user is not using their device. Additionally, OS is more likely to do background work while it is on WiFi and while it is plugged into a charger of some sort.

The OS will also look at how regularly the user uses your app, or when they regularly use it. If the user uses your app every day at 6pm, and never at any other time, it's most likely that your app will always get an opportunity to do background work between 5:30pm and 6pm (just before the user will use the app) and never during any other part of the day. If the user very rarely uses your app, it may be days, weeks, or months between opportunities to work in the background.