0

I wrote an app the monitors a user's location. Location services are turned on when my view loads as such:

// Create the location manager if this object does not
// already have one.
if (nil == self.locationManager) {
    self.locationManager = [[CLLocationManager alloc] init];
}

self.locationManager.delegate = self;

// Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
    [self.locationManager requestAlwaysAuthorization];
}

[self.locationManager startMonitoringSignificantLocationChanges];
NSLog(@"Started monitoring significant location changes");

If I terminate the app while its active, the location services stop. Here is the code I wrote to stop the location services in the AppDelegate.m:

- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also            
       applicationDidEnterBackground:.
    // Saves changes in the application's managed object context before the application terminates.
    NSLog(@"entered terminate in delegate");
    [myController.locationManager stopUpdatingLocation];
    [myController.locationManager stopMonitoringSignificantLocationChanges];
    myController.locationManager = nil;
    [self saveContext];
}

I ran into a problem such that if my app is already in the background, the above method is not called and as such I could not turn off location services. To work around this, I found this solution which I tried:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // 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.

    UIApplication *app = [UIApplication sharedApplication];
    if ([app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
        self.bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
            // Synchronize the cleanup call on the main thread in case
            // the task actually finishes at around the same time.
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.bgTask != UIBackgroundTaskInvalid)
                {
                    NSLog(@"Marking bgTask as Invalid when we entered background");
                    [app endBackgroundTask:self.bgTask];
                    self.bgTask = UIBackgroundTaskInvalid;
                }
            });
        }];
    }
}

So the above solution works if my app was in the background. However, I noticed that if leave my app running in the background for a long time, more than five minutes, the expiration handler kicks in. So then if I terminate the app without bringing it to the foreground. The location services icon still appears on the phone. I have to restart the app or bring it to the foreground first and then terminate it for the code that disables location services kicks in.

If I remove these two lines:

         [app endBackgroundTask:self.bgTask];
         self.bgTask = UIBackgroundTaskInvalid;

Then stopping location services works find after five minutes while the debugger is attached. If I leave it running longer in the background then the terminate code never kicks in. Is it because I am not changing locations or does the app eventually die?

So my question is, is there another way to make sure that the app properly stops location service monitoring if its been in the background for a while?

Thank you...Amro


Edit, I did more experiments and here are my findings:

While attached to the debugger if I wait 11 minutes from time it enters background mode, the method willTerminate gets called:

2015-01-13 08:52:45.935 [441:37074] @@@AMRO--->applicationWillResignActive entered

2015-01-13 08:52:46.642 [441:37074] @@@AMRO--->Entered background mode

2015-01-13 08:55:42.697 [441:37074] @@@AMRO--->beginBackgroundTaskWithExpirationHandler called

2015-01-13 09:03:26.265 [441:37074] entered terminate in delegate

If I try this without debugger, and only wait four minutes, the terminate function does not get called, I don't have to wait the whole 11 minutes.

Amro Younes
  • 1,261
  • 2
  • 16
  • 34
  • check out this post http://stackoverflow.com/questions/24778492/stop-location-updates-when-app-terminate/24778607#24778607, sounds like a similar issue, also, have you turned on location updates in background modes? – Guy S Jan 13 '15 at 07:28
  • @GuyS Yes I need location tracking while in background mode. Per my question and implementation details, the solution in stack overflow link you provided does not work if the background app has been in the background for more than 5 minutes – Amro Younes Jan 13 '15 at 14:21
  • are you sure it's 5 minutes and not 3 minutes? in iOS 7 background tasks were stopped after 3 minutes, i'm not sure if it was changed in ios 8 ( i don't think so). Anyway, I used this post http://stackoverflow.com/questions/18901583/start-location-manager-in-ios-7-from-background-task to get location updates in the background, I just checked and it's getting location updates in the background for 20 minutes now... just make sure to add requestAlwaysAuthorization and the plist string NSLocationAlwaysUsageDescription. hope this helps – Guy S Jan 13 '15 at 15:30
  • You are right, I timed it: 2015-01-13 08:46:38.468 @@@AMRO--->Entered background mode 2015-01-13 08:49:34.472 @@@AMRO---> beginBackgroundTaskWithExpirationHandler called. The issue for me is not the location services. Its terminating the app and being able to shut down location services gracefully. With regards to requesting authorization and programming the PLIST that is all implemented. So basically if I leave it well beyond three minutes, and then try to kill the app, the applicationWillTerminate method never gets called. – Amro Younes Jan 13 '15 at 16:56

1 Answers1

2

From the Apple's documentation:

App Termination

Apps must be prepared for termination to happen at any time and should not wait to save user data or perform other critical tasks. System-initiated termination is a normal part of an app’s life cycle. The system usually terminates apps so that it can reclaim memory and make room for other apps being launched by the user, but the system may also terminate apps that are misbehaving or not responding to events in a timely manner.

Suspended apps receive no notification when they are terminated; the system kills the process and reclaims the corresponding memory. If an app is currently running in the background and not suspended, the system calls the applicationWillTerminate: of its app delegate prior to termination. The system does not call this method when the device reboots.

In addition to the system terminating your app, the user can terminate your app explicitly using the multitasking UI. User-initiated termination has the same effect as terminating a suspended app. The app’s process is killed and no notification is sent to the app.

https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html#//apple_ref/doc/uid/TP40007072-CH2-SW1

About significantChangesLocation:

If you leave the significant-change location service running and your iOS app is subsequently suspended or terminated, the service automatically wakes up your app when new location data arrives.

https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html

If you don't want your app waked up by significantChangeLocation, you could call stopMonitoringSignificantLocationChanges when backgroundTimeRemaining is about to expire.

Att.

Jaumefm17
  • 126
  • 9
  • Thanks for the response, from my testing, this is what I gathered. I took my app for a drive. Put it in the background, and every time a location update was triggered, I sent out a notification so I could see it in the UI. So this proves that even though the app got suspended, I was still getting the notifications. This is good in terms of location updates, but users will be upset if the location pointer remains up all the time. I think I will have to solve this problem by using the backgroundExpirationHandler and a combination of timers to stop and start the location services – Amro Younes Jan 14 '15 at 05:48