3

I am creating navigation based app for iOS 7, for it I am taking users location data, Using CoreLocation framework,

App requirement is to start getting users location in background at particular time, For that I have implemented Silent Pushnotification with didReceiveRemoteNotification fetchCompletionHandler: method,

I have successfully implement this Using Silent Pushnotification & it Call startUpdatingLocation and I am able to get location data in delegate method :

Using This Payload:

{"aps" : {"content-available" : 1},"SilentPush" : "4"}

I have enabled location & remote notification for Background mode:

enter image description here

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
     __block UIBackgroundTaskIdentifier bgTask =0;
    UIApplication  *app = [UIApplication sharedApplication];
     bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [self.locationManager startUpdatingLocation];

 }];

didUpdateLocations

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
        lastLoc=[locations lastObject];
        [logFile addLocObject:[NSString stringWithFormat:@"Loc: %@",lastLoc]];
}

But Problem is :

After Some Seconds delegate method of location class is stops and it will not send any data if device is moving, And when i talking app to foreground it will called 'didFinishLaunhing' method , So i guess os will kill app even Location is updating, in Device Diagnostics & usage i am getting following crash report:

Application Specific Information:
MockUpApp2[390] has active assertions beyond permitted time: 
{(
    <BKProcessAssertion: 0x145ac790> identifier: Called by MockUpApp2, from -[AppDelegate application:didReceiveRemoteNotification:fetchCompletionHandler:] process: MockUpApp2[390] permittedBackgroundDuration: 40.000000 reason: finishTaskAfterBackgroundContentFetching owner pid:390 preventSuspend  preventIdleSleep  preventSuspendOnSleep 
)}

Yesterday I have asked this question now I am able to startlocation manager in background through push notification,

So please anybody can give solution of this problem.

Thank you.

Note: If I am running app in debugging mode, means I run app through XCode & not stoping it or not disconnecting , app will run.. In this case app will not stop by OS.

EDIT 1

As per @Stephen Darlington Answer if i remove all backgroundfatcher like,

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
        [self.locationManager startUpdatingLocation];

}

Now app will not call didUpdateLocations even once, :(

What should i write in this method?

Edit 2

As per Apple Doc say:

If your app is suspended or not running, the system wakes up or launches your app and puts it into the background running state before calling the method.

SO , is app should run in background for long as i enabled location backgound mode ,?

Community
  • 1
  • 1

3 Answers3

3

I don't think background processing on iOS works in the way that you're expecting. It's not like on the Mac or Windows where you can run indefinitely in the background (even with the changes in iOS 7).

So, to directly answer your question: you kick off a task that can continue to run in the background. There are two "buts."

  1. The code block is the expiration handler. It is not the code that you want to run in the background. This code will get execute just before the background task is out of time (so in your case it gets executed shortly before your app is killed). Apple doesn't really document how long that is but it looks as though you're getting about 40 seconds. The code you want to run in the background are the lines after beginBackgroundTaskWithExpirationHandler. When you're done you say endBackgroundTask:
  2. Secondly, location updates work in the background without all the beginBackgroundTaskWithExpirationHandler: stuff. The delegate methods will get called even if the app is in the background

To summarise: remove the beginBackgroundTaskWithExpirationHandler: statement. You only need to add the startUpdatingLocation:.

Stephen Darlington
  • 51,577
  • 12
  • 107
  • 152
  • @stephen: if i remove `beginBackgroundTaskWithExpirationHandler` is os will allow to run continue my app? – Toseef Khilji Feb 07 '14 at 12:08
  • @Virussmca See my first paragraph. Apps do not continue to run in the background -- that's just not how iOS works. However, when your app is in the background, iOS will wake up your app when something interesting happens and call your core location delegates. – Stephen Darlington Feb 07 '14 at 13:30
  • @StephenDarlington: i have edited question, now i have removed beginBackgroundTaskWithExpirationHandler: as you say, but now strtUpdateLocation is not called? –  Feb 07 '14 at 14:22
  • Good catch @StephenDarlington, I totally missed that. Wrong, so wrong. Was never going to work. Optimistic, you were essentially waiting for the backgroundTask to expire and then saying "hey wait, do this task". That is why you only had 40 seconds. Done correctly, you used to get about 10 minutes. Documents now state some number of minutes not necessarily 10. – Dean Davids Feb 07 '14 at 15:32
  • @Optimistic Can you put a break point on the remote notification method and see if it ever gets called? You don't request the background fetch privilege and you never call the fetch completion handler. – Stephen Darlington Feb 07 '14 at 16:12
  • @StephenDarlington: 1stly Sorry for delay in response, Due to Weekend i was enable . and Yes `didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler` is called , and location manager is also start but, only for 40 or 30 seconds, can i extands this? –  Feb 10 '14 at 05:30
  • @Optimistic Assuming everything else is set up correctly, the location manager delegate gets called _even when your app is in the background_. No, you can't extend the 30-40 seconds, but that's not a problem because you don't need to! – Stephen Darlington Feb 10 '14 at 08:31
  • @StephenDarlington: as you suggest i had removed `beginBackgroundTaskWithExpirationHandler` and write only `[self.locationManager startUpdatingLocation];` but still app location delegate is stopped sending location., can you tell me what to write in `didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler` method ? –  Feb 10 '14 at 13:52
  • @Optimistic 1. What happens if you put the `startUpdatingLocation` in a method that it run while the app is running in the foreground? Does it work then? 2. Are you sure that `self.locationManager` is set when your app is launched in the background? – Stephen Darlington Feb 10 '14 at 14:52
  • @StephenDarlington: Yes if i startUpdatingLocation in foreground os will not kill app, app run continuously. But if i have call `startUpdatingLocation` in background when pushnotification come app is run only for 30 - 40 seconds.. and app requirement is to start in background. –  Feb 10 '14 at 15:05
  • @StephenDarlington: if you need , i'll source upload code. –  Feb 10 '14 at 15:07
  • @Optimistic _THAT'S NOT HOW MULTITASKING ON IOS WORKS!_ You can't expect to get more than 30 seconds in the background -- nor do you _need_ more than that. Core Location will _wake up your app_ if it's not in the foreground. – Stephen Darlington Feb 10 '14 at 17:02
  • @Optimistic Starting location updates while in the foreground (and then putting the app in the background) would test whether Apple allows you to start location updates while in the background. I think it would be a reasonable restrict if that were not allowed. – Stephen Darlington Feb 10 '14 at 17:03
  • As per Apple Doc say: `If your app is suspended or not running, the system wakes up or launches your app and puts it into the background running state before calling the method.` SO , is app should run in background for long as i enabled location backgound mode ,? –  Feb 11 '14 at 07:06
  • Sort of. After calling didUpdateLocation: your app will go back to sleep. But this will happen every time there's a location update so this has the same effect as running in the background continually. – Stephen Darlington Feb 11 '14 at 09:12
2

I did quite a bit of work with CLLocation some time ago and I have some comments and suggestion, while maybe not complete solution.

I see you are putting your reaction to the silent notification in beginBackgroundTaskWithExpirationHandler block. I do not think that is correct. I do believe you should use that backgroundTask pattern on the CLLocationDelegate methods, however.

More importantly, there is a good chance Apple will deny you the use of the locations flag for background processes. Take from my experience. I spent a lot of time working out a decent use of location services improving the accuracy of my own process. I even enabled many protections against inordinate battery use and they rejected it without discussion. I made it so it only used background services when the device was plugged into the charger. I appealed and made a good case for the responsible use, to no avail.

You should look up the terms and make sure your use case is included precisely as stated or it will not float.

I still say that you can get what you want without background services. Using significant location changes and region monitoring your app will not need anything but to save latest known location and respond to notifications.

I would think of a scheme something like this:

  • significant location change event comes in. Check speed/activity. You can do this without background services enabled.
  • if driving just save location. You will get frequent changes when driving so you will only be minutes behind.
  • not driving or idle, create a region of reasonable size depending on your accuracy requirements. As long as device is in that region, you can use the currently saved recent location. This will be most of the time throughout a 24 hour period so you are impacting the battery very little overall.

It seems to me you are going at this with the wrong perspective. It is logical to think that you will send message then respond by making CL do something. That is not the way the service is intended. Essentially, you set up the Location Manager and let it go.

You may benefit from looking at some of the patterns used in a repository I have on GitHub.

TTLocationHandler

It hasn't been updated in a year, and I don't remember all of what I learned working on it but it has served me solid as I haven't had to fiddle with it.

Following that example, you would stop and start, or switch to and from region or significant location changes monitoring all based on application state. No interaction needed.

Your location events are handled as they come, background or no.

In your remote notification response you would only read the latest location info or whatever info you require and act as per your intention. You do not call startUpdatingLocation here. If you are using location services in the background you should never have stopped it in the first place.

Edit- Proper use of Expiration Handler Block

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    __block UIBackgroundTaskIdentifier bgTask =0;
    UIApplication  *app = [UIApplication sharedApplication];
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
          // Do something here. Perhaps you'll log the error or respond with some fall back
          // This block will only be run if the handler expires without having been ended
          // In that case, you need to end it now or you will crash
          If (bgTask != UIBackgroundTaskInvalid) {
              [app endBackgroundTask:bgTask];
              [bgTask = UIBackgroundTaskInvalid];
          }

      }];

      // This is where the code you intend to run in the background starts

     [self.locationManager startUpdatingLocation];

     // Now tell the system you are finished, ending background task normally before exit

     If (bgTask != UIBackgroundTaskInvalid) {
         [app endBackgroundTask:bgTask];
         [bgTask = UIBackgroundTaskInvalid;
     }
}
Dean Davids
  • 4,174
  • 2
  • 30
  • 44
  • @David Thank you vary much for This Explanation, But 1 question in my mind is if app is started & location manager is also started then why app was stop by os? can i get more time using some backgroundFatcher or some other technique ? –  Feb 10 '14 at 05:45
  • Many are under the mistaken impression that "background execution" in iOS is license to run freely in the background. The truth is that iOS is very particular about what it allows. You should think of it more as you are allowed to listen for specific events, in this case location events, and you are given a short window to respond. In all circumstances, the OS reserves the right to suspend or kill your app when it sees fit and will do so frequently based on memory, cpu, battery and other system pressures. – Dean Davids Feb 10 '14 at 11:12
  • In your case, @stephendarlington nailed your problem directly. You are using background task identifier incorrectly. If you fixed that, you should be given the time you need to finish. You should check off his answer as having been your solution. If you still have issues, it will likely be time for more new questions. – Dean Davids Feb 10 '14 at 11:17
  • @davids: yes i have removed `beginBackgroundTaskWithExpirationHandler:` and write only `[self.locationManager startUpdatingLocation];` as per stephendarlington suggestion, but still app is getting killed, It would be great if you show me proper use of background task identifier, or just point out just reference links. –  Feb 10 '14 at 13:49
  • I added an example of the expiration form. Also, note that I changed the method not to include the fetchHandler. Doesn't seem you are using that in your case, no? – Dean Davids Feb 10 '14 at 17:28
  • Yes i am using that method, Using your updated code `didReceiveRemoteNotification` i am not able to get silent push notification, and this method is not called when i send notification to device, not even in foreground using this payload `{"aps" : {"content-available" : 1},"SilentPush" : "4"}`. –  Feb 11 '14 at 05:39
  • As i know `didReceiveRemoteNotification` is only called when user tap on notification or notification is come and if app is in foreground, If i am sending normal payload like ( `{ "aps": { "alert": "Rush Hour", "sound": "default" }, "id": 1234}` ) than this works, a notification is displayed in notification center , and if user tap that app is come to foreground and location updates is starts. –  Feb 11 '14 at 05:46
  • But i dont want user interaction for that, thats why i am using silent pushnotification , and for that `didReceiveRemoteNotification` with `fetchHandler` is required. –  Feb 11 '14 at 05:48
  • As per Apple Doc say: If your app is suspended or not running, the system wakes up or launches your app and puts it into the background running state before calling the method. SO , is app should run in background for long as i enabled location backgound mode ,? –  Feb 11 '14 at 07:07
  • @DeanDavids Outstanding response, but one question: if using significantChange and and you want to request an updated, more accurate current location (lets say an accurate one within a few meters), how would you trigger this? – YWCA Hello Jul 19 '16 at 21:18
  • @YWCAHello significantChange event will notify/wake your app. It does not have any bearing on what accuracy you will get. Your location manager will begin to receive events and they will be as per the settings you chose on initializing it. If you want to request more accuracy than that, You will need to reset the options in your location manager and wait for the result, respond as you wish. – Dean Davids Jul 20 '16 at 22:00
0

The method beginBackgroundTaskWithExpirationHandler: give the app time to to its stuffs, but it doesn't meant that the task can run forever.
You're app is killed because you are clearly running out time, as stated in the crash log.
You should get the same result just asking for location update without wrapping into the expiration handler.
I remember that in older systems I've managed something similar, by triggering location updates in background using region monitoring.
[EDIT]
To have best result while playing with location I suggest you to use real devices and maybe you are.

Andrea
  • 26,120
  • 10
  • 85
  • 131
  • As per Apple Doc say: If your app is suspended or not running, the system wakes up or launches your app and puts it into the background running state before calling the method. SO , is app should run in background for long as i enabled location backgound mode? –  Feb 11 '14 at 08:24
  • Sorry but I don't understand what you are asking. Apple doc says also:`When this method is called, your app has up to 30 seconds of wall-clock time to perform the download operation and call the specified completion handler block. In practice, your app should call the handler block as soon as possible after downloading the needed data. If you do not call the handler in time, your app is suspended. More importantly, the system uses the elapsed time to calculate power usage and data costs for your app’s background downloads` – Andrea Feb 11 '14 at 09:34