ios-swift
/

iOS App Lifecycle – From Launch to Termination

Last Sync: Today

On this page

10
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

ios-swift

iOS App Lifecycle – From Launch to Termination

The iOS App Lifecycle – Overview

The iOS app lifecycle describes the sequence of states an app transitions through from launch to termination. Understanding these states is critical for managing resources, saving user data, and providing a seamless experience. In 2026, iOS supports two lifecycle paradigms: UIKit App Delegate (legacy, still common) and SwiftUI App Protocol (modern). Additionally, Scene-based lifecycle (introduced in iOS 13) handles multi-window apps on iPad and macOS.

  1. UIKit App Lifecycle – UIApplicationDelegate

The traditional UIKit lifecycle uses UIApplicationDelegate methods. These are called automatically by the system at specific moments. Below is the complete flow with modern iOS 15+ methods.

SWIFTRead-only
1
// AppDelegate.swift – UIKit lifecycle
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    // 1. Launch – app starts
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        print("1. App finished launching")
        // Setup: Crash reporting, analytics, dependency injection
        return true
    }
    
    // 2. About to become active (receiving events)
    func applicationDidBecomeActive(_ application: UIApplication) {
        print("2. App became active – user can interact")
        // Resume animations, timers, refresh data
    }
    
    // 3. Will resign active (incoming call, user swipes to home)
    func applicationWillResignActive(_ application: UIApplication) {
        print("3. App will resign active – pause tasks")
        // Pause game, stop timers, throttle network requests
    }
    
    // 4. Entered background – app still in memory
    func applicationDidEnterBackground(_ application: UIApplication) {
        print("4. App entered background – save state")
        // Save user data, release resources, invalidate timers
        // You have ~5 seconds to complete tasks before suspension
    }
    
    // 5. Will enter foreground (coming back from background)
    func applicationWillEnterForeground(_ application: UIApplication) {
        print("5. App will enter foreground – prepare UI")
        // Undo background changes, refresh data
    }
    
    // 6. Will terminate (rare – user swipes up from app switcher)
    func applicationWillTerminate(_ application: UIApplication) {
        print("6. App will terminate – final cleanup")
        // Save final state (though background entry already saved)
    }
    
    // Memory warning – critical!
    func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
        print("⚠️ Memory warning – clear caches immediately")
        // Clear image caches, release large resources
    }
}

  1. SwiftUI App Lifecycle – App Protocol

SwiftUI apps use the App protocol with scene lifecycle. No AppDelegate needed (unless integrating UIKit features). Modern SwiftUI apps respond to scene phases via @Environment(\.scenePhase).

SWIFTRead-only
1
// SwiftUI App – iOS 14+
@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onChange(of: scenePhase) { oldPhase, newPhase in
                    switch newPhase {
                    case .active:
                        print("App is active – user can interact")
                        // Resume tasks
                    case .inactive:
                        print("App is inactive – transitioning")
                        // Pause but don't save yet
                    case .background:
                        print("App in background – save state")
                        // Save user data, commit transactions
                    @unknown default:
                        break
                    }
                }
        }
    }
}

// Observing scene phase in any view
struct ContentView: View {
    @Environment(\.scenePhase) var scenePhase
    
    var body: some View {
        Text("Current phase: \(String(describing: scenePhase))")
            .onChange(of: scenePhase) { _, newPhase in
                if newPhase == .background {
                    // Save data specifically from this view
                }
            }
    }
}

  1. Scene-Based Lifecycle (iOS 13+)

Introduced for iPad multitasking and macOS Catalyst. A single app can have multiple scenes (windows). Each scene has its own independent lifecycle state, while the app delegate handles shared resources.

SWIFTRead-only
1
// SceneDelegate.swift – UIKit scene lifecycle
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    // Scene attached to the app
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        print("Scene connecting – setup UI")
        guard let _ = (scene as? UIWindowScene) else { return }
    }
    
    // Scene entering foreground (becoming visible)
    func sceneWillEnterForeground(_ scene: UIScene) {
        print("Scene will enter foreground")
    }
    
    // Scene became active (receiving events)
    func sceneDidBecomeActive(_ scene: UIScene) {
        print("Scene active – ready for interaction")
    }
    
    // Scene resigning active (will lose focus)
    func sceneWillResignActive(_ scene: UIScene) {
        print("Scene resigning active – pause")
    }
    
    // Scene moved to background
    func sceneDidEnterBackground(_ scene: UIScene) {
        print("Scene in background – save state")
        // Save scene-specific state for restoration
    }
    
    // Scene disconnecting (closed by user)
    func sceneDidDisconnect(_ scene: UIScene) {
        print("Scene disconnected – cleanup scene resources")
        // Release scene-specific caches
    }
}

// Supporting multiple scenes in Info.plist
// Add: UIApplicationSceneManifest → Supports multiple scenes: YES

  1. State Transitions – Visual Diagram

Understanding state transitions helps you choose the right method for saving data or cleaning up resources.

TEXTRead-only
1
                         ┌─────────────────┐
                         │   Not Running    │
                         │   (Terminated)   │
                         └────────┬────────┘
                                  │ User taps icon
                                  │ or push notification
                                  ▼
                         ┌─────────────────┐
          ┌─────────────►│    Inactive      │◄─────────────┐
          │              │ (Launch/Interrupt)│              │
          │              └────────┬────────┘              │
          │                       │                       │
          │ User finishes         │ Becomes active        │ Incoming call,
          │ multitasking          │                       │ notification shade
          │                       ▼                       │
          │              ┌─────────────────┐              │
          │              │     Active       │              │
          │              │  (Foreground)    │              │
          │              └────────┬────────┘              │
          │                       │                       │
          │ User presses          │ User presses          │
          │ Home button           │ Home button           │
          │ (multi-task)          │ (multi-task)          │
          │                       │                       │
          │              ┌────────┴────────┐              │
          │              │                 │              │
          │              ▼                 ▼              │
          │     ┌──────────────┐   ┌──────────────┐       │
          │     │   Inactive   │   │  Background  │       │
          │     │ (Transition) │──►│  (Suspended) │       │
          │     └──────────────┘   └──────────────┘       │
          │              │                 │              │
          │              └─────────────────┘              │
          │                       │                       │
          │                User swipes up                 │
          │               from app switcher               │
          │                       │                       │
          └───────────────────────┴───────────────────────┘
                                  │
                                  ▼
                         ┌─────────────────┐
                         │   Terminated    │
                         │  (Purged from   │
                         │    memory)      │
                         └─────────────────┘

  1. Background Execution & Tasks

iOS limits background execution time to preserve battery. Use BGTaskScheduler for long-running background work (iOS 13+). Legacy beginBackgroundTask is still supported but limited.

SWIFTRead-only
1
// Modern background tasks – BGTaskScheduler
import BackgroundTasks

class BackgroundTaskManager {
    static let shared = BackgroundTaskManager()
    
    func registerTasks() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.app.refresh",
            using: nil
        ) { task in
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }
    }
    
    func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 3600) // 1 hour
        
        do {
            try BGTaskScheduler.shared.submit(request)
            print("Background refresh scheduled")
        } catch {
            print("Could not schedule: \(error)")
        }
    }
    
    private func handleAppRefresh(task: BGAppRefreshTask) {
        // Schedule next refresh before doing work
        scheduleAppRefresh()
        
        // Expiration handler – cleanup if system kills us
        task.expirationHandler = {
            print("Background task expired – cancel work")
            // Cancel ongoing operations
        }
        
        // Perform background work (fetch data, sync)
        Task {
            await fetchLatestData()
            task.setTaskCompleted(success: true)
        }
    }
    
    func fetchLatestData() async {
        // Network call, database sync, etc.
    }
}

// Legacy background task (iOS 4-12)
func startBackgroundTask() {
    var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
    backgroundTaskID = UIApplication.shared.beginBackgroundTask {
        // Expiration handler
        UIApplication.shared.endBackgroundTask(backgroundTaskID)
        backgroundTaskID = .invalid
    }
    
    DispatchQueue.global().async {
        // Perform work
        
        UIApplication.shared.endBackgroundTask(backgroundTaskID)
        backgroundTaskID = .invalid
    }
}

  1. State Preservation & Restoration

iOS can terminate your app in the background to reclaim memory. State restoration preserves the UI state so users return to exactly where they left off, even after termination.

SWIFTRead-only
1
// UIKit State Restoration
class ViewController: UIViewController {
    
    // Encode state before background
    override func encodeRestorableState(with coder: NSCoder) {
        super.encodeRestorableState(with: coder)
        coder.encode(lastSelectedItemID, forKey: "selectedItemID")
        coder.encode(scrollPosition, forKey: "scrollPosition")
    }
    
    // Decode state after relaunch
    override func decodeRestorableState(with coder: NSCoder) {
        super.decodeRestorableState(with: coder)
        lastSelectedItemID = coder.decodeInteger(forKey: "selectedItemID")
        scrollPosition = coder.decodeCGPoint(forKey: "scrollPosition")
        restoreUI()
    }
    
    // Called after decode – final setup
    override func applicationFinishedRestoringState() {
        print("State restoration complete")
    }
    
    func restoreUI() {
        // Restore scroll position, selected tab, etc.
    }
}

// SwiftUI State Restoration (iOS 15+)
struct DetailView: View {
    @SceneStorage("detail.selectedTab") var selectedTab = 0
    @AppStorage("lastViewedItemID") var lastViewedItemID = ""
    
    var body: some View {
        TabView(selection: $selectedTab) {
            // Tabs automatically restore selection via SceneStorage
        }
    }
}

// Enabling state restoration in Info.plist
// Add: UIApplicationSupportsStateRestoration = YES

  1. Launch Scenarios & Options

Apps launch differently based on how they're opened. Handle each scenario appropriately.

SWIFTRead-only
1
// AppDelegate – Different launch scenarios
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    
    // Cold launch (app not in memory)
    if launchOptions == nil {
        print("Normal cold launch")
    }
    
    // Launched from push notification
    if let pushPayload = launchOptions?[.remoteNotification] as? [String: Any] {
        print("Launched from push: \(pushPayload)")
        handlePushNotification(pushPayload)
    }
    
    // Launched from URL scheme (myapp://something)
    if let url = launchOptions?[.url] as? URL {
        print("Launched from URL: \(url)")
        handleDeepLink(url)
    }
    
    // Launched from shortcut (3D Touch / Home screen quick action)
    if let shortcut = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem {
        print("Launched from shortcut: \(shortcut.type)")
        handleShortcut(shortcut)
    }
    
    // Launched from widget
    if let activity = launchOptions?[.userActivity] as? NSUserActivity {
        print("Launched from widget: \(activity.activityType)")
        handleUserActivity(activity)
    }
    
    return true
}

// Handle URL while app is running (not launched)
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    print("App already running, opened URL: \(url)")
    return true
}

// Handle user activity (handoff, spotlight, Siri)
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]) -> Void) -> Bool {
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
        if let url = userActivity.webpageURL {
            print("Universal link: \(url)")
            return true
        }
    }
    return false
}

  1. Memory Management During Lifecycle

iOS sends memory warnings before terminating apps. Always implement memory warning handlers to clear caches and release large resources.

SWIFTRead-only
1
// UIKit memory warning
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    print("⚠️ Memory warning received")
    
    // Clear image caches
    imageCache.removeAllObjects()
    
    // Release large temporary objects
    temporaryData = nil
    
    // Tell system we can release more
    URLCache.shared.removeAllCachedResponses()
}

// SwiftUI memory monitoring
class MemoryMonitor: ObservableObject {
    @Published var memoryPressure: Bool = false
    
    init() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleMemoryWarning),
            name: UIApplication.didReceiveMemoryWarningNotification,
            object: nil
        )
    }
    
    @objc func handleMemoryWarning() {
        memoryPressure = true
        // Clear caches
    }
}

// Use in SwiftUI view
struct ImageListView: View {
    @StateObject private var monitor = MemoryMonitor()
    
    var body: some View {
        if monitor.memoryPressure {
            Text("Low memory – reducing quality")
                .onAppear {
                    // Load lower resolution images
                }
        }
    }
}

  1. Best Practices & Common Pitfalls

Do's:

  • Save user data in applicationDidEnterBackground (UIKit) or .background scene phase (SwiftUI)
  • Request background time only when necessary (use BGTaskScheduler)
  • Test memory warnings with simulator: Debug → Simulate Memory Warning
  • Handle all launch options (push, URL, shortcuts, widgets)
  • Use @SceneStorage for simple UI state, @AppStorage for user preferences

Don'ts:

  • ❌ Don't assume applicationWillTerminate will be called (it often isn't)
  • ❌ Don't perform heavy work in applicationDidEnterBackground (you have ~5 seconds)
  • ❌ Don't ignore scene lifecycle if supporting iPad multitasking
  • ❌ Don't store sensitive data in UserDefaults (use Keychain)
  • ❌ Don't ignore memory warnings – clear caches aggressively

Test Your Knowledge

Q1
of 4

Which method is always called when an app moves to the background (UIKit)?

A
applicationWillTerminate
B
applicationDidEnterBackground
C
applicationWillResignActive
D
sceneDidDisconnect
Q2
of 4

In SwiftUI, how do you observe the current app state (active/inactive/background)?

A
UIApplication.shared.applicationState
B
@Environment(\.scenePhase)
C
NotificationCenter.default.addObserver
D
AppDelegate.applicationDidBecomeActive
Q3
of 4

What is the recommended way to perform long-running background tasks in iOS 13+?

A
beginBackgroundTask
B
DispatchQueue.global().async
C
BGTaskScheduler
D
performSelector(inBackground:)
Q4
of 4

Which method is called when the system is running low on memory?

A
applicationWillTerminate
B
applicationDidReceiveMemoryWarning
C
sceneDidEnterBackground
D
applicationWillResignActive

Frequently Asked Questions

What's the difference between applicationDidEnterBackground and applicationWillTerminate?

applicationDidEnterBackground is always called when the app moves to the background. applicationWillTerminate is rarely called – only when the user explicitly kills the app from the app switcher or the system terminates a suspended app. Always save critical data in didEnterBackground, not willTerminate.

How long does an app have to execute background tasks?

UIKit gives approximately 30 seconds for beginBackgroundTask. BGTaskScheduler can run for minutes but is limited by system discretion. Apps in the background can also use specific background modes (audio, location, fetch, push) for extended runtime.

Does SwiftUI's @SceneStorage persist after app termination?

Yes, @SceneStorage automatically persists state across app launches, similar to state restoration in UIKit. It's scoped to the scene (window) and works for simple value types. For complex data, use @AppStorage or your own persistence layer.

Why is my app sometimes launched directly to the background?

When launched by background events (location update, Bluetooth event, push notification with content-available), the app launches directly into the background state. Check UIApplication.shared.applicationState == .background in didFinishLaunchingWithOptions and avoid showing UI.

How do I test background behavior in the simulator?

Use the simulator's Debug → Simulate Background Fetch or Send Memory Warning. For scene lifecycle, cmd+shift+H (home) then cmd+shift+HH (app switcher). For push notifications, use xcrun simctl push with a JSON payload.

Previous

ios project structure

Next

ios uikit

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.