• Home
  • Mastering App Life Cycle: A Comprehensive Guide to Implementing SwiftUI

With this in-depth guide, learn how to implement SwiftUI and manage your app’s life cycle effectively. Discover the best practices and techniques to take your iOS app development to the next level.

When you are using an iOS application you have experienced some app states like,

  • The app is visible on the screen.
  • The app is in the background.
  • The app isn’t visible on the screen or background (Multitasking UI).

But do you know, Apple has defined 5 execution states of an iOS application? The OS transitions between these states by considering the user’s action.

The execution states can be listed as below,

  • Not Running
  • Inactive
  • Active
  • Background
  • Suspended

Among these 5 app states only the InactiveActive, and Background states and considered as the running states.

  1. Not Running

This is the basic app state of an iOS application.

  • This means the app has not launched yet or the app has been terminated by the user.
  • Also, there is a possibility that the app has been terminated by the OS (This will be explained in the Suspended state explanation)

So, if the app is not available on the screen or background among the other apps, that means the app is in the Not Running state.

2. Inactive

This state occurs while the app transitioning to the Active state or transitioning from the Active state in the foreground. This means that this state occurs only while transitioning from one state to another.

Additionally to the app state transitions the Inactive app state can occur while running the app if a phone call is received or activated Siri.

These are the app state transitions that transit through the Inactive state,

  • Not Running -> Inactive -> Active
  • Active -> Inactive -> Background
  • Background -> Inactive -> Active

If you are holding your app on the multitasking UI, the OS keeps the app in the Inactive state and transits to the next state by considering the next user’s action on the app.

What are the actions that can be taken place from the multitasking UI,

  • Open another app from the multitasking UI and the app goes to the background.
  • Directly move the app to the background.
  • Screen off by clicking on the power button and the app goes to the background. (After that if you are on the screen, the app will come to the foreground automatically.)
  • Swipe the app and terminate.
  • Open the app again.

In the Inactive app state, the application isn’t receiving any events (The UI is also not accessible in this state. So, so the UI events are not available.) However, the app can continuously run the code on the task that is executing.

3. Active

This is the main execution state of an iOS application. The app is running in the foreground and the UI is accessible. It receives any events and executes the code. As shown in the Inactive app state explanation, Transition from any of app state to this state occurs only through the Inactive app state.

4. Background

Once the app moves to the background it will be transited to the Background state. In this state, still, the app can still execute the code and receive the events (Not UI events).

But in a limited period, the OS automatically moves the app to the Suspended state and stops execution of code. When you are using iOS 12, the app remains in the Background state for around 180 seconds. But it can be different on other iOS versions. This is given to facilitate us to finish any task which was executed. But we can extend the execution time in the background by using beginBackgroundTask(expirationHandler:) or  beginBackgroundTask(withName:expirationHandler:).

5. Suspended

This is the app state when your app is on background after the app has been transited from the Background state by the OS. At this app state, your app remains in the memory. But not executing any code. Since the app is not executing the code, the battery life is not affected by the application. But if any running app needs memory and the system is low on memory, the OS will terminate some suspended apps to free the memory.

The transition to move to the Suspended state or terminate the app by the system from the Suspended state is not notified by the OS.

You can see the Active state is only accessible through the Inactive app state. The state transition through the Suspended has been indicated by dotted arrows, as it is not notified by the OS. Now it’s the time to handle this app state transition in our app.

Why do we need to handle the app state transitions?

As an iOS developer, it is important to be familiar with the app state transitions and handle those. Why am I saying that,

Just imagine,

  • You are calling an API and retrieving some data from your backend and you are using a timer to execute that. What if the app goes background and after some time app comes to the foreground again?
  • You are using CoreData and you have some dirty objects to be saved. But the user terminates the app?
  • You need to download some files whether the app on the foreground or background.

Above are some examples that you can be faced with confusion about data lost or task executions. So, you have to have a good understanding of handing the app state transitions.

Implementing SwiftUI with App Life Cycle

To implement SwiftUI with the app life cycle, we need to understand how to handle each stage of the app life cycle. Let’s create a simple SwiftUI app that demonstrates how to handle each stage.

Launch

When the app is launched, we need to initialize the app and load the main SwiftUI view. We can do this by creating a struct that conforms to the App protocol:

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

In this example, we create a MyApp struct that conforms to the App protocol. The body property returns a WindowGroup that contains a ContentView.

Active

When the app is active, we need to handle user interactions and update the UI accordingly. We can do this by using SwiftUI’s built-in @State and @Binding property wrappers:

struct ContentView: View {
    @State private var counter = 0

    var body: some View {
        VStack {
            Button("Increment") {
                counter += 1
            }
            Text("Count: \(counter)")
        }
    }
}

In this example, we create a ContentView struct that has a counter property with a default value of 0. We use the @State property wrapper to indicate that the counter property should be stored and updated by the system. When the user taps the “Increment” button, the counter property is updated, and the UI is updated accordingly.

Inactive

When the app is inactive, we need to handle interruptions such as incoming calls or texts. We can do this by using the onDisappear modifier:

struct ContentView: View {
    @State private var counter = 0

    var body: some View {
        VStack {
            Button("Increment") {
                counter += 1
            }
            Text("Count: \(counter)")
        }
        .onDisappear {
            print("App is inactive")
        }
    }
}

In this example, we add an onDisappear modifier to the VStack that prints a message to the console when the app becomes inactive.

Background

When the app is running in the background, we need to perform tasks such as updating the app’s data. We can do this by using the BackgroundTask framework:

import BackgroundTask

struct ContentView: View {
    @State private var counter = 0

    var body: some View {
        VStack {
            Button("Increment") {
                counter += 1
            }
            Text("Count: \(counter)")
        }
        .onAppear {
            BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.app.backgroundtask", using: .background) { task in
                print("App is running in the background")
                // Perform background tasks here
            }
        }
    }
}

In this example, we use the BackgroundTask framework to register a background task that prints a message to the console when the app is running in the background.

Suspended

When the app is suspended, we need to release any system resources and prepare for termination. We can do this by using the onSuspend modifier:

struct ContentView: View {
    @State private var counter = 0

    var body: some View {
        VStack {
            Button("Increment") {
                counter += 1
            }
            Text("Count: \(counter)")
        }
        .onSuspend {
            print("App is suspended")
            // Release system resources here
        }
    }
}

In this example, we add an onSuspend modifier to the VStack that prints a message to the console when the app is suspended.

Terminated

When the app is terminated, we need to release all system resources and clean up any remaining data. We can do this by using the onTerminate modifier:

struct ContentView: View {
    @State private var counter = 0

    var body: some View {
        VStack {
            Button("Increment") {
                counter += 1
            }
            Text("Count: \(counter)")
        }
        .onTerminate {
            print("App is terminated")
            // Release all system resources here
        }
    }
}

In this example, we add an onTerminate modifier to the VStack that prints a message to the console when the app is terminated.

Mapping app states with Code:

The flow of the app life cycle from launch to suspended states:

The Main Run Loop:

  • An app’s main run loop processes all user-related events.
  • App delegate sets up the main run loop at launch time and uses it to process events and handle updates to view-based interfaces.
  • the main run loop executes on the app’s main thread
  • main thread is a serial thread and this ensures that user-related events are processed serially in the order in which they were received.

Interview Questions on App life cycle:-

  1. How to background the iOS app resume in the foreground?

When a user launches an app that is currently in the background, the system moves the app to the inactive state and then to the active state.

2. What are the steps involved when the app enters to foreground after the device reboots?

When a user launches an app for the first time after the device reboots or after the system terminates the app, the system moves the app to the active state.

3. What are the steps involved when the app moves from foreground to background?

4. How can you opt out of background execution?

  • You can explicitly opt out of background execution by adding UIApplicationExitsOnSuspend key to application’s Info.plist file and setting its value to YES.
  • When you opt-out background state, the app life cycles will be between the not running, inactive, and active states and never enter the background or suspended states.

5. What is the app state when the device is rebooted?

Ans: Not Running state.

6. When the app is running but not receiving the event. In which state app is in?

Ans: Inactive state.

7. How does an iOS app respond to interruptions like SMS, Incoming Calls, Calendar, etc.?

Application moves to the inactive state temporarily and it remains in this state until the user decides whether to accept or ignore the interruption.

  • If the user ignores the interruption, the application is reactivated.
  • If the user accepts the interruption, the application moves into the suspended state.

8. What are the uses of background state?

  • It allows saving any application data that will help users to relaunch the app where they left off.
  • App releases any resources that don’t need.

9. How can you add additional background execution time for your app?

  • The app can stay in the background for a few seconds and you can execute any code within that time.
  • You can call beginBackgroundTask(expirationHandler handler: (() -> Void)? = nil) method which requests additional background execution time for your app
  • To extend the execution time of an app extension, use the performExpiringActivity(withReason:using:) method

10. How can you check the maximum amount of time available for the app in the background

backgroundTimeRemaining to get the maximum amount of time remaining for the app to run in the background?

The value is valid only after the app enters the background and has started at least one task using beginBackgroundTask(expirationHandler:) in the foreground.

11. How can you debug your background task?

beginBackgroundTask(withName:expirationHandler:) for debugging background task.

Best Practices

  • Use SwiftUI’s built-in @State and @Binding property wrappers to handle user interactions and update the UI accordingly.
  • Use the onDisappear modifier to handle interruptions such as incoming calls or texts.
  • Use the BackgroundTask framework to perform tasks such as updating the app’s data when the app is running in the background.
  • Use the onSuspend modifier to release system resources and prepare for termination when the app is suspended.
  • Use the onTerminate modifier to release all system resources and clean up any remaining data when the app is terminated.

By following these best practices, you can create a robust and efficient SwiftUI app that integrates seamlessly with the app life cycle.

Author: Muhammad Talha Waseem

Leave Comment