Difference between iOS state restoration VS User Defaults and SQLite? - ios

What I currently know:
When an app is suspended, its memory space is not deallocated, but the app's code execution is paused. This means that any variables and objects that are currently in memory will still be there when the app resumes.
However, if the app remains suspended for an extended period of time or if the system needs to free up memory for other apps, the operating system may choose to terminate the app to free up resources. When an app is terminated, all of its memory and data are released, and the next time the app is launched, it will start from scratch.
If your app has implemented state preservation and restoration, any data that you've saved using the encodeRestorableState method will be available when the app resumes, even if the app was terminated in the meantime.
If your app hasn't implemented state preservation and restoration, any data that wasn't saved before the app was suspended or terminated will be lost. It would be like starting from scratch on the home screen.
What I am confused about:
These statements imply that the main purpose of the state restoration methods is to preserve the state of an app "across" its lifecycle, specifically when an app has been terminated and removed from memory entirely. This is because if the app is only temporarily suspended and later resumed, any variables and objects currently in memory will still exist when we come back.
If that's true, what's the purpose of application(_:shouldRestoreSecureApplicationState:)? Do we really need it when we already have tools such as User Defaults and SQLite? Or is it simply a built-in layer on top of these existing tools?

The application state is different to the application data.
To use a word processor as an example:
The content of the document the user is working on is application data.
The name of the document they are working on, and the font style they had selected are examples of application state.
Prior to iOS 4, there was no app suspension state; When the user pressed the home button, the active app was terminated and relaunched when they tapped the application icon.
App state preservation was important to give the impression that the app had actually kept running and not just drop the user back at the initial screen of the app after, say, they answered a phone call.
Although today it can take some time before an application is actually terminated, if your application has complex state that the user would expect to be maintained, then it is important to adopt state restoration. E.g. For a word processor, state restoration might be vital. For a social media app that always displays the most recent "feed", state restoration may not be important at all.
While you can use UserDefaults or even Core Data to store and retrieve application state, encodeRestorableState provides an elegant way for each UIViewController subclass to provide its state to the restoration process in isolation and without tightly coupling UIViewController classes to some state restoration object, which would happen if you used Core Data or UserDefaults.

Related

Resume downloads on iOS between lifecycles

I am trying to make sure that if my background download on iOS stops for whatever reason (app is killed by the system, stopped by the user from the multitasking screen, crash?) will continue in the next lifecycle.
So far I have found that the documentation for NSURLSession clearly differentiates between two clear use cases: terminated by the system ( in which case downloads will continue smoothly in the background and the system will wake up the application when the download finishes ) or killed by user (and which case everything is lost - or is it? ):
If an iOS app is terminated by the system and relaunched, the app can use the same identifier to create a new configuration object and session and retrieve the status of transfers that were in progress at the time of termination. This behavior applies only for normal termination of the app by the system. If the user terminates the app from the multitasking screen, the system cancels all of the session’s background transfers. In addition, the system does not automatically relaunch apps that were force quit by the user. The user must explicitly relaunch the app before transfers can begin again.
This is the line that bugs me. What transfers can begin again? I thought they were lost. Does it refer to the fact that I can start another download session?
Also, I could not find any info on what happens with downloads that are suspended by the system because of the network connection type. For instance if allowsCellularAccess is set to false, and my app is terminated by the system, will it still continue when it moves to an allowed network?

Wake up application even it is not running in background

It should not be the first question about this subject, but after reading lots of articles and documents, i still doubt about the feasibility.
According to Apple's document.
State Preservation and Restoration Because state preservation and
restoration is built in to Core Bluetooth, your app can opt in to this
feature to ask the system to preserve the state of your app’s central
and peripheral managers and to continue performing certain
Bluetooth-related tasks on their behalf, even when your app is no
longer running. When one of these tasks completes, the system
relaunches your app into the background and gives your app the
opportunity to restore its state and to handle the event
appropriately. In the case of the home security app described above,
the system would monitor the connection request, and re-relaunch the
app to handle the centralManager:didConnectPeripheral: delegate
callback when the user returned home and the connection request
completed.
Does it mean that we can wake up the application even if it is not running in background? and by which BT event, and implementation of code, we can wake up our application and make it run in background?

Core Bluetooth - Performing Long-Term Actions in the Background

This is from the Core Bluetooth Background Processing for iOS Apps section found in official docs:
Performing Long-Term Actions in the Background
Some apps may need to use the Core Bluetooth framework to perform
long-term actions in the background. As an example, imagine you are
developing a home security app for an iOS device that communicates
with a door lock (equipped with Bluetooth low energy technology). The
app and the lock interact to automatically lock the door when the user
leaves home and unlock the door when the user returns—all while the
app is in the background. When the user leaves home, the iOS device
may eventually become out of range of the lock, causing the connection
to the lock to be lost. At this point, the app can simply call the
connectPeripheral:options: method of the CBCentralManager class, and
because connection requests do not time out, the iOS device will
reconnect when the user returns home.
Okay, we have an app which locks/unlocks doors as appropriate... So as pointed out, this works when app is in the background (most likely in the suspended mode). Now, lets move on (with quoting docs):
Now imagine that the user is away from home for a few days. If the app
is terminated by the system while the user is away, the app will not
be able to reconnect to the lock when the user returns home, and the
user may not be able to unlock the door. For apps like these, it is
critical to be able to continue using Core Bluetooth to perform
long-term actions, such as monitoring active and pending connections.
So, if the user was away from home for a few days, and app has been terminated by the iOS, we will have to implement state preservation and restoration, so that iOS relaunch the app when connection request is detected, and let the app to unlock the door. Related quotes:
In the case of the home security app described above, the system
would monitor the connection request, and re-relaunch the app to
handle the centralManager:didConnectPeripheral: delegate callback when
the user returned home and the connection request completed.
This all make sense, but pay attention to this part again:
Now imagine that the user is away from home for a few days. If the app
is terminated by the system while the user is away, the app will not be able to reconnect to the lock when the user returns home, and
the user may not be able to unlock the door. For apps like these, it
is critical to be able to continue using Core Bluetooth to perform
long-term actions...
Does this means, if the app is forcefully killed by the user at some moment while he was away from home, that this will work as well ? Means when user comes home, the door will unlock anyway, or he must manually relaunch the app to unlock the door?
I am asking this, because of how relaunch of terminated apps works. It is not the same when user kills the app, and when iOS kills the app which supports background execution:
Apps that support background execution may be relaunched by the system
to handle incoming events. If an app is terminated for any reason
other than the user force quitting it, the system launches the app
when one of the following events happens...
Source
So once again, if the user was away for a few days and he has closed the app by double tapping Home button and dragging up, will he be able to enter his home without manually relaunching the app?
No. If the app is forcefully killed by the user then it will not be woken up again. The only scenario where it will be woken up is if the app was terminated by iOS itself, which will happen sooner or later when the app has not been it the foreground for a while. It will also not be relaunched if the device is rebooted.
Having said that, from my experience with Core Bluetooth I have come to the conclusion that State Preservation is way too unreliable. I would believe that the use-case that you are trying to implement will not work well enough, which is ironic since it is exactly the use-case that Apple is promoting it their documentation.
For example, you will have issues with the following:
State restoration will only relaunch your app due to bluetooth related activity if the event originates from a peripheral accessory that you are communicating with, such as connect/disconnect events and characteristics notifications. For other events, most importantly general bluetooth-state-change events, your app will not be relaunched and notified of this. The reason why this is so bad is because any bluetooth-state-change events will cause all pending connections to be tossed, meaning that your pending connections to the door lock will be lost. However, since your app is not relaunched to be notified of this, then it effectively means that your application will still believe that the connections are still pending when in fact they are not. Since your application is terminated at this time, the only way for it to wake up again is by having the user manually launch it again (or alternatively “hack” other background modes for this purpose, which will NOT work very reliably either).
This scenario happens if the user toggles Flight Mode, toggles Bluetooth, power cycles the iOS device, or any other undefined reasons that many cause state changes… And it is very unlikely that a bluetooth-state-change will not happen if "... the user is away from home for a few days.".
This "issue" has been reported by me and others multiple times, but Apple does not appear to want to fix it for some reason.
Apart from this, many other issues exists as well, such as the XPC connection being interrupted at different times for no apparent reason. I have also noticed that the pending connection can go into “limbo” mode where the peripheral state gets set to Connecting, but in fact it will never connect unless you cycle the connection state. Etc, etc, ...
/A

Debugging and state restoration concerns of WatchOS WCSession sendMessage waking killed iPhone app

I'm trying to add simple WatchOS 2.0 functionality to my app to allow simple actions to be triggered on the watch and have an effect in my main app on the iPhone. All of this hinges by sending messages from the watch using WCSession's sendMessage method
[[WCSession defaultSession] sendMessage:applicationData
replyHandler:^(NSDictionary *reply) {
//handle reply from iPhone app here
DbgLog(#"reply received=%#", reply);
}
errorHandler:^(NSError *error) {
//catch any errors here
DbgLog(#"error received=%#", error);
}
];
Everything is working ok so far, the message is sent, the message is received, my app reacts correctly. I can even have my app backgrounded and everything seems to still work ok (I can see on the watch whether or not the iPhone has reacted to the messages as the iPhone will update the watch connectivity context which is then reflected back on the watch). I just want to start debugging the edge cases now.
My main two worries are...
How exactly can I debug the system waking up my app if it's been killed. I obviously can't spawn the process from Xcode, if i try to attach to the process it'll obviously be too late, ideally i'd like to be able to get break points set really early to see how things work. Worse case I can rely on lots of logs I guess and look at those. Just wondering if there's a good way of trying to debug this scenario?
Finally, what happens in this killed app being ran by the sendMessage being sent scenario with regards to state restoration. ie.
My app was running with the app in a certain state (A)
I press the home button to background the app, state encoding is performed for my app state (A) at that time.
The app then sits in the background.
I then force kill the app by stopping running it in Xcode.
Finally I then sendMessage from the watch causing the system to re-run the killed app.
At this stage I'd expect state restoration loading to occur, the message comes in and is processed changing app state to state B, and then the app goes back to being backgrounded.
My problem is that the app still has its state encoding saved in state A when it was originally backgrounded.
Is there any way I can force a new state encode to occur whilst backgrounded after I've finished processing the watch connectivity sendMessage?
What happens when I next open my app properly, does state restoration occur which brings us back to state A when having processed the watch sendMessage it should be in state B. I'm guessing its still running in the background so whatever has occurred when the app spawned the app in the background should persist and no new state restoration should occur. However what if the system kills the app again whilst backgrounded in state B. Does it appreciate this scenario requires a new state encode to save the new state B or does it just kill the app and do nothing. If so then when we finally do freshly launch it then we may again be heading back to app state A rather than B.
As an aside, having just started to try and debug this sort of thing, though without the ability to seemingly connect my debugger for this system re-ran instance of my iPhone app. I've noticed that when I do the sendMessage that should wake up the killed iPhone app, the watch app does NOT update properly, ie. the phone app doesn't fully perform the action that has been requested. I'm currently blind at what things have taken place in this state, but wonder if perhaps state restoration could be getting in the way. Is the message received, waking the app, trying to act on data that doesn't exist as no state restoration has occurred yet, then state restoration occurs.. the 2nd time i send the message from the watch the app behaves correctly, so its as if the initial message wakes the app up but for some reason doesn't correctly process the message. Once woke up properly however the next sendMessage is handled as normal.
Sorry I find the documentation rather vague on what occurs when the iPhone app is background re-ran from a sendMessage call. Anyone got any ideas? Your time as always is really appreciated! Cheers!
Background and State Restoration:
Your app which was launched in the background (then terminated) will never save state, because it never transitions from foreground to background (which is when state preservation happens).
From Preserving and Restoring State:
UIKit preserves your app’s state at appropriate times, such as when your app moves from the foreground to the background.
The reason why it doesn't save state is because it is launched directly into the background.
From The App Life Cycle:
In addition, an app being launched directly into the background enters [the background state] instead of the inactive state.
Debugging an app launched in the background:
As for attaching to your app when it is launched in the background from a terminated state, there's a question which already provides the correct approach for this scenario.
If you have specific questions about that, please leave a comment with the author of that answer.

How to ensure iOS app doesn't remain suspended in the background indefinitely?

Our iOS app retrieves new app data every time it is freshly started i.e. not resumed from the background. App data is updated periodically every couple of months via web services so this is generally fine.
However, there may be edge cases where the user's iOS device - iPad, specifically - may keep the app suspended in the background for an extended period of time - potentially indefinitely.
Is it possible to mitigate this edge case by telling iOS "please release this app if it has been suspended for more than a few hours"?
The issue you describe is due to poor app design or a poor understanding of app architecture. If you need to refresh app data whenever the app becomes active you can simply call your update function off of the UIApplicationDelegate event (or register for a notification), specifically:
applicationDidBecomeActive:
Tells the delegate that the application has become active.
- (void)applicationDidBecomeActive:(UIApplication *)application Parameters
application
The singleton application instance.
Discussion
This method is called to let your application know that it moved from
the inactive to active state. This can occur because your application
was launched by the user or the system. Applications can also return
to the active state if the user chooses to ignore an interruption
(such as an incoming phone call or SMS message) that sent the
application temporarily to the inactive state.
You should use this method to restart any tasks that were paused (or
not yet started) while the application was inactive. For example, you
could use it to restart timers or throttle up OpenGL ES frame rates.
If your application was previously in the background, you could also
use it to refresh your application’s user interface.
After calling this method, the application also posts a
UIApplicationDidBecomeActiveNotification notification to give
interested objects a chance to respond to the transition. Availability
When the app is suspended it shouldn't be refreshing. Per Apple's documentation, unless your app has registered for one of the specific background processes, the app is essentially frozen until it resumes. There shouldn't be any network calls made.
However, if you DO want to kill the app once it's been suspended for too long, you could implement a hack that registers a background timer for 10 minutes, then after 10 minutes call some garbage code that you know will crash. Problem solved :)

Resources