2

I am trying to defer location updates in ios 11 in order to conserve power. Default seems to be updating as fast as possible (ie every second), so I want to defer the updates or doing something else clever to make a power efficient application.

When implementing CLLocationManager and setting up like this:

    _locationManager = [[CLLocationManager alloc] init];
    _locationManager.delegate = _sharedInstance = [[LocationManager alloc] init];
    [_locationManager requestAlwaysAuthorization];
    _locationManager.allowsBackgroundLocationUpdates = true;
    _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    _locationManager.distanceFilter = 0;
    _locationManager.pausesLocationUpdatesAutomatically = NO;
    _locationManager.activityType = CLActivityTypeFitness; 

[CLLocationManager deferredLocationUpdatesAvailable] returns false in didUpdateLocations and thus I get error 11 when trying to defer using the method allowDeferredLocationUpdatesUntilTraveled.

These questions (here and here) indicates that deferring locations updates are no longer supported.

Anyhow, how do you then make ios location applications power efficient when you want updates every x (e.g. 15) seconds, the best (ie gps) accuracy and working/running always in the background?

Dag Vegar
  • 31
  • 6
  • Essentially if you want continuous GPS-based (ie highly accurate) location then you are going to use a lot of power. It is the GPS receiver itself rather than the CPU processing the updates that consumes the power, which, I suspect, is why deferring location updates is no longer supported; it was pointless in terms of saving power. The only way you can use less power is to use a less accurate location service. – Paulw11 Jun 03 '18 at 12:50
  • @Paulw11, thanks for the comment. An accurate position every minute should consume less power than updating every second, right? (So why dont they just turn off the GPS receiver meanwhile deferring or making some other method that works the similar way?) Anyone having experience with just duty cycling GPS on/off as an alternative for saving power? Which callback will run in the background in order to prepare the next duty cycle? – Dag Vegar Jun 03 '18 at 13:29
  • You can conserve power: By setting a coarse `distanceFilter` value. By not requiring overly high accuracy. By being correct about the `activityType` and allowing updates to pause. By using location monitoring instead of background updates. – matt Jun 03 '18 at 17:56
  • @matt, thanks. By testing, setting the distanceFilter saves power indeed. – Dag Vegar Jun 21 '18 at 09:13
  • Deferred locations are now officially deprecated (iOS 13). So Apple is admitting that they don't work. – matt Oct 17 '19 at 12:00

1 Answers1

1

This answer might be a little off, because it is actually not possible anymore to use the deffering functionality even the doc has not been removed. But the intention was to switch on/off the location in intervals to avoid having it on all the time to conserve power.

To getting close to turning on/off completely, one might togge between high and low accuracy, this will save power for sure. (high accuracy typically <10m will use GPS module, low accuracy >500m will typically use AGPS.

First, start an NSTimer with the interval you want, then start updating with high accuracy. When receiving a location in didUpdateLocations, change from high accuracy to low accuracy. The callback of NSTimer requests a new high accuracy location update (ie calls setupHighAccuracy). The timer thus sets the frequency of the high accuracy updates. This will also work in the background because the location service is still running. If stopping updates, the app will stop in the background. When stopping and at once starting the location manager again, it will immediately call didUpdateLocation, but check the timestamp, the location may be buffered.

Showing some snippets below to get going. Production code will need more filtering and testing:

@property (retain, nonatomic) NSTimer *atimer;

static BOOL _lowAccuracy;  

- (void)timerCallback {
    [self setupHighAccuracy];
}

- (void)setupTimer
{
    if(self.atimer != nil)
    {
        return;
    }

    UIApplication *app = [UIApplication sharedApplication];
    __block UIBackgroundTaskIdentifier bgTaskId =
    [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTaskId];
        bgTaskId = UIBackgroundTaskInvalid;
    }];

    dispatch_async( dispatch_get_main_queue(), ^{

                        self.atimer = [NSTimer scheduledTimerWithTimeInterval:15.0 // consider higher interval
                                    target:self
                                    selector:@selector(timerCallback)
                                    userInfo:nil
                                    repeats:YES];

                    }   

        [app endBackgroundTask:bgTaskId];
        bgTaskId = UIBackgroundTaskInvalid;
    });
}

- (void)setupHighAccuracy
{    
    if(_lowAccuracy)
    {        
        [_locationManager performSelectorOnMainThread:@selector(stopUpdatingLocation) withObject:nil waitUntilDone:YES];
        _locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; // or kCLLocationAccuracyBest, tune yourself
        _locationManager.activityType = CLActivityTypeFitness;
        _locationManager.distanceFilter = 15; // or 0, tune yourself
        _lowAccuracy = false;
        [_locationManager performSelectorOnMainThread:@selector(startUpdatingLocation) withObject:nil waitUntilDone:YES];
    }
}

- (void)setupLowAccuracy
{    
    if(!_lowAccuracy)
    {
        s_initialized = true;            
        [_locationManager performSelectorOnMainThread:@selector(stopUpdatingLocation) withObject:nil waitUntilDone:YES];
        _locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
        _locationManager.activityType = CLActivityTypeFitness;
        _locationManager.distanceFilter = 3000;
        _lowAccuracy = true;
        [_locationManager performSelectorOnMainThread:@selector(startUpdatingLocation) withObject:nil waitUntilDone:YES];
    }
}

- (void)locationManager:(CLLocationManager *)manager
    didUpdateLocations:(NSArray *)locations
{
    CLLocation *location = locations.lastObject;
    if(!_lowAccuracy) {            
        // update system with location here
        [_locationManager.delegate setupLowAccuracy];
        self.s_lastLocation = location;
    }             
}
Dag Vegar
  • 31
  • 6
  • 1
    Considering that deferredUpdates do not work, isn't it a good idea to use distanceFilter of the locationManager to a value like 100/300 if the user's device is supposed to be on the go? – Roboris Jan 03 '19 at 14:26
  • @Roboris, yes, using the distance filter could be a good option as long as continuous updates are not required. Remember that in that case it is the os that decide when to give the next position update. In some cases it is convenient with a new position (with timestamp) at regular intervals. – Dag Vegar Jan 04 '19 at 09:43