GameBoy
GameBoy

Reputation: 41

iOS9 Objective-C Run GPS in the background with Timer

here's yet another question on having GPS run in the background. Apparently, there are specific conditions where my app can run (seemingly indefinitely) in the background, and sometimes it will terminate after 3 minutes. My iPad is currently running on 9.3.2.

TL;DR: Did the necessary code and project configurations, but does not always run in background for longer than 3 minutes. Why?

My post will be lengthy. I have tried to keep it concise.

My app will need to send the GPS locations at every interval: 60 seconds if the user is "Logged in," and 900 seconds (15 minutes) if the user is "Logged out." I need these requirements; The program requirements are not decided by me. This app is not published to the app store either.

I understand that I need to add this in my plist:

<key>NSLocationAlwaysUsageDescription</key>
<string>Location information from this device is required for tracking purposes.</string>

Under the project capabilities, I have Background Modes -> Location updates selected, and also in the plist:

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

In my AppDelegate, I also have these 2 (located inside application:didFinishLaunchingWithOptions:):

if ([locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
{
    [locationManager requestAlwaysAuthorization];
    NSLog(@"===>locationManager responds to requestAlwaysAuthorization<===");
    }
    else
{
    NSLog(@"===>locationManager not responding to requestAlwaysAuthorization! :(<===");
}

if([locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
{
    [locationManager setAllowsBackgroundLocationUpdates:YES];
    NSLog(@"===>locationManager responds to setAllowsBackgroundLocationUpdates<===");
}
else
{
     NSLog(@"===>locationManager not responding to setAllowsBackgroundLocationUpdates! :(<===");
}

In my applicationDidEnterBackground, I have the beginBackgroundTaskWithExpirationHandler function as follows:

bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
    NSLog(@"ending background task. Background time remaining: %f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
    //Do I need to uncomment the 2 lines below?
    //[[UIApplication sharedApplication] endBackgroundTask:bgTask];
    //bgTask = UIBackgroundTaskInvalid;
}];

So now, my biggest question is: What have I still not done yet/done wrong, that doesn't allow for background execution?

  1. My app has a csv log file (locally) that records what GPS coordinates are being sent over to a tracking web service. In my logs, I also record events like "App to Background" and "App to Foreground" so when I retrieve the logs, I will know if it terminated after 3 minutes. Also, I log down the [[UIApplication sharedApplication] backgroundTimeRemaining] remaining time.
  2. I have CLLocationManager *locationManager;. When my app goes into the background, I make [locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers]; and then [locationManager startUpdatingLocation]; just to keep the app running in the background. A timer changes [locationManager setDesiredAccuracy:kCLLocationAccuracyBest]; when at the interval as stated above, and change it back to [locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers] after that. All this is when the app is in the background, [locationManager stopUpdatingLocation]; only if the app is in the foreground. Do I actually need to keep the locationManager running so the app remains active in the background?
  3. The strange part is as follows: I realised that when I send my app to the background by: (a.) Home button (b.) power button (c.) closing the iPad case, (a.) will always have the app running in the background, while (b.) and (c.) may allow the app running for more than 3 minutes ??!? Is there really a difference? Because I know the delegate functions applicationWillResignActive and applicationDidEnterBackground will be called nonetheless. Whether or not it runs more than 3 minutes seems non-deterministic.
  4. Did I put beginBackgroundTaskWithExpirationHandler in the correct place?
  5. Did I put [locationManager setAllowsBackgroundLocationUpdates:YES]; in the correct place (currently in the AppDelegaate)?
  6. Running the app with xcode debugger will always work perfect, but running it without (i.e. actual use conditions) might not let the app run. With the debugger running, I NSLog the background time remaining. It might show something like 179.348015 seconds but after 3 minutes the app will continue to run. Is this issue faced by any others?

Help is very much appreciated. o/

Upvotes: 1

Views: 538

Answers (1)

GameBoy
GameBoy

Reputation: 41

Alright, so I have asked this 1 year ago and have not received any responses...

When I first asked this question, my intention was to turn off the GPS once the app has obtained the coordinates. So in pseudo code, the logic should go something like this:

applicationStart()
{
    startTimer();
}

startTimer()
{
    timer repeats: TRUE;
    timer cycle: 60 seconds;
    function to call: getGPS();
}

getGPS()
{
    GPS start;
    retrieve coordinates;
    send coordinates to server;
    GPS stop;
}

By doing this, I intended to save battery power, as GPS is a battery draining feature. However, by doing this, there is no guarantee that the GPS would constantly run in the background. On top of that, as I have mentioned, the results of running such logic is non-deterministic; sometimes it would run, sometimes it would not.

In the end, my current code goes as such (in pseudo code):

applicationStart()
{
    GPS start;
    GPS accuracy 3 kilometers;
    startTimer();
}

startTimer()
{
    timer repeats: TRUE;
    timer cycle: 60 seconds;
    function to call: getGPS();
}

getGPS()
{
    GPS accuracy best;
    retrieve coordinates;
    send coordinates to server;
    GPS accuracy 3 kilometers;
}

Notice that I do not actually turn off the GPS; in lieu of turning it off, I make it run constantly, but changed to [locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers] when I do not need it.

Of course, I am still open to a better solution. At the point of asking this question, I was running iOS9. My above solution works on iOS10 right now, and with iOS11 round the corner, I'm not sure what other changes Apple will be bringing to the API relating to GPS.

Battery consumption? Well, so far so good; My users are not complaining.

Upvotes: 1

Related Questions