5

I am looking into using deferred location updates for an iOS activity tracker, which allows location services in background. I've implemented the suggested code snippets (see below). In Xcode debugging, deferred locations attempt to start a few times until location data comes in at about 1 per second. After that, it claims to succeed in starting deferrals, and the callback for the finish trigger also succeeds after the specified time period expires. However during the time, the location handler still runs once per second. I've read that this is because the phone hasn't deemed itself ready to enter the background, and that testing in Xcode does this. Note, AppDelegate's "didEnterBackground" eventhandler got called immediately when turning off the screen, and resumed when reopening app.

I ran the same code with the phone disconnected as another test, near the window with GPS, screen off, or switching to entirely different apps, and it still never actually defers the updates. I can tell because the networked update still comes in once every 30 seconds, instead of the interval of 120 seconds which is desired in the code sample below.

What else is needed to actually get deferrals to work, since there is no error occurring in starting them and they do get their finish callback? Why do location updates continue at 1 per second even when the app goes to background?

Iphone 5s, IOS 7.1.1

// .h file (partial)
@interface MotionTracker : NSObject<CLLocationManagerDelegate, UIAccelerometerDelegate> 

@property (strong, nonatomic) CLLocationManager *locationManager;

@end

// .m file (parial)
- (id) init {
    if(self = [super init]){
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
        _locationManager.distanceFilter = kCLDistanceFilterNone; 
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;

        // if set to YES (default), app stops logging location at some point and doesn't resume in any timely fashion, all data points lost in between
        _locationManager.pausesLocationUpdatesAutomatically = NO;

        _locationManager.activityType = CLActivityTypeFitness; 
    }
    return self;
}

// called early in program after login confirmed
- (void) startCollectingLocation {
    [_locationManager startUpdatingLocation];
}    

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray *)locations {

    // logs to file when device is not in debug
    // always returns 1
    NSLog(@"Location update count: %d",[locations count]);

    // some code here to handle location updates
    // - collect key location day in NSDictionary
    // - every N seconds send Network call to server to save (have tried 30 seconds, 15 minutes, 30 minute network intervals). Have also tried turning off network calls completely.


    // deferred updates starter
    if (!self.deferringUpdates) {
        if([CLLocationManager deferredLocationUpdatesAvailable]){
            [_locationManager allowDeferredLocationUpdatesUntilTraveled:500 timeout:(NSTimeInterval)120]; // (have also tried large numbers, and "Infinite"
            self.deferringUpdates = YES;
            NSLog(@"Deferred updates start");
        } else {
            NSLog(@"Deferred updates not available");
        }
    }
}

- (void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {
    if(!error){
        _deferringUpdates = NO;
        NSLog(@"Deferred updates: finished");
    } else {
        _deferringUpdates = NO;
        NSLog(@"Deferred updates: %@", [error localizedDescription]);
    }
}
Miro
  • 5,307
  • 2
  • 39
  • 64
  • Have you done everything listed in [this answer](http://stackoverflow.com/a/14509263/1693173)? I was able to get it to work most of the time following that list. – progrmr Apr 29 '14 at 04:18
  • Yes, I've done everything on that list and checked it several times. Most of what is there seems pretty basic. – Miro Apr 29 '14 at 15:18
  • One of the keys is to wait for location updates to be coming in regularly before enabling deferred updates. Even then the system won't enable it all the time, it depends on what else is going on the device. If it ever works for you, even just once, then you're doing it right, it's just up to the system at that point to decide when to let the CPU sleep. – progrmr Apr 29 '14 at 16:28
  • It has never worked. I initially called it immediately and it would give error codes until GPS kicked in fully, then run without errors but still not defer. I then waited up to 15 seconds before attempting its first call, no longer getting errorcodes, but still with no luck. I've left it running on the debugger for hours in background with screen off. I've run it standalone on device, and done offline logging, and let it run overnight, essentially until the battery drained from all the location updates every second for 12+ hrs. I'm not sure what else could be needed to let the CPU sleep. – Miro Apr 30 '14 at 06:05
  • I've also killed all other apps from running on the device, disabled all other background mode activity on the device, and left it so only my short test program was running in the background. I've updated the test code to be a stand alone app that does just location collecting and this snippet even, so no other code could interfere. I've tested this on two devices, IPhone5 and IPhone5S. Still no deferral. – Miro Apr 30 '14 at 06:13
  • 1
    The device never sleeps when hooked up to the debugger. – progrmr Apr 30 '14 at 14:15
  • The way to tell if deferred location updates is actually working is to check the count on the location array passed to didUpdateLocations:. If the count is >= 2 then you have an array of deferred updates. You don't show enough of your code to determine what's really happening. – progrmr Apr 30 '14 at 15:47
  • Also, that 120 second timeout is a *maximum* time to defer. It can often be less, you can't rely on it just giving you one array of locations every 120 seconds. – progrmr Apr 30 '14 at 15:48
  • What is the difference between the app entering the background, the device "sleeping", technically? – Miro Apr 30 '14 at 16:36
  • I've tested both debugger and standalone for the count. The app sends the location data to the server on regular intervals, so for standalone, I monitor the data points and timestamps. If deferral worked, they should all be received at once after the deferral ideally. Could the network request in didUpdateLocations be causing it not to defer? I can adjust the interval as needed if that helped. – Miro Apr 30 '14 at 16:51
  • I've also tested standalone logging simply to a txt file on the device, limiting network output. With deferrals claiming to start+finish right on every interval, it still runs once per second, literally all night with no other usage in this case. No more than 1 location point ever comes into the handler at a time. Not sure what else could be limiting it here. – Miro May 01 '14 at 16:37
  • Most likely there is something wrong with your code in how you set up the location manager, it may not be doing what you think. You might want to include that portion of code in your question. – progrmr May 02 '14 at 00:21
  • I added the code for the location manager initialization as well. – Miro May 02 '14 at 14:09
  • @Miro Did you ever resolve this? I am seeing this same behavior on iOS 8 running on an iPhone 5. – Sir Wellington Oct 09 '14 at 18:13
  • I've never resolved it, and have since given up on deferred updates as an optimization – Miro Oct 09 '14 at 19:22

3 Answers3

6

If the device is connected to a debugger or on a charger, the device will remain powered (not sleep) and therefore will not enter deferred mode. Deferred mode is a power optimization allowing the device to sleep. If the device is not scheduled to sleep for other reasons, enabling deferred mode will not force it to sleep otherwise. Try your test by ensuring no other apps are using location services, and disconnecting it from a charger with the screen off. After running for some time, plug back in and check your logs, you should see that the device slept and deferred updates.

From Apple's allowDeferredLocationUpdatesUntilTraveled:timeout: documentation:

Deferred updates are delivered only when the system enters a low power state. Deferred updates do not occur during debugging because Xcode prevents your app from sleeping and thus prevents the system from entering that low power state.

It is also worth noting that deferred updates are only available when locationManager.desiredAccuracy is set to kCLLocationAccuracyBest OR kCLLocationAccuracyBest; locationManager.distanceFilter must also be set to kCLDistanceFilterNone.

From Apple's documentation:

...the location manager allows deferred updates only when GPS hardware is available on the device and when the desired accuracy is set to kCLLocationAccuracyBest or kCLLocationAccuracyBestForNavigation.

and

...the distanceFilter property of the location manager must be set to kCLDistanceFilterNone.

Robert Altman
  • 5,355
  • 5
  • 33
  • 51
user4138380
  • 61
  • 1
  • 1
  • It is also worth noting that deferred updates are only available when locationManager.desiredAccuracy is set to kCLLocationAccuracyBest OR kCLLocationAccuracyBest?, You clearly didn't mean to write that twice – Gustavo Baiocchi Costa Dec 21 '16 at 10:40
4

I have been struggling with the same issue, and I may have found an answer that solves this problem for many - at least it solves my problem and gets deferred updates working consistently for me. I followed all of the steps in this list and no matter what I did, location updates would not defer. It occurred to me that I might have other apps running that were not allowing the system to sleep, so I killed all other apps in the multitasking tray. I ran my sample app again and ... it worked! But the story doesn't end there. I tried again a little later, and even though there were no other apps running in the multitasking tray I couldn't get location services to defer. Then it occurred to me that I have an app on my phone called "Moves" which manages to keep itself alive even after you manually kill it. I'm not entirely sure how Moves comes magically back to life when you kill it, but it does (perhaps using bluetooth and the app preservation / restoration service). Even though it is alive and tracking your location it doesn't appear in the multitasking tray. I think that only apps that are manually launched appear in the tray - if the OS launches an app it doesn't appear in the tray. But I digress ... I was able to get deferred location services to work consistently in my app by disallowing Moves to use location services. When I did, Moves complained even though it wasn't in multitasking tray. It seems that if another app is using location services (and not deferring) your app won't defer either.

Community
  • 1
  • 1
Colin Phillips
  • 1,001
  • 1
  • 10
  • 11
  • I've got Moves installed as well. I will check this later. This sounds consistent with my experience. It has never worked on my phone, however a coworker has it working all the time with the same exact app release.... – Miro Jun 26 '14 at 16:47
  • Does Defering work for IPad? I get constantly kCLErrorDeferredFailed in my Ipad Simulator, even though I have added external GPX files – Debanjan Chakraborty Nov 17 '14 at 10:28
  • How many locations are delivered in the deferred update? And is the delegate called before or after resumeFromBackground and didActivate? Thanks – malhal Jul 24 '15 at 10:07
1

Hi recently with the iOS 9 GM seed version out,I have seen location update(allowDeferredLocationUpdatesUntilTraveled:timeout:) not getting deferred.The same code used to work in iOS 8.4 and below versions.Its draining my device's battery by a huge margin.

Is there anything we need to explicitly set or mention for iOS 9?Didn't find anything from Apple documentation

Here is the code that I implemented.

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {

if (!self.deferringUpdates) {

[self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:30]; self.deferringUpdates = YES; } }

-(void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error { // Stop deferring updates self.deferringUpdates = NO;

}

I also set the allowsBackgroundLocationUpdates property but even that didn't help. self.locationManager.allowsBackgroundLocationUpdates=YES;

In iOS 9 and later, regardless of deployment target, you must also set the allowsBackgroundLocationUpdatesproperty of the location manager object to YES in order to receive background location updates. By default, this property is NO, and it should remain this way until a time when your app actively requires background location updates.

https://developer.apple.com/library/prerelease/ios/documentation/Performance/Conceptual/EnergyGuide-iOS/LocationBestPractices.html

Please let me know what additional I need to make

Thanks

IOSDevops
  • 111
  • 10
  • Can you please tell me- deferringUpdates is bydefault value or we have to declare this property as a Boolean?? – Kirti Sep 20 '16 at 09:24
  • No we declare this property to keep track of deferred mode the device enters into. – IOSDevops Nov 17 '16 at 09:03