1

I have an app that posts data to a server. This data is only posted if the user is within a certain distance to a location. I regisered for background location updates in hope that would allow me to keep the background going. But after about 17-18 minutes, the background stops executing.

I thought it might have been because of the locationManager.pauseslocationupdatesautomatically. But even when I set that to false, the app still terminates at around 17 minutes. Here is the code for my app delegate.

//
//  BAAppDelegate.m
//  Beacon App
//
//  Created by Huy Ly on 2/10/13.
//  Copyright (c) 2013 Placesign. All rights reserved.
//

#import "BAAppDelegate.h"

@implementation BAAppDelegate
@synthesize backgroundAnnouncementRevision, backgroundAnnouncementText, backgroundOfferDescription, backgroundOfferName, backgroundOfferPrice, backgroundOfferRevision, isAnnouncing, isOffering, locationManager, targetLocation, currentLocation, beaconTimer;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    NSLog(@"Application will resign active");
    // 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.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    NSLog(@"Application entered background");
    // 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.
    locationManager.distanceFilter = kCLDistanceFilterNone;
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    [locationManager setPausesLocationUpdatesAutomatically:YES];
    [locationManager startUpdatingLocation];
    NSLog(@"Starting timer for posting in background");
    /*
    //Runs the Timer on a background task main thread
    UIApplication *app = [UIApplication sharedApplication];


    //create new uiBackgroundTask
    __block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];*/

    //and create new timer with async call:
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //run function methodRunAfterBackground
        beaconTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(sendBeacon) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:beaconTimer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    });

}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    NSLog(@"Application entered foreground");

    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    [beaconTimer invalidate];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    NSLog(@"Application became active");

    // 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.
    [beaconTimer invalidate];
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    NSLog(@"Application will terminate");
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    [beaconTimer invalidate];
}


-(void) sendBeacon{
    NSLog(@"Beacon Background Send Started");

    NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
    if ([[NSUserDefaults standardUserDefaults]valueForKey:@"PlaceID"]== nil) {
        isAnnouncing = FALSE;
        isOffering   = FALSE;
    }

    //Do a single distance check to see if user is still within bounds
    //Get the current location
    [locationManager startUpdatingLocation];

    //Compare current location with target location
    CLLocationDistance distance = [currentLocation distanceFromLocation:targetLocation];
    distance=distance/1000;

    //If user is within location boundary, posts to server
    if (distance < 0.1 || true) {
        NSLog(@"Background Sent");
        //Set Up the NSURL
        NSString *urlString = [standardUserDefaults valueForKey:@"statusUpdate"];
        NSURL *url          = [NSURL URLWithString:urlString];
        NSString *jsonString = [[NSString alloc] initWithFormat:@"{\"Announcement\":{\"Text\":\"%@\",\"ElementContext\":{\"Revision\":%@,\"Source\":{\"ID\":0,\"Type\":0}}},\"Offer\":{\"Description\":\"%@\",\"ElementContext\":{\"Revision\":%@,\"Source\":{\"ID\":0,\"Type\":0}},\"Name\":\"%@\",\"Price\":%@},\"OpStatus\":{\"ElementContext\":{\"Revision\":0,\"Source\":{\"ID\":0,\"Type\":0}},\"Value\":0},\"PlaceID\":%@,\"ResourcesOnPremise\":[{\"ElementContext\":{\"Revision\":0,\"Source\":{\"ID\":%@,\"Type\":1}},\"OnPremiseStatus\":2,\"Resource\":{\"ID\":%@,\"Type\":1}}],\"SignalSources\":[{\"LastSignal\":0,\"Source\":{\"ID\":0,\"Type\":0}}]}", backgroundAnnouncementText, backgroundAnnouncementRevision, backgroundOfferDescription, backgroundOfferRevision, backgroundOfferName, backgroundOfferPrice, [[NSUserDefaults standardUserDefaults]valueForKey:@"PlaceID"], [[NSUserDefaults standardUserDefaults]valueForKey:@"UserID"], [[NSUserDefaults standardUserDefaults]valueForKey:@"UserID"]];
        NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];

        //setup the request
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
        [request setHTTPMethod:@"POST"];
        [request setValue:[NSString stringWithFormat:@"%d", [jsonString length]] forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:jsonData];
        NSURLConnection *requestConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

        [requestConnection start];

    }
    else{
    }
}

-(void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
    NSLog(@"Location Manager failed with error %@", error);
}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    currentLocation = [locations lastObject];
    NSLog(@"Manager did update location");
}

-(void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager{
    NSLog(@"Location Manager Paused");
}

-(void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager{
    NSLog(@"Location Manager Resumed");
}

@end
mfaani
  • 33,269
  • 19
  • 164
  • 293

1 Answers1

2

You shouldn't expect to run indefinitely. If you're a location app, then you should expect to be called when the device moves. But if the device isn't moving, then there's no reason for the OS to call you, and it won't.

You need to design your app so that it uses the minimum battery to achieve the user-desired behavior. To that end, if you have a boundary that you care about, you should set up a location region, and you will be woken up whenever the device moves into or out of that region. This is much, much cheaper than constantly watching the GPS.

If the user wants you to record every small movement, then you can set the location manager as you have (with kCLLocationAccuracyBest), but you still will only get called when the device moves. Since this will cause the maximum battery drain, make sure it's the only way to achieve the user's goal.

There is, by design, no way to request "Indefinite Background Time."

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Do you mean do `locationManager.startMonitoringForRegion(region)` inside the `locationManagerDidPauseLocationUpdates` callback? **OR** you mean do something like `UNLocationNotificationTrigger(region: region, repeats: false)`?? Aren't you no longer allowed to do anything related to locations? I guess you're saying you're allowed and then eventually catch the launch using `UIApplicationLaunchOptionsLocationKey` like the answer provided [here](https://stackoverflow.com/questions/27742677/how-to-get-location-updates-for-ios-7-and-8-even-when-the-app-is-suspended/27742678#27742678). Is that right? – mfaani Jul 20 '17 at 19:07