Developers tend to neglect the importance of states an app can be in. Application states make an important part of the app lifecycle, and they should be addressed properly. This article addresses this problem. Additionally, a demo project is provided demonstrating what is referred to in the following article.
So, to recap, an iOS app can be in one of the five different states:
- Not running
- Active
- Inactive
- Background
- Suspended
The state diagram below describes transitions between the states.
As an indication that the app is about to transition to a different state, there are different callbacks available in UIApplicationDelegate:
- applicationWillTerminate
- applicationWillResignActive
- applicationWillBecomeActive
- applicationDidEnterBackground
There is no callback when the app is transiting from background to suspended state or from suspended to terminated state.
Depending on your app’s complexity, handling each state can be quite complex. That makes AppDelegate
class complex, which in turn, makes testing also complex.
How to deal with this problem? The answer can be quite simple: a state machine. In its official documentation regarding handling app states, Apple mentions the word ‘state’ 48 times, and not once did I see someone applying the state machine concept to this area.
For apps that have a complex handling of different states, state patterns can make your life easier.
So what are the apps that actually do have complex state handling? Some examples are apps featuring:
- games – dropping frames and pausing whole game when going to background, resuming game when coming back from background
- voip – handling incoming call in background, continuing call while in background, answering call while in background…
- security – blurring some sensitive content on the screen before app enters background and iOS captures screen for multi-task purpose
- background tasks – scheduling any work to be done in background
The state machine
So, how would a state diagram solution look for an iOS app? On an image below is a proposed state diagram.
You might expect 5 states on the diagram, but there are actually 7, all explained below the AppDelegate
code sample:
1class AppDelegate: UIResponder, UIApplicationDelegate {
2
3 var window: UIWindow?
4
5 var _currentState: AppState = AppStateInit()
6 var currentState: AppState {
7 get {
8 return _currentState
9 }
10 set(newVal) {
11 _currentState.leaveState()
12 _currentState = newVal
13 _currentState.enterState()
14 }
15 }
16
17 func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
18 return (currentState as! AppStateInit).willFinishLaunchingWithOptions()
19 }
20
21 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
22 return (currentState as! AppStateInit).didFinishLaunchingWithOptions()
23 }
24
25 func applicationWillResignActive(application: UIApplication) {
26 currentState = AppStateResignActive()
27 }
28
29 func applicationDidEnterBackground(application: UIApplication) {
30 currentState = AppStateBackground()
31 }
32
33 func applicationWillEnterForeground(application: UIApplication) {
34 currentState = AppStateWakeUp()
35 }
36
37 func applicationDidBecomeActive(application: UIApplication) {
38 currentState = AppStateActive()
39 }
40
41 func applicationWillTerminate(application: UIApplication) {
42 currentState = AppStateTerminating()
43 }
44}
Init state
This is a brief state existing while an app is launching. It does the work of willFinishLaunchingWithOptions
and didFinishLaunchingWithOptions
methods. Some examples of what this state is responsible for:
- setting up the app
- showing initial view, ie Login view if user hasn’t be logged in already, or ‘Main’ view if auto-login is supported.
- registering for Apple Push Notifications
- registering launch shortcuts
- other one-time-per-app-lifetime actions
Active state
This is the state where an app is shown on the device’s screen and the app is receiving user touches. The only way to enter this state is from inactive state, except when app is launching. Then the app transits directly from Init to Active state.
In this state we usually want to start the timers which were paused when leaving the active state.
1class AppStateActive: AppState {
2
3 override func enterState() {
4 super.enterState()
5 // 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.
6 }
7}
Inactive state
Inactive state is a brief state appearing while the app is leaving or entering the active state. While inactive, the app is updating the UI but it is not receiving user touches.
The inactive state can be triggered by showing the Notifications view from the status bar, or by showing ‘Control centre’ from the bottom (see video further down).
In our model, there are two Inactive states:
- Resign Active – app is leaving active state and entering background state
- Wake Up – app has been in background state and is about to become active
We want to differentiate these because different actions are taken in each case. Additionally, different callbacks are called in AppDelegate
to let us know about each state: application:willResignActive
and application:willEnterForeground
.
Resign Active state
When the app enters resign-active state we may want to do the following:
- pause any timers that were running (ie pause game)
- throttle down OpenGL frames
- prepare the app for taking screenshot for multi-task menu: if the app shows sensitive or secure data, it may be good to blur that content or just show single-colored view with app logo in the centre
Wake up state
This state is like resign-active state but with a different direction: an app has been backgrounded and is about to become active. In this state we usually want to undo any changes made when entering the background. It is easy to separate this state from resign-active because iOS provides a special callback for it: application:willEnterForeground
.
1class AppStateInactive: AppState {
2
3 override func enterState() {
4 super.enterState()
5 // 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.
6 // 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.
7 }
8}
9
10class AppStateWakeUp: AppStateInactive {
11
12 override func enterState() {
13 super.enterState()
14 // undo any changes made when entering the background
15 }
16}
17
18class AppStateResignActive: AppStateInactive {
19
20 override func enterState() {
21 super.enterState()
22 // undo any changes made when entering the background
23 }
24}
Background state
This state is triggered by the application:didEnterBackground
callback. This state usually lasts about 10 seconds unless more time was requested from the system, in which case the app gets 180 seconds of background. VoIP apps can have longer background state. This state can be used for:
- saving any persistent data that should be saved
- saving enough information about the app state so that it can be restored at later point, in case it gets killed by iOS
- you may want to pause timers when this state is entered (instead of pausing them when entering Inactive (Resign Active) state). This depends on what app’s timers actually do and should UI be updated while the app is in inactive state
1class AppStateBackground : AppState {
2
3 override func enterState() {
4 super.enterState()
5 // 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.
6 // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
7 }
8
9 override func leaveState() {
10 super.leaveState()
11 // can leave to terminated or wake-up
12
13 // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
14 }
15}
Terminating state
This is a brief state entered just before the app is about to be killed. However, if the app was in ‘Suspended’ state, Terminating state won’t be entered. So don’t rely on this state for any important work: It will only be entered if the app was previously in the background state. In this state, you may want to save any data if it’s appropriate, but it’s better to handle that when entering Background state. This state can be useful when opting-out from the background.
1class AppStateTerminating: AppState {
2
3 override func enterState() {
4 super.enterState()
5 // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
6 }
7}
Below is a video demonstration of most states (the init-state and terminated are not visible)
Below is a class diagram. Notice that AppStateWakeUp
and AppStateResignActive
extend AppStateInactive
, but that need not be done like this. If it suits you, those classes can extend abstract AppState
directly.
Notifications approach
A similar goal can also be accomplished by listening to notifications:
- UIApplicationDidEnterBackgroundNotification
- UIApplicationWillEnterForegroundNotification
- UIApplicationDidBecomeActiveNotification
- UIApplicationWillResignActiveNotification
- UIApplicationWillTerminateNotification
That way, AppDelegate
doesn’t have to implement any of the ‘transition’ methods, but every state would have to listen for these changes or some kind of mediator would have to be introduced which will set the current state.
Benefits
Okay, so here are a few benefits of breaking app states into a state machine:
- Testability – it’s usually easier to unit-test each state than testing the whole AppDelegate class
- Happier OCLint – less complexity and LOC of the probably already overcomplicated AppDelegate class
- better (or more clear) control of app states
Conclusion
For simple apps (most apps) you usually don’t need any of this. This approach is intended only when an app’s state handling becomes somewhat complicated. I don’t recommend abusing or forcing this design unless you actually need it. I use this approach for complex apps I’ve worked on, and it has proven good for me so far.
Don’t forget to checkout the example .
Useful links
Strategies for Handling App State Transitions
The App Life Cycle
Background Execution
Execution States for a Swift iOS App
More articles
fromMarko Cicak
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Marko Cicak
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.