I have background mode on for location services and aiming to send out location (latitude and longitude) to the server every 30 minutes. For now I am printing the same in the console. It seems to work for a while but I am wondering how do I work with NSTimer in this case. And from where should I be calling it?
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var locationManager = CLLocationManager()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(application: UIApplication) {
// 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.
}
func applicationDidEnterBackground(application: UIApplication) {
// 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.
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation() // I know i should be using signification location option here. this is just for testing now.
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
self.sendBackgroundLocationToServer(newLocation);
}
func sendBackgroundLocationToServer(location: CLLocation) {
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(bgTask)
}
println(location.coordinate.latitude)
if (bgTask != UIBackgroundTaskInvalid)
{
UIApplication.sharedApplication().endBackgroundTask(bgTask);
bgTask = UIBackgroundTaskInvalid;
}
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
application.beginBackgroundTaskWithExpirationHandler{}
}
func applicationDidBecomeActive(application: UIApplication) {
// 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.
application.beginBackgroundTaskWithExpirationHandler{}
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
application.beginBackgroundTaskWithExpirationHandler{}
}
}
Maybe calling application.beginBackgroundTaskWithExpirationHandler{} is a bad idea? What options do I go with here?
The idea of beginBackgroundTask... is to start a finite length task so that if the user leaves the app, it will keep running in the background task for some short, finite period of time (3 minutes, I believe). And before the time runs out, you have to call endBackgroundTask or else the app will be summarily terminated.
So, sadly, the background task mechanism is not really suited for your desired intent. There are, though, a narrow set of special background modes designed for continued background operation outside a narrow set of functions (VOIP, audio, etc.). For more information, see the Implementing Long-Running Tasks section of the App Programming Guide for iOS: Background Execution.
Now, one of those background modes is for a "location" service. So, if that is a central feature of your app, essential for proper function, then you can register for the location background mode, and your app will continue to run in the background. From there, you can monitor for location updates, and if a sufficient amount of time has elapsed, trigger some process. But if this background location mode is not an essential feature of your app, Apple is likely to reject your app for requesting a background mode that it doesn't need.
By the way, you should be aware that starting standard location services may drain the device battery. You might consider using the battery efficient "significant change" location service. This also has the virtue of automatically waking your app every time the user moves some significant distance (e.g. measured in km; I believe it's triggered by moving to different cell tower).
Couple of notes, since we've been fighting the location issues and struggling with background applications that don't seem to wake us up.
NSTimer is only good as long as your application is not suspended. Sure, it's in the background, but onlyinasmuchas it is capable of receiving notifications from the iOS (APN, location, ...). Eventually, your timer WILL stop firing while you run in the BG. So, you are relying on one of the BG modes to kick you.
If you ARE using CLLocationManager.startUpdatingLocation, you're going to notice that you quit getting those after a bit. Especially when the phone relaxes and tries to sleep. This is because of a poorly documented feature called CLLocationManager.pausesLocationUpdatesAutomatically ... which decides to stop sending those when set to "true" (the DEFAULT setting). You can set this to "false", and you'll continue to get your position updates. Which will pretty much make sure your app does what you are wanting.
As noted above, you must make sure when you get your locationManager.didUpdateLocations callback that you begin a backgroundTask to keep your application working while you process that information and send your network data. But you seem to understand that.
Just waking up long enough to "record" a location update isn't so bad. Just make sure you don't spawn your network code unless you've clearly hit your 30 min expectation.
try this:
Make a singleton for Location Service. Registered it with a NSNotification
In AppDelegate's willEnterBackGround, you send a notification via NSNotificationCenter
then, your singleton will start updatingLocation and send it to server when location's data received.
Related
In order to identify the app termination, i have implemented the Finite-length task but after some period of time app gets terminated and started from start screen.
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != .invalid)
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
if backgroundTask != .invalid {
endBackgroundTask()
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
registerBackgroundTask()
}
app should not be terminated when performing Finite-Length tasks, also please provide examples to identify about the termination of the application when it is in suspended mode.
app should not be terminated when performing Finite-Length tasks
That's not promised at all. There are lots of reasons your app might be terminated. beginBackgroundTask is a request to the OS for more time to finish executing a short-running operation for the user. It doesn't promise your request will be granted.
The specific way you're doing this is likely to fail occasionally, and is specifically addressed in the docs:
Call this method as early as possible before starting your task, and preferably before your app actually enters the background. The method requests the task assertion for your app asynchronously. If you call this method shortly before your app is due to be suspended, there is a chance that the system might suspend your app before that task assertion is granted. For example, do not call this method at the end of your applicationDidEnterBackground(_:) method and expect your app to continue running. If the system is unable to grant the task assertion, it calls your expiration handler.
You want to wrap the specific operation you want to request time for. You always wrap that operation, whether you think you're going into the background or not. You don't just call this every time you go into the background.
please provide examples to identify about the termination of the application when it is in suspended mode.
In applicationWillEnterBackground write a value into UserDefaults. In applicationDidEnterForeground, remove the key. In applicationWillFinishLaunching, look for that key. If it's there, then you're being relaunched, so at some point you died in the background. That includes something like a reboot or power-failure. If it's not there, then this is first launch, or you crashed in the foreground. The precise way to implement this highly depends on what you plan to do with the information.
I'm using CoreLocation in my iOS Application. I'm using Significant Location changes and some Geofences. It's currently working well but I would like to improve my location data quality with getting more location updates.
Currently I have one CLLocationManager instance running in my application. This single instance is listening geofence and locationUpdates.
As far as I know, significant changes are related to cell tower changes which is enough for me but I have some cases that I need more location data.
I don't want to use startUpdatingLocation as it increases the battery consumption but I have an idea about enabling and disabling locationUpdates (while keeping the significant updates open) between the geofences. So, I'm planning to start a secondary instance of CLLocationManager when a geofence event occurs. I'll keep that secondary instance running until another Geofence event occurs.
This will help me to get more detailed location data where I need without keeping location services always open.
My question is I don't know if my approach is possible in iOS. In any event of geofence, my app will most like be on background. Can I still enable the secondary CLLocationManager start and stop?
I already enabled Background Modes for Location Updates, it's working fine on the current version.
Thanks in advance.
I don't think you need separate CLLocationManager instances. My understanding (and experience) is that geofencing and location updates can coexist in the same CLLocationManager.
Your app can respond to location events in the background, e.g.:
In AppDelegate.swift:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
// were we invoked due to LocationManager event:
if launchOptions?[UIApplicationLaunchOptionsLocationKey] != nil {
printLog( "UIApplicationLaunchOptionsLocationKey" )
// We were previously terminated/suspended and need to configure locmgr
// etc.
// after this, app will process received location events via
// didUpdateLocations()
// didExitRegions()
Assume that I am build an iPhone application which accepts user name and password in one of the screens. While I am entering my username, I get an high priority event like a phone call.
My app will transit from being active to an inactive state at this point.
My question here is: What are the steps that I need to do in my app so that I can save the current application state (and entered info) so that I can restore the same when my app becomes active later on?
This question was asked in one of iOS interviews.
My answer was that handle the active to inactive state transition in the applicationWillResignActive delegate method to save the partially entered user info details and restore it in applicationDidBecomeActive method. The App UI state, we don't have to handle as the OS will take care of it.
It looked like the interviewer was not convinced with my answer. He kept asking me as to how would you handle (steps that you would take in your app) the transition from an active state to inactive state and then to active state in your app so that app state is restored/intact?
Your answer is correct, the interviewer was probably looking for you to name each of these methods and you most likely only named the two you stated. There are a few other delegate methods for certain App UI states, such as applicationDidEnterBackground and applicationWillEnterForeground
If you read the default description of these methods in the comments you will get a better understanding of what each is used for. For example, applicationWillResignActive specifically refers to incoming phone calls or SMS messages:
// 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.
And applicationDidEnterBackground is for saving user data when a user closes your app:
// 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.
Check out your AppDelegate.m file of a new project and the comments really are very, very useful for understanding exactly which ones will handle what states and when you should use each.
Also, be sure to read up on Apple's documentation on handling application states: The App Life Cycle
Apple has already provided decent documentation on your question:
link.
And for interruptions:
link for apple developer site on handling interruptions
I think you are talking much more about restore state.
First of all, implement App delegate protocol:
func application(application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
func application(application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
Then in your ViewController, implement UIStateRestoring protocol. Here you should notice that if you want archive your own complicated object, don't forget to implement NSCoder.
Please go deep into "State Restoration".
Besides, when your app go to background, you need to do some work such as stop timer, pause ongoing task.
When go to foreground, you need to do something like start the paused task, and prepare to refresh page.
In didFinishLaunchingWithOptions, how do you know if the app is resuming from the foreground or is a new launching? I need to know in this method because there are 2 tasks to run depending on what the launchOptions are and what state the app is in. Thank you.
From Test if app did become active from a UILocalNotification:
When an app enters the foreground from the background it does not trigger applicationDidFinishLaunchingWithOptions. It does, however, call applicationWillEnterForeground and applicationDidBecomeActive. This can be verified with a couple of NSLogs.
So it's possible to know if the app returns from background or if it's a new launch.
From the developer page: search for didFinishLaunchingWithOptions and you will have all the options. Also, they have this diagram telling all the stages that the app has:
What you are looking is for the Inactive State:
The app is running in the foreground but is not receiving events. (It may be executing other code though.) An app usually stays in this state only briefly as it transitions to a different state.
Upon entering this state, the app should put itself into a quiescent state with the expectation of moving to the background or active state shortly.
Or the Suspended State:
The app is in memory but is not executing code. The system suspends apps that are in the background and do not have any pending tasks to complete. The system may purge suspended apps at any time without waking them up to make room for other apps.
After reading this I don't think that what you need is Background State
didFinishLaunchingWithOptions method calls only with a new launch. It can be regular launch in foreground, or it can be app relaunching in background when you use Background Modes. To define the details of launching see the launchOptions.
When app goes into background or foreground see applicationDidEnterBackground and applicationWillEnterForeground methods.
Here is how Branch defines launch options (https://github.com/BranchMetrics/iOS-Deferred-Deep-Linking-SDK/blob/master/Branch-SDK/Branch-SDK/Branch.m) :
- (void)initSessionWithLaunchOptions:(NSDictionary *)options isReferrable:(BOOL)isReferrable explicitlyRequestedReferrable:(BOOL)explicitlyRequestedReferrable automaticallyDisplayController:(BOOL)automaticallyDisplayController {
self.shouldAutomaticallyDeepLink = automaticallyDisplayController;
self.preferenceHelper.isReferrable = isReferrable;
self.preferenceHelper.explicitlyRequestedReferrable = explicitlyRequestedReferrable;
if ([BNCSystemObserver getOSVersion].integerValue >= 8) {
if (![options objectForKey:UIApplicationLaunchOptionsURLKey] && ![options objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
[self initUserSessionAndCallCallback:YES];
}
else if ([options objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
self.preferenceHelper.isContinuingUserActivity = YES;
}
}
else {
if (![options objectForKey:UIApplicationLaunchOptionsURLKey]) {
[self initUserSessionAndCallCallback:YES];
}
}
}
I am currently tracking the user location updates for every mile travelled. And I have background mode turned on for app to look for location updates.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
locationManager.delegate = self
if iOS8 {
locationManager.requestAlwaysAuthorization()
} else {
locationManager.startUpdatingLocation()
locationManager.stopUpdatingLocation()
}
locationManager.distanceFilter = 1609.34 // meters in 1 mile
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
return true
}
But, I am wondering, if the application has been force quit, I would still like for the application to be updating the location. Is that possible with startUpdatingLocation class? Or should I be using startMonitoringSignificantLocationChanges
I read the doc here but didn't quite understand when to move from startUpdatingLocation to startMonitoringSignificantLocationChanges when/while the app is being force quit. Should it be under applicationWillTerminate function ?
Or if that is even possible or is there something else i should be doing.
UPDATE:
I read here
In most cases, the system does not relaunch apps after they are force
quit by the user. One exception is location apps, which in iOS 8 and
later are relaunched after being force quit by the user. In other
cases, though, the user must launch the app explicitly or reboot the
device before the app can be launched automatically into the
background by the system.
If thats the case, should I use startUpdatingLocation or move to startMonitoringSignificantLocationChanges ?
If the app was terminated, you could not run any program for the app.
And it's not always to handle applicationWillTerminate function when the app was terminated. In some situations, the system kills the app without any notification. Please read the document about application life cycle.
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html
I think you want to do your app is running like a daemon service. iOS does not allow us that unless the device is jail broken.
https://www.chrisalvares.com/blog/7/creating-an-iphone-daemon-part-1/
If you are care of the device battery, you would to do like this.
startUpdatingLocation and stopMonitoringSignificantLocationChanges at applicationWillEnterForeground.
startMonitoringSignificantLocationChanges and stopUpdatingLocation at applicationDidEnterBackground.