I'm trying to build an iPhone app that requires me to keep track of the user's location at all times using Core Location. I am using startMonitoringSignificantLocationChanges so that it updates in the background and because the accuracy is not as important as it being updated whenever there is a significant change.
Currently I make an HTTP request to a web service to update the location. This works perfectly when I am running the app - my location gets updated in the MySQL database I'm storing location data in. But when the app enters into the background - I can see the location services icon in the top right corner of the phone still running, but when I go back and look at the database, it didn't update my location at all. I tested this by taking a drive across town, one way with the app running, and one way with the app in the background.
From Apple's documentation:
If you leave this service running and your application is subsequently suspended or terminated, the service automatically wakes up your application when new location data arrives. At wake-up time, your application is put into the background and given a small amount of time to process the location data. Because your application is in the background, it should do minimal work and avoid any tasks (such as querying the network) that might prevent it from returning before the allocated time expires. If it does not, your application may be terminated.
How long exactly is this "small amount of time to process location data"? And is it just not recommended to query the network, or is it not possible to query the network in that time? Is there a better way to keep track of the location of several different users even when the app is in the background?
You should have look at background tasks.
When Apple says small amount of time to process location data, you shouldn't really rely on getting any processing time after the locationManager:didUpdateToLocation:fromLocation: method has returned. Assuming that you're running the HTTP request asynchronously in a separate thread, you are likely not given enough time to finish it before your app is suspended.
UIBackgroundTasks let you ask the operating system for extra processing time while in background. Making a HTTP request could be such a task. The time limit is 10 minutes although you are not guaranteed to get this much time.
In your location callback, you should define a new background task for your request. The expiration handler block is triggered at any time if the operating system decides that it can not give you any more processing time.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// Start your request here
UIBackgroundTaskIdentifier backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
backgroundTask = UIBackgroundTaskInvalid;
// Cancel your request here
}];
}
And when the request has finished you should tell the application that the task is done:
- (void)requestFinished:(id)request {
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}
In this example, I've not taken into account that you may receive multiple location callbacks before the request has finished. If that happens, you need to cancel that request and current background task before starting a new one, or create a separate background task for each request.
Another way of doing it would be to run the HTTP request synchronously on the main thread in the location callback method, but that will be a bad thing to do for multiple reasons, like locking up the interface if the user opens the app while a request is running.
Related
I'm creating an application for my school which should check every n minutes if there is a new mark on the website.
To do this when the user login for the first time, the number of the actual mark is saved in the "UserDefaults". When app is terminated, after n minutes, the number of mark is recounted and compared with the previous one and send a notification in case the number is changed.
What I'd like to know if there is a way to perform this task. I've tried to create a timer in -applicationWillTerminate- but it's fired only once.
This is what I tried:
func applicationWillTerminate(_ application: UIApplication) {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AppDelegate.findMark), userInfo: nil, repeats: true)
self.timer.fire()
}
}
Selector findMark is the task.
Thanks in advance!
You have two options
Background App Refresh
Silent push notifications
Easiest one is Background App Refresh. Because later one needs a server to send the notification. You can check following API for the usage. Basically you set Background Fetch capability on Capabilities/Background Modes of your app. Then from time to time, iOS will wake up your app and call application(_:performFetchWithCompletionHandler:) delegate. You will have around 30-45 seconds to call your function and call completion handler. If you don't finish it on time, iOS will kill your app. If you don't obey the rules, iOS will give you less chances to wake up. For more detailed usage of Background Modes, you may check following tutorial
It's not possible to perform tasks like described in your question after the app is terminated. As described in the 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.
Link: https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html
Edit:
You cannot perform any task after the application is terminated. What you can do is get that calculation done from server side and send a Push Notification to the device.
Accutally , the answers is yes. But you should not.
We can use Location to archive your goal.
Accroding to offical document in here: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW10:
Note: If your app is terminated either by a user or by the system, the
system doesn’t automatically restart your app when new location
updates arrive. A user must explicitly relaunch your app before the
delivery of location updates resumes. The only way to have your app
relaunched automatically is to use region monitoring or the
significant-change location service.
So we can archive it by using Starting the Significant-Change Location Service:
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. At
wake-up time, the app is put into the background and you are given a
small amount of time (around 10 seconds) to manually restart location
services and process the location data. (You must manually restart
location services in the background before any pending location
updates can be delivered, as described in Knowing When to Start
Location Services.) Because your app is in the background, it must do
minimal work and avoid any tasks (such as querying the network) that
might prevent it from returning before the allocated time expires. If
it does not, your app will be terminated. If an iOS app needs more
time to process the location data, it can request more background
execution time using the
beginBackgroundTaskWithName:expirationHandler: method of the
UIApplication class.
Call location update in willFinishLaunchingWithOptions and applicationDidBecomeActive Then you can excute your own code right after
[_locationManager startUpdatingLocation];
But is is extremly drain your battery, you may be rejected by app store.
I'm creating an application for my school which should check every n minutes if there is a new mark on the website.
To do this when the user login for the first time, the number of the actual mark is saved in the "UserDefaults". When app is terminated, after n minutes, the number of mark is recounted and compared with the previous one and send a notification in case the number is changed.
What I'd like to know if there is a way to perform this task. I've tried to create a timer in -applicationWillTerminate- but it's fired only once.
This is what I tried:
func applicationWillTerminate(_ application: UIApplication) {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AppDelegate.findMark), userInfo: nil, repeats: true)
self.timer.fire()
}
}
Selector findMark is the task.
Thanks in advance!
You have two options
Background App Refresh
Silent push notifications
Easiest one is Background App Refresh. Because later one needs a server to send the notification. You can check following API for the usage. Basically you set Background Fetch capability on Capabilities/Background Modes of your app. Then from time to time, iOS will wake up your app and call application(_:performFetchWithCompletionHandler:) delegate. You will have around 30-45 seconds to call your function and call completion handler. If you don't finish it on time, iOS will kill your app. If you don't obey the rules, iOS will give you less chances to wake up. For more detailed usage of Background Modes, you may check following tutorial
It's not possible to perform tasks like described in your question after the app is terminated. As described in the 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.
Link: https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html
Edit:
You cannot perform any task after the application is terminated. What you can do is get that calculation done from server side and send a Push Notification to the device.
Accutally , the answers is yes. But you should not.
We can use Location to archive your goal.
Accroding to offical document in here: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW10:
Note: If your app is terminated either by a user or by the system, the
system doesn’t automatically restart your app when new location
updates arrive. A user must explicitly relaunch your app before the
delivery of location updates resumes. The only way to have your app
relaunched automatically is to use region monitoring or the
significant-change location service.
So we can archive it by using Starting the Significant-Change Location Service:
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. At
wake-up time, the app is put into the background and you are given a
small amount of time (around 10 seconds) to manually restart location
services and process the location data. (You must manually restart
location services in the background before any pending location
updates can be delivered, as described in Knowing When to Start
Location Services.) Because your app is in the background, it must do
minimal work and avoid any tasks (such as querying the network) that
might prevent it from returning before the allocated time expires. If
it does not, your app will be terminated. If an iOS app needs more
time to process the location data, it can request more background
execution time using the
beginBackgroundTaskWithName:expirationHandler: method of the
UIApplication class.
Call location update in willFinishLaunchingWithOptions and applicationDidBecomeActive Then you can excute your own code right after
[_locationManager startUpdatingLocation];
But is is extremly drain your battery, you may be rejected by app store.
I need to check backend via API for app settings update, therefore I'm not sure would it be sufficient to perform such check upon launching or I need to do as soon as app comes to the foreground. Of course it depends on how many apps are opened, device capabilities, but what is there a general rule about how long an app can survive in the background until it's killed? A week?
There is no general rule and can not give you any time approximation. Moreover probably for you is more important how much time system can give you for a background fetch as in background app goes to suspended state.
For the time you can request after going to background if I remember correctly before iOS 7 it was up to 10 minutes, on iOS 7 it was up to 180 seconds and I haven't checked it personally on newer versions. Anyway that time is not guaranteed.
Apple tells us to use the following method
Listing 3-1 Starting a background task at quit time
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
But this method will not allow to keep running forever
Note: Always provide an expiration handler when starting a task, but if you want to know how much time your app has left to run, get the value of the backgroundTimeRemaining property of UIApplication.
So you will need to use one of the following techniques
Implementing Long-Running Tasks
For tasks that require more execution time to implement, you must request > specific permissions to run them in the background without their being suspended. In iOS, only specific app types are allowed to run in the background:
Apps that play audible content to the user while in the background, such as a music player app
Apps that record audio content while in the background
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of those services.
Declaring the services lets the system know which services you use,
but in some cases it is the system frameworks that actually prevent
your application from being suspended.
In the last method u are limited to what you are able to do
You can read about implementing long-running tasks here
iOS: Background Execution
I am building an app, that has location updates enabled in the background, for this I am using an instance of CLLocationManager with the following code:
self.coreLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.coreLocationManager startMonitoringSignificantLocationChanges];
I implemented its delegate callback - (void)locationManager:(CLLocationManager *)locationManager didUpdateLocations:(NSArray *)locations, in which I trigger a network request to Google in order to reverse geocode the coordinates and retrieve the address of the location.
This does work very well if the app is in the foreground, but in the background it it doesn't seem to work. I have difficulties debugging it for testing as well, because I can't access the logs when the app is in the background (so I used Mixpanel, an analytics framework to capture the location updates as events, but apparently they don't work either).
In order to receive significant location change updates in the background you need to restart location services when your app is launched into the background - your delegate methods won't be called unless you do this.
From the Location And Maps Programming Guide -
If you are monitoring regions or using the significant-change location
service in your app, there are situations where you must start
location services at launch time. Apps using those services can be
terminated and subsequently relaunched when new location events
arrive. Although the app itself is relaunched, location services are
not started automatically. When an app is relaunched because of a
location update, the launch options dictionary passed to your
application:willFinishLaunchingWithOptions: or
application:didFinishLaunchingWithOptions: method contains the
UIApplicationLaunchOptionsLocationKey key. The presence of that key
signals that new location data is waiting to be delivered to your app.
To obtain that data, you must create a new CLLocationManager object
and restart the location services that you had running prior to your
app’s termination. When you restart those services, the location
manager delivers all pending location updates to its delegate.
Also, when you are launched into the background you should not perform network operations unless you request additional execution time. From the Location and Maps Programming Guide again -
Because your app is in the background, it must do minimal work and
avoid any tasks (such as querying the network) that might prevent it
from returning before the allocated time expires. If it does not, your
app will be terminated. If an iOS app needs more time to process the
location data, it can request more background execution time using the
beginBackgroundTaskWithName:expirationHandler: method of the
UIApplication class.
I would like to run a process in the background of my application that gets the user's current latitude/longitude (at 5 minute intervals) and reports it back to our server via web service call. When I add required background modes to my info.plist I have the following options:
-App plays audio
-App registers for location updates
-App provides Voice over IP services
Now in my case I obviously need the second choice on the list, but once I have the location what can I do with it? Can I make a web service call in the background?? What other limitations might there be??
Thanks a lot!
When you App is in background mode you can not establish a HTTP connection the usual way. You have to setup a background task that sends the location updates to your server.
I found a short tutorial that covers exactly what you're looking for (I've not tested the code).
This is a summary of how it works
When your App is running in background and a location change occurs, iOS wakes up your App and calls the delegate method locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation of your CLLocationManager. When this happens, check if your App is running in the background and setup the background task if it is. The background task then should send the data to your server.
Limitations
The documentation is not clear about how much time such a background task may take to finish, but I slightly remember it was something around 10 minutes maximum.
As far as I know you can not update the UI while your App runs in the
background