Is the state of my app guaranteed to be reinstated when UIApplicationWillEnterForeground notification gets called?
In other words, if I have a private boolean field set to true, will that variable still be true if I check its value in my UIApplicationWillEnterForeground notification method handler when transitioning from the background to the foreground?
No, When the App enters the background, it still stays in memory, however if memory runs low, the iOS begins to release these suspended apps to make room for those that must be loaded. So basically if you want to ensure that there is no data loss, you must save app data and settings in:
func applicationDidEnterBackground(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
If you get told that your app is being suspended, (Via a call to applicationWillResignActive) you should save your app state. Your app may then be activated again while still in memory, or it may be terminated. If you get reactivated again, all of your app's variables will still have their previous values. If not, you will be launched "cold" and you will need to restore your saved state.
At the time you are told that you are going to switch to the inactive state you can't know if you will get reactivated or terminated. Thus the answer to your question is no. You might get killed, not reactivated.
EDIT:
Apple's docs say:
Handle Deactivation
When a foreground app moves to the background, UIKit first calls
the applicationWillResignActive: method to deactivate the app. When
deactivation occurs, do the following.
Save user data to disk and close any open files. Always save user
data in case your app needs to be terminated. Close files in case the
user locks the device.
Do only work that’s critical to preserving the user’s data. Pause
dispatch queues and operation queues, and don’t schedule any new tasks
for execution.
Invalidate any active timers.
Related
I have create a smart alarm which relies on WKExtendedRuntimeSession.
There should be a possibility to manually stop the background session by, for example navigating back in the view hierarchy.
class BackgroundSessionController {
private var session: WKExtendedRuntimeSession?
func start() {
guard session?.state != .running else { return }
if nil == session || session?.state == .invalid {
session = WKExtendedRuntimeSession()
}
print("session started")
session?.start(at: Date())
}
func stopManual() {
session?.invalidate()
}
func stopByAlarm() {
session?.notifyUser(hapticType: .stop)
session?.invalidate()
}
}
When firing the function stopManual, and so invalidating the session I receive the message:
App has been running in the background but failed to play a scheduled
alarm. Would you like to disable the app's ability to run background
tasks ...
Seems that manually invalidating a session requires a haptic notification as well ?
How can I invalidate the session without the haptic feedback ?
added example:
let's say i'm a terrorist and i'm making a secret bomb which fires when movement stops. So you need to keep moving or else a timer starts counting down.
I Activate the app, I need to enable background modes, else the sensors stop working when the app goes into the background.
When movement stops, a smart alarm timer will fire .start(at:) which
counts down from 10 minutes.
I'm using smart alarm as functionality, which allows me to use 30 minutes of background modes. When these 30 minutes are finished and the person is still moving, i want to invalidate and then restart the session without sending any haptic feedback (the person will notice something isn'tright and deactivates the bomb)
What to use in this case then? This example is a bit weird but almost the same functionality I want.
The documentation of WKExtendedRuntimeSession clearly states this behaviour:
For sessions started with start(at:), you can only call invalidate() when the app is active. For all other sessions, you can call invalidate() to end a session at any time.
Since you are passing the current date for start(at:), why don't you just use start() instead and then you can call invalidate() even while your app is inactive. If you actually call start(at:) with a future date, then you don't have any alternatives.
The docs also state that you must play a haptic during your session. So if your session has started, you cannot invalidate it without playing a haptic. If your session hasn't started yet, you can invalidate it.
During the session, your app must trigger the alarm by calling the session’s notifyUser(hapticType:repeatHandler:) method.
If you fail to play a haptic during the session, the system displays a warning and offers to disable future sessions.
Bear in mind, this is probably a designed feature of watchOS. Due to the battery constraints of Apple Watches, watchOS puts an even bigger emphasis on apps being energy efficient, so if your app uses a background mode and the system thinks it might be abusing it (by declaring a smart alarm background mode, but not playing an alarm), it will alert the user.
When I pressing "home" button, app calling - (void)applicationWillResignActive:(UIApplication *)application, ok its good, but when I wait for a some time (10-15 mins), app starting from first loading screen, why? I have added:
[UIApplication sharedApplication].idleTimerDisabled = YES;
But not success. I think it goes to suspended mode, how do I prevent this? Thanks.
It means that your app has been killed by the system.
You can't prevent it completely, the system kills background apps when it needs more memory, but you can do something like this:
free as memory as you can when the app goes in background: doing this, when the system needs memory, your app won't be one of the first to be killed because its footprint is low.
Since you can't be sure that your app won't be killed, use state preservation / restoration: Link
EDIT:
since your app needs to reproduce audio in the background (as written in the comments) I give you some extra advices.
remember to register your app background mode (from this link: Multitasking guide) --> "by including the UIBackgroundModes key (with the value audio) in its Info.plist file"
the app continues to work in the background since it's playing audio, but when the audio is suspended the app is suspended too (it means that it can be killed). So, you must use state preservation to restore it when the user comes back.
Another extract:
"When the UIBackgroundModes key contains the audio value, the system’s media frameworks automatically prevent the corresponding app from being suspended when it moves to the background. As long as it is playing audio or video content, the app continues to run in the background. However, if the app stops playing the audio or video, the system suspends it."
You app has exited. If you want it to come back to where you left off you'll have to preserve the state somehow, check on launch, and then restore the state.
I have an app which uses CLLocationManager to track the user's route, drawing dots along the path taken. The app runs in the background using Required background modes > App registers for location updates.
As I understand, anything that happens in the background needs to be called from locationManager:didUpdateToLocation:fromLocation as this is the method that gets called with each location update.
The problem I'm having is that sometimes this stops getting called. It seems to happen when the user's location does not change much within the space of maybe 15 minutes or so. As far as I can tell, calls to locationManager:didUpdateToLocation:fromLocation just stop, presumably to save the battery. Unfortunately, it doesn't resume again when you're back on the move.
I presume there's no way to override this behaviour, so I would like to use Notification Centre to inform the user that the app is no longer recording the route. The problem is, how can the app know that this has happened? If locationManager:didUpdateToLocation:fromLocation is not called, I can't fire my notification. If it is being called, the notification should not fire.
I don't think that there is any way to be notified that the location manager has stopped sending you events, but there is a way to prevent it from happening. In iOS 6, a new feature was added that allows the location manager to power down services if it doesn't think they are being used. If you do the following, the location manager will continue sending you events in the background until you run out of battery:
if ([self.locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)])
{
self.locationManager.pausesLocationUpdatesAutomatically = NO;
}
Also, in iOS6, you should be using locationManager:didUpdateLocations: as locationManager:didUpdateToLocation:fromLocation: is deprecated.
The 2 delegate methods:
-(void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
and
-(void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager
tells you when the location updates stop and start due to pausing.
I have an app which uses CLLocationManager to track the user's route, drawing dots along the path taken. The app runs in the background using Required background modes > App registers for location updates.
As I understand, anything that happens in the background needs to be called from locationManager:didUpdateToLocation:fromLocation as this is the method that gets called with each location update.
The problem I'm having is that sometimes this stops getting called. It seems to happen when the user's location does not change much within the space of maybe 15 minutes or so. As far as I can tell, calls to locationManager:didUpdateToLocation:fromLocation just stop, presumably to save the battery. Unfortunately, it doesn't resume again when you're back on the move.
I presume there's no way to override this behaviour, so I would like to use Notification Centre to inform the user that the app is no longer recording the route. The problem is, how can the app know that this has happened? If locationManager:didUpdateToLocation:fromLocation is not called, I can't fire my notification. If it is being called, the notification should not fire.
Is there some kind of system notification that says location updates will cease?
I'm finding it quite hard to debug this as I can't take my Mac everywhere when I'm out and about testing the location on the device (there's only so much you can do in the simulator). Any tips for debugging would also be much appreciated!
If you haven't found the answer, I think it is because of a new attribute added to CLLocationManager called pausesLocationUpdatesAutomatically. The attribute defaults to YES, and its behaviour is exactly as you describe. Try setting it to NO and I think it will fix your problem.
Starting in iOS9, make sure you're also setting this property on your location manager:
[locationManager setAllowsBackgroundLocationUpdates:YES]
There's a delegate for location update did Fail
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
There are a few kinds of errors: kCLErrorDenied kCLErrorNetwork Add code here to handle them in the delegate method above not updating location, perhaps a UIAlertView to tell the user.
Personally, I call [locationManager stopUpdatingLocation]; on any error then restart it with an error message depending on the reason for the failure.
ALSO re background, check code in your appDelegate:
- (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.
[self saveContext];
if ([CLLocationManager significantLocationChangeMonitoringAvailable]) {
// Stop normal location updates and start significant location change updates for battery efficiency.
[self.locationHandler.locationManager stopUpdatingLocation];
[self.locationHandler.locationManager startMonitoringSignificantLocationChanges];
}
else {
NSLog(#"Significant location change monitoring is not available.");
}
}
LASTLY re: testing. You can simulate some errors in location by changing the location movement in the simulator. For example, going from running to driving will cause an error. Going from running to a single specific custom location will cause an error. They should all appear in the delegate method for locationManager above.
I've managed to solve the problem by adding a local notification that fires with a 90 second delay every time a new location is added to the route. When the next location is added, the previous notification is cancelled and a new one is scheduled. This way, if it stops updating, a notification is received by the user (albeit with a 90 second delay). It's not ideal, and it may not be great for battery life, but it is a solution and it's the best I've got for the time being.
#Ron, I meet the same problem as beev describe, and i had already set pausesLocationUpdatesAutomatically to NO. I think because iOS will kill some apps that didn't be triggered in 10 minutes when it's under background. So add local notification maybe a good choice at the moment.
While going through the iOS-4 Multitasking for fast context switching, I have a doubt regarding save last state of application.
Do applications have to manually save the last state in "- (void)applicationDidEnterBackground:(UIApplication *)application"? Or iOS-4 will take care of it?
In the video it's mentioned as follows:
-(void)applicationDidEnterBackground:(UIApplication *)application {
// save app state
[self saveState];
// reduce memory usages
....
// prepare UI
....
// close listening sockets
....
}
Thanks in advance,
Sunil
Once your application has entered background, there's no guarantee it will ever come back to the foreground. It may at any point in time be terminated, without notification of any kind. Thus, entering background, you want to save the state or risk losing it.
To quote Apple (source: http://developer.apple.com/iphone/library/documentation/iphone/conceptual/iphoneosprogrammingguide/BackgroundExecution/BackgroundExecution.html),
Save your application state before moving to the background. During low-memory conditions, background applications are purged from memory to free up space. Suspended applications are purged first, and no notice is given to the application before it is purged. As a result, before moving to the background, an application should always save enough state information to reconstitute itself later if necessary. Restoring your application to its previous state also provides consistency for the user, who will see a snapshot of your application’s main window briefly when it is relaunched.
Do applications have to manually save the last state in "- (void)applicationDidEnterBackground:(UIApplication *)application"? Or iOS-4 will take care of it?
Yes, if you want your app to restore after it's been killed, you need to manually save state here.
you should use these 2 methods in your app delegate to save current state before entering background/terminate.
- (void)applicationWillResignActive:(UIApplication *)application {
/*
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)applicationWillTerminate:(UIApplication *)application {
/*
Called when the application is about to terminate.
See also applicationDidEnterBackground:.
*/
}