15

I have background mode on for location services and aiming to send out location (latitude and longitude) to the server every 30 minutes. For now I am printing the same in the console. It seems to work for a while but I am wondering how do I work with NSTimer in this case. And from where should I be calling it?

import UIKit
import CoreLocation

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {

    var window: UIWindow?
    var locationManager = CLLocationManager()

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

        self.locationManager.delegate = self
        self.locationManager.startUpdatingLocation() // I know i should be using signification location option here. this is just for testing now.
    }

    func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
        self.sendBackgroundLocationToServer(newLocation);
    }

    func sendBackgroundLocationToServer(location: CLLocation) {
        var bgTask = UIBackgroundTaskIdentifier()
        bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
            UIApplication.sharedApplication().endBackgroundTask(bgTask)
        }

        println(location.coordinate.latitude)

        if (bgTask != UIBackgroundTaskInvalid)
        {
            UIApplication.sharedApplication().endBackgroundTask(bgTask);
            bgTask = UIBackgroundTaskInvalid;
        }
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
        application.beginBackgroundTaskWithExpirationHandler{}
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
        application.beginBackgroundTaskWithExpirationHandler{}
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        application.beginBackgroundTaskWithExpirationHandler{}
    }


}

Maybe calling application.beginBackgroundTaskWithExpirationHandler{} is a bad idea? What options do I go with here?

Cœur
  • 37,241
  • 25
  • 195
  • 267
psharma
  • 1,279
  • 2
  • 19
  • 47
  • As an aside, the delegate method `didUpdateToLocation:fromLocation` is deprecated - refer to the CLLocationManagerDelegate docs – Paulw11 Oct 09 '14 at 04:44

3 Answers3

10

The idea of beginBackgroundTask... is to start a finite length task so that if the user leaves the app, it will keep running in the background task for some short, finite period of time (3 minutes, I believe). And before the time runs out, you have to call endBackgroundTask or else the app will be summarily terminated.

So, sadly, the background task mechanism is not really suited for your desired intent. There are, though, a narrow set of special background modes designed for continued background operation outside a narrow set of functions (VOIP, audio, etc.). For more information, see the Implementing Long-Running Tasks section of the App Programming Guide for iOS: Background Execution.

Now, one of those background modes is for a "location" service. So, if that is a central feature of your app, essential for proper function, then you can register for the location background mode, and your app will continue to run in the background. From there, you can monitor for location updates, and if a sufficient amount of time has elapsed, trigger some process. But if this background location mode is not an essential feature of your app, Apple is likely to reject your app for requesting a background mode that it doesn't need.


By the way, you should be aware that starting standard location services may drain the device battery. You might consider using the battery efficient "significant change" location service. This also has the virtue of automatically waking your app every time the user moves some significant distance (e.g. measured in km; I believe it's triggered by moving to different cell tower).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hey @Rob, thanks for the answer. Yes I have enbaled background mode for location and indeed location is the center piece for the application. Also this application is only for internal user / ad-hoc. Having said that, could you give me an example or shed some light on how would I go about using NSTimer here? – psharma Oct 09 '14 at 19:09
  • Hey @Rob, thanks for the answer. This helps a lot. And yes I realize this drains battery but since its for a ad-hoc usage, its ok:). Plus yes I will be using significant changes. Right now what I have really helps me testing on the logs. Thanks again. – psharma Oct 10 '14 at 00:42
  • @Rob, the method you have described is working for me in simulator but not on device. Any idea of what could have gone wrong? – nbbk May 18 '17 at 13:17
  • I've set the location updates in background mode capabilities. I've added 'Privacy-Location Always..' in info.plist. Everything works just fine in simulator. But in actual device, the handleTimer function never gets triggered. I just press the home button to enter background. I doubt if the Timers actually work on devices. Have a look at this http://stackoverflow.com/questions/30630687/background-task-works-on-simulator-only. Is the answer for this question wrong? [PS-Location is the center piece for my application] – nbbk May 19 '17 at 01:38
  • Correct, timers will not work. You need to have location services running and upon notification of a location update, check yourself whether the enough time has elapsed. And if you don't want to suffer crippling battery consumption, you may be limited to the less accurate significant change service. I've edited my answer accordingly. – Rob May 21 '17 at 08:09
8

Couple of notes, since we've been fighting the location issues and struggling with background applications that don't seem to wake us up.

  1. NSTimer is only good as long as your application is not suspended. Sure, it's in the background, but onlyinasmuchas it is capable of receiving notifications from the iOS (APN, location, ...). Eventually, your timer WILL stop firing while you run in the BG. So, you are relying on one of the BG modes to kick you.

  2. If you ARE using CLLocationManager.startUpdatingLocation, you're going to notice that you quit getting those after a bit. Especially when the phone relaxes and tries to sleep. This is because of a poorly documented feature called CLLocationManager.pausesLocationUpdatesAutomatically ... which decides to stop sending those when set to "true" (the DEFAULT setting). You can set this to "false", and you'll continue to get your position updates. Which will pretty much make sure your app does what you are wanting.

  3. As noted above, you must make sure when you get your locationManager.didUpdateLocations callback that you begin a backgroundTask to keep your application working while you process that information and send your network data. But you seem to understand that.

  4. Just waking up long enough to "record" a location update isn't so bad. Just make sure you don't spawn your network code unless you've clearly hit your 30 min expectation.

GNewc
  • 445
  • 4
  • 4
3

try this:

  • Make a singleton for Location Service. Registered it with a NSNotification
  • In AppDelegate's willEnterBackGround, you send a notification via NSNotificationCenter

then, your singleton will start updatingLocation and send it to server when location's data received.

Pham Hoan
  • 2,107
  • 2
  • 20
  • 34