iOS App Lifecycle Explained
How the iOS lifecycle works, and how we can interact with it.
This week an interesting question has been raised in Bending Spoons (virtual) office about how the iOS Lifecycle works. To properly frame the problem, you need to know that we can interact with the lifecycle of iOS in two different ways:
- By overriding some methods of the AppDelegate (like the usual
application(_:didFinishLaunchingWithOptions:)) to respond to changes in the steps of the lifecycle.
- By checking the
UIApplication.shared.applicationStateto know in which state the app is in a given moment in time. This enum can assume three different values:
background. Note: this property can be accessed only from the main thread.
Note: there is another mechanism to respond to the lifecycle changes. We can register some observer to the
NotificationCenter. We can then react to
Notifications whose name belongs to the
UIApplicationnamespace. However, this method is equivalent to override methods in the
Now, the question was: what is the relationship between the
UIApplication.State and the
AppDelegate’s methods? How does the former changes when those methods are invoked?
Weirdly enough, we could not find any relevant piece of documentation that explains that relationship clearly. We found a lot of information about the app lifecycle. We found documentation on the
UIApplication.State property, but we could not find a piece of documentation that put those together explicitly.
Therefore, I decided to set up a small experiment to learn how the two interact.
The experiment is simple, and it recalls a bit of what I did when I was exploring how much background time we have once we put the app in the background.
We want to create an elementary app that can collect the events fired by iOS when a lifecycle event occurs and that can associate to them the current value of the
UIApplication.State property. We decorate the event with a timestamp to depict their timeline. The last requirement is to store them immediately on the disk to ensure we do not lose anything, especially when investigating edge cases.
The app's last step is the UI: I used a plain table view, where each event is rendered in a cell.
Preparing the Models
The models for this app are just two: we need to store the events in a state that can be encoded and stored on the disk. Every event has three property: a
date and the associated
The code that represents these models is the following:
In the snippet, you can see a couple of extra details:
Event.Kindis an enumeration that contains all the lifecycle events we want to track.
- I added a couple of extensions that will help us create a new
Statestarting from the previous one by adding a new event. And I added another
initfor the events.
Storing and Retrieving the State
Another essential ingredient for the app is a mechanism to:
- store the
Statewhenever it changes.
- retrieve it when the app is launched.
For simplicity’s sake, I decided to use the
UserDefault as a storage for the
The code to store and retrieve the state looks like this:
In this snippet, we create a static key to store the information in the
UserDefaults. Then, in the
retrieveState() method, we access the
UserDefaults and we try to get and decode the data associated with that key. In case there is no data, we create a new struct for this launch.
store(state:) method, we encode the struct and we store it in the user defaults.
The app uses these methods in the
AppDelegate: it defines a variable
var state: State that, when set, automatically stores the new value in the
UserDefaults. Then, it overrides the
application(_:willFinishLaunchingWithOptions:) to create the state as soon as possible.
Note: we are using the
Stateand not the more common
application(_:didFinishLaunchingWithOptions:). The former runs before the latter and if we create the state in the latter, we cannot track the former event.
The code to glue everything together is the following.
At line 7, you can see that every time the state changes, we also update the state of the
ViewController to reflect updates in the UI.
Tracking the Events
The last and most important piece of code consists of tracking all the events we are interested in. From the
Event.Kind enum, we know that these events are:
we can easily override all these methods in the
AppDelegate directly. The code looks like this:
The most interesting parts of this snippet are:
application(_:didFinishLaunchingWithOptions:)method that has the responsibility to create a
ViewControllerand make it visible on the screen
updateState(with:event:)that packs together a couple of instructions that would have been repeated over and over otherwise.
Running the app
Now that everything is in place, we can run the app and see how the
UIApplication.State changes with the evolution of the lifecycle events.
From this short video, you can see that the sequence of events when the app starts is:
appWillFinishLaunching-> with a
appDidFinishLaunching-> with a
appDidBecomeActive-> with a
Notice that, during all these events, the app is in the foreground and visible.
When, instead, we move the app in the background, and then we come back, the event sequence is:
appWillResignActive-> with a
appDidEnterBackground-> with a
appWillEnterForeground-> with a
appDidBecomeActive-> with a
Notice that, in this case, the app is never
inactive but, instead, it passes from
background and vice-versa. Also, notice that when the
appWillEnterForeground is executed, the app is still in the background!
There are a couple of edge cases that are worth investigating. What happens when the app is started in the background due to a silent notification or due to a background-fetch? What happens when the user invokes the app switcher?
Let’s explore these two cases!
Modern applications can be launched by the system to prepare the user's data as soon as they open the app or to download data from a server when new content is available.
All of this computation happens in the background, without the user even noticing it. The system launches the app, letting it perform some tasks for a fixed amount of time, and then it puts the app back to sleep. Which events are invoked? How the
UIApplication.State evolves in those circumstances?
Luckily, the code we wrote so far is also suitable for this case. What we need to do is to simulate one of these events. To simulate a background fetch in Xcode we need to:
- Select the project in the
- Select the
Signing and Capabilitiestab
- Press the
- Double-click on
- In the new panel, select
These steps allow us to trigger a background fetch. Now, we just have to launch the app in that mode. We can do that by operating on the schema.
- Click on the app name, next to the Xcode play button.
- Check the
Launch due to a background fetchoption
Now, just launch the app. You won’t see the app pop up. Instead, you will see the icon appear in the simulator. Click on the icon to launch the app, and you’ll see something like this:
As you can see, in that case, both the
willFinishLaunching and the
didFinishLaunching have a
UIApplication.State that is
background while the only event which has
active is when the app actually becomes active. Notice that the app is never
inactive in this execution.
The last, interesting thing to analyze is what happens when the user opens the app switcher. Our code is already set up to track all the events so we just have to trigger the action in the simulator.
I found this step a little bit tricky: although the simulator said that the
App Switcher can be triggered by pressing the
^+⇧+⌘+H shortcut… It does not work every time. However, once you manage to pull it off, what you can see is that the first event triggered is the
appWillResignActive event, with an associated value of
active. If then we come back to the app, the following event is the
appDidBecomeActive with a value of
In this article, we explored two mechanisms to interact with the app lifecycle. These mechanisms are different and, therefore, should be used for different tasks. However, they are related in a very strict way: when a lifecycle method is invoked, the
UIApplication.State can change… and not always with a value we would expect.