app-lifecycle

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

App Lifecycle — Expert Decisions

应用生命周期——专家决策

Expert decision frameworks for app lifecycle choices. Claude knows scenePhase and SceneDelegate — this skill provides judgment calls for architecture decisions and background task trade-offs.

应用生命周期选择的专家决策框架。Claude精通scenePhase和SceneDelegate——本技能为架构决策和后台任务权衡提供判断依据。

Decision Trees

决策树

Lifecycle API Selection

生命周期API选择

What's your project setup?
├─ Pure SwiftUI app (iOS 14+)
│  └─ @main App + scenePhase
│     Simplest approach, sufficient for most apps
├─ Need UIKit integration
│  └─ SceneDelegate + UIHostingController
│     Required for some third-party SDKs
├─ Need pre-launch setup
│  └─ AppDelegate + SceneDelegate
│     SDK initialization, remote notifications
└─ Legacy app (pre-iOS 13)
   └─ AppDelegate only
      window property on AppDelegate
The trap: Using SceneDelegate when pure SwiftUI suffices. scenePhase covers most use cases without the boilerplate.
What's your project setup?
├─ Pure SwiftUI app (iOS 14+)
│  └─ @main App + scenePhase
│     Simplest approach, sufficient for most apps
├─ Need UIKit integration
│  └─ SceneDelegate + UIHostingController
│     Required for some third-party SDKs
├─ Need pre-launch setup
│  └─ AppDelegate + SceneDelegate
│     SDK initialization, remote notifications
└─ Legacy app (pre-iOS 13)
   └─ AppDelegate only
      window property on AppDelegate
误区:在纯SwiftUI应用足够满足需求时仍使用SceneDelegate。scenePhase无需样板代码即可覆盖大多数使用场景。

Background Task Strategy

后台任务策略

What work needs to happen in background?
├─ Quick save (< 5 seconds)
│  └─ UIApplication.beginBackgroundTask
│     Request extra time in sceneDidEnterBackground
├─ Network sync (< 30 seconds)
│  └─ BGAppRefreshTask
│     System schedules, best-effort timing
├─ Large download/upload
│  └─ Background URL Session
│     Continues even after app termination
├─ Location tracking
│  └─ Location background mode
│     Significant change or continuous
└─ Long processing (> 30 seconds)
   └─ BGProcessingTask
      Runs during charging, overnight
What work needs to happen in background?
├─ Quick save (< 5 seconds)
│  └─ UIApplication.beginBackgroundTask
│     Request extra time in sceneDidEnterBackground
├─ Network sync (< 30 seconds)
│  └─ BGAppRefreshTask
│     System schedules, best-effort timing
├─ Large download/upload
│  └─ Background URL Session
│     Continues even after app termination
├─ Location tracking
│  └─ Location background mode
│     Significant change or continuous
└─ Long processing (> 30 seconds)
   └─ BGProcessingTask
      Runs during charging, overnight

State Restoration Approach

状态恢复方案

What state needs restoration?
├─ Simple navigation state
│  └─ @SceneStorage
│     Per-scene, automatic, Codable types only
├─ Complex navigation + data
│  └─ @AppStorage + manual encoding
│     More control, cross-scene sharing
├─ UIKit-based navigation
│  └─ State restoration identifiers
│     encodeRestorableState/decodeRestorableState
└─ Don't need restoration
   └─ Start fresh each launch
      Some apps are better this way
What state needs restoration?
├─ Simple navigation state
│  └─ @SceneStorage
│     Per-scene, automatic, Codable types only
├─ Complex navigation + data
│  └─ @AppStorage + manual encoding
│     More control, cross-scene sharing
├─ UIKit-based navigation
│  └─ State restoration identifiers
│     encodeRestorableState/decodeRestorableState
└─ Don't need restoration
   └─ Start fresh each launch
      Some apps are better this way

Launch Optimization Priority

启动优化优先级

What's blocking your launch time?
├─ SDK initialization
│  └─ Defer non-critical SDKs
│     Analytics can wait, auth cannot
├─ Database loading
│  └─ Lazy loading + skeleton UI
│     Show UI immediately, load data async
├─ Network requests
│  └─ Cache + background refresh
│     Never block launch for network
└─ Asset loading
   └─ Progressive loading
      Load visible content first

What's blocking your launch time?
├─ SDK initialization
│  └─ Defer non-critical SDKs
│     Analytics can wait, auth cannot
├─ Database loading
│  └─ Lazy loading + skeleton UI
│     Show UI immediately, load data async
├─ Network requests
│  └─ Cache + background refresh
│     Never block launch for network
└─ Asset loading
   └─ Progressive loading
      Load visible content first

NEVER Do

绝对禁忌

Launch Time

启动时间

NEVER block main thread during launch:
swift
// ❌ UI frozen until network completes
func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let data = try! Data(contentsOf: remoteURL)  // Synchronous network!
    processData(data)
    return true
}

// ✅ Defer non-critical work
func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    setupCriticalServices()  // Auth, crash reporting

    Task.detached(priority: .background) {
        await self.setupNonCriticalServices()  // Analytics, prefetch
    }
    return true
}
NEVER initialize all SDKs synchronously:
swift
// ❌ Each SDK adds to launch time
func application(...) -> Bool {
    AnalyticsSDK.initialize()      // 100ms
    CrashReporterSDK.initialize()  // 50ms
    FeatureFlagsSDK.initialize()   // 200ms
    SocialSDK.initialize()         // 150ms
    // Total: 500ms added to launch!
    return true
}

// ✅ Prioritize and defer
func application(...) -> Bool {
    CrashReporterSDK.initialize()  // Critical — catches launch crashes

    DispatchQueue.main.async {
        AnalyticsSDK.initialize()  // Can wait one runloop
    }

    Task.detached(priority: .utility) {
        FeatureFlagsSDK.initialize()
        SocialSDK.initialize()
    }
    return true
}
绝对不要在启动时阻塞主线程:
swift
// ❌ UI frozen until network completes
func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let data = try! Data(contentsOf: remoteURL)  // Synchronous network!
    processData(data)
    return true
}

// ✅ Defer non-critical work
func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    setupCriticalServices()  // Auth, crash reporting

    Task.detached(priority: .background) {
        await self.setupNonCriticalServices()  // Analytics, prefetch
    }
    return true
}
绝对不要同步初始化所有SDK:
swift
// ❌ Each SDK adds to launch time
func application(...) -> Bool {
    AnalyticsSDK.initialize()      // 100ms
    CrashReporterSDK.initialize()  // 50ms
    FeatureFlagsSDK.initialize()   // 200ms
    SocialSDK.initialize()         // 150ms
    // Total: 500ms added to launch!
    return true
}

// ✅ Prioritize and defer
func application(...) -> Bool {
    CrashReporterSDK.initialize()  // Critical — catches launch crashes

    DispatchQueue.main.async {
        AnalyticsSDK.initialize()  // Can wait one runloop
    }

    Task.detached(priority: .utility) {
        FeatureFlagsSDK.initialize()
        SocialSDK.initialize()
    }
    return true
}

Background Tasks

后台任务

NEVER assume background time is guaranteed:
swift
// ❌ May not complete — iOS can terminate anytime
func sceneDidEnterBackground(_ scene: UIScene) {
    performLongSync()  // No protection!
}

// ✅ Request background time and handle expiration
func sceneDidEnterBackground(_ scene: UIScene) {
    var taskId: UIBackgroundTaskIdentifier = .invalid
    taskId = UIApplication.shared.beginBackgroundTask {
        // Expiration handler — save partial progress
        savePartialProgress()
        UIApplication.shared.endBackgroundTask(taskId)
    }

    Task {
        await performSync()
        UIApplication.shared.endBackgroundTask(taskId)
    }
}
NEVER forget to end background tasks:
swift
// ❌ Leaks background task — iOS may terminate app
func saveData() {
    let taskId = UIApplication.shared.beginBackgroundTask { }
    saveToDatabase()
    // Missing: endBackgroundTask!
}

// ✅ Always end in both success and failure
func saveData() {
    var taskId: UIBackgroundTaskIdentifier = .invalid
    taskId = UIApplication.shared.beginBackgroundTask {
        UIApplication.shared.endBackgroundTask(taskId)
    }

    defer { UIApplication.shared.endBackgroundTask(taskId) }

    do {
        try saveToDatabase()
    } catch {
        Logger.app.error("Save failed: \(error)")
    }
}
绝对不要假设后台执行时间有保障:
swift
// ❌ May not complete — iOS can terminate anytime
func sceneDidEnterBackground(_ scene: UIScene) {
    performLongSync()  // No protection!
}

// ✅ Request background time and handle expiration
func sceneDidEnterBackground(_ scene: UIScene) {
    var taskId: UIBackgroundTaskIdentifier = .invalid
    taskId = UIApplication.shared.beginBackgroundTask {
        // Expiration handler — save partial progress
        savePartialProgress()
        UIApplication.shared.endBackgroundTask(taskId)
    }

    Task {
        await performSync()
        UIApplication.shared.endBackgroundTask(taskId)
    }
}
绝对不要忘记结束后台任务:
swift
// ❌ Leaks background task — iOS may terminate app
func saveData() {
    let taskId = UIApplication.shared.beginBackgroundTask { }
    saveToDatabase()
    // Missing: endBackgroundTask!
}

// ✅ Always end in both success and failure
func saveData() {
    var taskId: UIBackgroundTaskIdentifier = .invalid
    taskId = UIApplication.shared.beginBackgroundTask {
        UIApplication.shared.endBackgroundTask(taskId)
    }

    defer { UIApplication.shared.endBackgroundTask(taskId) }

    do {
        try saveToDatabase()
    } catch {
        Logger.app.error("Save failed: \(error)")
    }
}

State Transitions

状态转换

NEVER trust applicationWillTerminate to be called:
swift
// ❌ May never be called — iOS can kill app without notice
func applicationWillTerminate(_ application: UIApplication) {
    saveCriticalData()  // Not guaranteed to run!
}

// ✅ Save on every background transition
func sceneDidEnterBackground(_ scene: UIScene) {
    saveCriticalData()  // Called reliably
}

// Also save periodically during use
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
    saveApplicationState()
}
NEVER do heavy work in sceneWillResignActive:
swift
// ❌ Blocks app switcher animation
func sceneWillResignActive(_ scene: UIScene) {
    generateThumbnails()  // Visible lag in app switcher
    syncToServer()        // Delays user
}

// ✅ Only pause essential operations
func sceneWillResignActive(_ scene: UIScene) {
    pauseVideoPlayback()
    pauseAnimations()
    // Heavy work goes in sceneDidEnterBackground
}
绝对不要依赖applicationWillTerminate被调用:
swift
// ❌ May never be called — iOS can kill app without notice
func applicationWillTerminate(_ application: UIApplication) {
    saveCriticalData()  // Not guaranteed to run!
}

// ✅ Save on every background transition
func sceneDidEnterBackground(_ scene: UIScene) {
    saveCriticalData()  // Called reliably
}

// Also save periodically during use
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
    saveApplicationState()
}
绝对不要在sceneWillResignActive中执行繁重工作:
swift
// ❌ Blocks app switcher animation
func sceneWillResignActive(_ scene: UIScene) {
    generateThumbnails()  // Visible lag in app switcher
    syncToServer()        // Delays user
}

// ✅ Only pause essential operations
func sceneWillResignActive(_ scene: UIScene) {
    pauseVideoPlayback()
    pauseAnimations()
    // Heavy work goes in sceneDidEnterBackground
}

Scene Lifecycle

场景生命周期

NEVER confuse scene disconnect with app termination:
swift
// ❌ Wrong assumption
func sceneDidDisconnect(_ scene: UIScene) {
    // App is terminating!  <- WRONG
    cleanupEverything()
}

// ✅ Scene disconnect means scene released, not app death
func sceneDidDisconnect(_ scene: UIScene) {
    // Scene being released — save per-scene state
    // App may continue running with other scenes
    // Or system may reconnect this scene later
    saveSceneState(scene)
}

绝对不要混淆场景断开与应用终止:
swift
// ❌ Wrong assumption
func sceneDidDisconnect(_ scene: UIScene) {
    // App is terminating!  <- WRONG
    cleanupEverything()
}

// ✅ Scene disconnect means scene released, not app death
func sceneDidDisconnect(_ scene: UIScene) {
    // Scene being released — save per-scene state
    // App may continue running with other scenes
    // Or system may reconnect this scene later
    saveSceneState(scene)
}

Essential Patterns

核心模式

SwiftUI Lifecycle Handler

SwiftUI生命周期处理器

swift
@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appState)
        }
        .onChange(of: scenePhase) { oldPhase, newPhase in
            handlePhaseChange(from: oldPhase, to: newPhase)
        }
    }

    private func handlePhaseChange(from old: ScenePhase, to new: ScenePhase) {
        switch (old, new) {
        case (_, .active):
            appState.refreshDataIfStale()

        case (.active, .inactive):
            // Transitioning away — pause but don't save yet
            appState.pauseActiveOperations()

        case (_, .background):
            appState.saveState()
            scheduleBackgroundRefresh()

        default:
            break
        }
    }
}
swift
@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appState)
        }
        .onChange(of: scenePhase) { oldPhase, newPhase in
            handlePhaseChange(from: oldPhase, to: newPhase)
        }
    }

    private func handlePhaseChange(from old: ScenePhase, to new: ScenePhase) {
        switch (old, new) {
        case (_, .active):
            appState.refreshDataIfStale()

        case (.active, .inactive):
            // Transitioning away — pause but don't save yet
            appState.pauseActiveOperations()

        case (_, .background):
            appState.saveState()
            scheduleBackgroundRefresh()

        default:
            break
        }
    }
}

Background Task Manager

后台任务管理器

swift
final class BackgroundTaskManager {
    static let shared = BackgroundTaskManager()

    func registerTasks() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.app.refresh",
            using: nil
        ) { task in
            self.handleAppRefresh(task as! BGAppRefreshTask)
        }

        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.app.processing",
            using: nil
        ) { task in
            self.handleProcessing(task as! BGProcessingTask)
        }
    }

    func scheduleRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)

        try? BGTaskScheduler.shared.submit(request)
    }

    private func handleAppRefresh(_ task: BGAppRefreshTask) {
        scheduleRefresh()  // Schedule next refresh

        let refreshTask = Task {
            await performRefresh()
        }

        task.expirationHandler = {
            refreshTask.cancel()
        }

        Task {
            await refreshTask.value
            task.setTaskCompleted(success: true)
        }
    }
}
swift
final class BackgroundTaskManager {
    static let shared = BackgroundTaskManager()

    func registerTasks() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.app.refresh",
            using: nil
        ) { task in
            self.handleAppRefresh(task as! BGAppRefreshTask)
        }

        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.app.processing",
            using: nil
        ) { task in
            self.handleProcessing(task as! BGProcessingTask)
        }
    }

    func scheduleRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)

        try? BGTaskScheduler.shared.submit(request)
    }

    private func handleAppRefresh(_ task: BGAppRefreshTask) {
        scheduleRefresh()  // Schedule next refresh

        let refreshTask = Task {
            await performRefresh()
        }

        task.expirationHandler = {
            refreshTask.cancel()
        }

        Task {
            await refreshTask.value
            task.setTaskCompleted(success: true)
        }
    }
}

Launch Time Optimization

启动时间优化

swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    private var launchStartTime: CFAbsoluteTime = 0

    func application(_ application: UIApplication,
        willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        launchStartTime = CFAbsoluteTimeGetCurrent()

        // Phase 1: Absolute minimum (crash reporting)
        CrashReporter.initialize()

        return true
    }

    func application(_ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Phase 2: Required for first frame
        configureAppearance()

        // Phase 3: Deferred to after first frame
        DispatchQueue.main.async {
            self.completePostLaunchSetup()
            let launchTime = CFAbsoluteTimeGetCurrent() - self.launchStartTime
            Logger.app.info("Launch completed in \(launchTime)s")
        }

        return true
    }

    private func completePostLaunchSetup() {
        // Analytics, feature flags, etc.
        Task.detached(priority: .utility) {
            Analytics.initialize()
            FeatureFlags.refresh()
        }
    }
}

swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    private var launchStartTime: CFAbsoluteTime = 0

    func application(_ application: UIApplication,
        willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        launchStartTime = CFAbsoluteTimeGetCurrent()

        // Phase 1: Absolute minimum (crash reporting)
        CrashReporter.initialize()

        return true
    }

    func application(_ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Phase 2: Required for first frame
        configureAppearance()

        // Phase 3: Deferred to after first frame
        DispatchQueue.main.async {
            self.completePostLaunchSetup()
            let launchTime = CFAbsoluteTimeGetCurrent() - self.launchStartTime
            Logger.app.info("Launch completed in \(launchTime)s")
        }

        return true
    }

    private func completePostLaunchSetup() {
        // Analytics, feature flags, etc.
        Task.detached(priority: .utility) {
            Analytics.initialize()
            FeatureFlags.refresh()
        }
    }
}

Quick Reference

快速参考

Lifecycle Events Order

生命周期事件顺序

EventWhenUse For
willFinishLaunchingBefore UICrash reporting only
didFinishLaunchingUI readyCritical setup
sceneWillEnterForegroundComing to frontUndo background changes
sceneDidBecomeActiveFully activeRefresh, restart tasks
sceneWillResignActiveLosing focusPause playback
sceneDidEnterBackgroundIn backgroundSave state, start bg task
sceneDidDisconnectScene releasedSave scene state
事件触发时机用途
willFinishLaunchingUI加载前仅用于崩溃上报
didFinishLaunchingUI就绪后关键初始化操作
sceneWillEnterForeground应用即将回到前台撤销后台状态变更
sceneDidBecomeActive应用完全激活刷新数据、重启任务
sceneWillResignActive应用即将失去焦点暂停媒体播放
sceneDidEnterBackground应用进入后台保存状态、启动后台任务
sceneDidDisconnect场景被释放保存场景状态

Background Task Limits

后台任务限制

Task TypeTime LimitWhen Runs
beginBackgroundTask~30 secondsImmediately
BGAppRefreshTask~30 secondsSystem discretion
BGProcessingTaskMinutesCharging, overnight
Background URL SessionUnlimitedSystem managed
任务类型时间限制运行时机
beginBackgroundTask~30秒立即执行
BGAppRefreshTask~30秒系统调度
BGProcessingTask数分钟充电时、夜间
Background URL Session无限制系统管理

State Restoration Options

状态恢复选项

ApproachScopeTypesAuto-save
@SceneStoragePer-sceneCodableYes
@AppStorageApp-widePrimitivesYes
Restoration IDPer-VCCustomManual
方案作用范围支持类型自动保存
@SceneStorage单场景仅Codable类型
@AppStorage全应用基本数据类型
恢复ID单个视图控制器自定义类型手动

Red Flags

危险信号

SmellProblemFix
Sync network in launchBlocked UIAsync + skeleton UI
All SDKs in didFinishSlow launchPrioritize + defer
No beginBackgroundTaskWork may not completeAlways request time
Missing endBackgroundTaskLeaked taskUse defer
Heavy work in willResignActiveLaggy app switcherMove to didEnterBackground
Trust applicationWillTerminateMay not be calledSave on background
Confuse sceneDidDisconnectScene != app terminationSave scene state only
问题表现潜在风险修复方案
启动时同步网络请求UI阻塞异步加载+骨架屏
所有SDK都在didFinish中初始化启动缓慢优先级划分+延迟初始化
未使用beginBackgroundTask后台任务可能无法完成始终申请后台执行时间
未结束后台任务任务泄漏导致应用被终止使用defer确保任务结束
willResignActive中执行繁重工作应用切换动画卡顿移至didEnterBackground执行
依赖applicationWillTerminate保存数据数据可能丢失在进入后台时保存数据
混淆sceneDidDisconnect与应用终止错误清理资源仅保存场景状态