文章

iOS 应用生命周期深度解析

iOS 应用是一台状态机:Not Running → Inactive → Active → Background → Suspended,AppDelegate/SceneDelegate/SwiftUI App 三种入口管理它,跨端开发者看懂才能正确处理数据保存与后台任务。

iOS 应用生命周期深度解析

一句话概括

iOS 应用本质是一台严格的状态机:Not Running → Inactive → Active → Background → Suspended,中间状态很短、后台能做的事极少。管理它的入口有三套——传统的 AppDelegate、iOS 13+ 的 SceneDelegate、以及 SwiftUI 的 @main App。对跨端开发者,核心不是背方法名,而是知道「切后台那一刻」是保存数据的最后安全窗口,且后台执行时间只有约 30 秒。

面试一句话:AppDelegate 管应用级事件(启动、前后台、终止),SceneDelegate 管窗口级(多窗口),SwiftUI 用 @main App + scenePhase 把这套状态抽象成 active/inactive/background;Flutter 的 WidgetsBindingObserver 和 RN 的 AppState 都是对这套原生状态的封装。

核心知识点

1. 五个状态 + UIApplicationState

UIApplicationState 只有三个值,但结合「挂起」实际上是五个阶段:

1
2
3
4
5
6
7
8
9
10
11
Not Running(未启动/已终止)
    ↓ 启动
Inactive(前台但未收事件,如来电遮挡、多任务动画)
    ↓
Active(前台可交互)←→ Inactive(来电/锁屏瞬间)
    ↓ 按 Home / 上划
Background(后台,可能还在跑)
    ↓ 系统回收
Suspended(挂起,代码停止、内存保留)
    ↓ 被杀/用户关闭
Not Running
1
2
3
4
5
enum UIApplicationState {
    case active      // 前台可交互
    case inactive    // 过渡态(电话、锁屏、控制中心)
    case background  // 后台(可能在跑,也可能即将挂起)
}

关键认知:Inactive 极短,Suspended 后你的代码完全停了,只有用户返回、推送唤醒、后台模式事件才能把它拉回来。

2. AppDelegate:应用级中枢(UIKit 项目)

直到 iOS 12 这都是唯一入口,现在仍负责应用级事件。最容易考、也最实用的是这几个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 启动完成、界面还没出来:初始化 SDK、注册推送、配全局外观
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    return true
}

// 即将失去焦点(来电/锁屏):暂停游戏、停定时器、暂停音视频
func applicationWillResignActive(_ application: UIApplication) { }

// 进入后台:保存数据、释放大资源——只有约 5 秒必须返回!
func applicationDidEnterBackground(_ application: UIApplication) {
    saveDraft(); clearMemoryCache()
    // 需要更久?必须 beginBackgroundTask(见第 5 节)
}

// 回到前台:重连网络、刷新数据
func applicationWillEnterForeground(_ application: UIApplication) { }

// 重新活跃:恢复动画、重开定时器、ATT 弹窗最佳时机
func applicationDidBecomeActive(_ application: UIApplication) { }

// 终止:被系统回收时【不】调用;别把唯一保存逻辑押在这儿
func applicationWillTerminate(_ application: UIApplication) { }
1
2
// ❌ 在 applicationDidEnterBackground 里做耗时网络同步 → 5 秒没返回被强杀
// ✅ 只做快速保存,长时间任务用 beginBackgroundTask 借时间

3. SceneDelegate:窗口级(iOS 13+ 多窗口)

iPadOS / macOS 多窗口引入 SceneDelegate,把「UI 生命周期」从 AppDelegate 拆出来。分工记一张表:

AppDelegate(应用级)SceneDelegate(窗口/场景级)
启动、远程通知注册每个窗口的 UI 生命周期
深层链接、Handoff场景间状态、窗口大小位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene,
               willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        window = UIWindow(windowScene: windowScene)
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
    }
    func sceneDidBecomeActive(_ scene: UIScene) { }       // 类似 didBecomeActive
    func sceneWillResignActive(_ scene: UIScene) { }      // 类似 willResignActive
    func sceneDidEnterBackground(_ scene: UIScene) { }    // 类似 didEnterBackground
    func sceneDidDisconnect(_ scene: UIScene) { }         // 窗口关了,不是 App 终止
}

iPhone 单窗口 App 用不用 SceneDelegate 都行——Flutter / RN 默认仍能在 iOS 13+ 跑,只是用不上 iPad 多窗口。面试能说清「什么时候才需要它」是加分项。

4. SwiftUI App 生命周期(现代入口)

新项目用 SwiftUI 时,往往看不到 AppDelegate / SceneDelegate,入口是 @main App:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import SwiftUI

@main
struct MyApp: App {
    // 需要访问 UIApplicationDelegate 方法时,用适配器桥接
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// 在 View 里监听前后台状态
struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
    var body: some View {
        Text("Hello")
            .onChange(of: scenePhase) { phase in
                switch phase {
                case .active:    /* 恢复 */ break
                case .inactive:  /* 暂停 */ break
                case .background: /* 保存数据 */ break
                @unknown default: break
                }
            }
    }
}

一句话:SwiftUI 不是没有生命周期,而是用 scenePhase(active/inactive/background)把原生状态抽象掉了,需要原生钩子时再用 UIApplicationDelegateAdaptor 接回 AppDelegate。

5. 后台任务:时间极其有限

iOS 没有 Android 那种常驻 Service。后台能做的很克制:

1
2
3
4
5
6
7
8
9
10
11
// 短时后台:进入后台后向银行「借」约 30 秒
var bgTask: UIBackgroundTaskIdentifier = .invalid
bgTask = UIApplication.shared.beginBackgroundTask(withName: "save") {
    // 超时回调:紧急保存后必须 end
    UIApplication.shared.endBackgroundTask(bgTask); bgTask = .invalid
}
saveCriticalData()
UIApplication.shared.endBackgroundTask(bgTask); bgTask = .invalid

// 长时后台:靠 Background Modes(音频/定位/VoIP)或 BGTaskScheduler
// BGAppRefreshTask(定期刷新)/ BGProcessingTask(大文件处理,iOS 13+)
方式时长适用
beginBackgroundTask约 30 秒(可靠按 15 秒规划)保存数据、收尾
Background Modes音频/定位可较长播放、轨迹
BGTaskScheduler系统空闲时调度刷新、大文件

6. 跨端框架如何映射这套状态

  • Flutter:WidgetsBindingObserver 的 didChangeAppLifecycleState → resumed≈active、inactive≈inactive、paused≈background、detached≈终止。paused 是保存数据的最后安全时机。
  • React Native:AppState 只有 active / inactive / background 三态,背景对应 applicationDidEnterBackground。
1
2
// ❌ 只在 background 时保存 → 若 Inactive 崩溃数据就丢
// ✅ 数据变化即存 + 进后台再存 + 定期自动存,多重保险

其实你每天都在用

  • 填表单接电话回来内容没了:applicationDidEnterBackground / RN background 里没存草稿,用户一切后台数据就飞了
  • 切后台回来网络断了:willEnterForeground 里没重连 WebSocket / 刷新 token
  • 后台播放音乐断掉:没开 Audio 后台模式、没配 AVAudioSession 的 .playback 分类
  • ATT 弹窗时机不对被拒:应该在 applicationDidBecomeActive 之后弹,不是一启动就弹
  • Flutter paused 里做收尾:这就是对应 iOS 进后台,是保存的最后窗口
  • applicationWillTerminate 里才保存:被系统回收时这个方法【不】调用,押宝它会丢数据
  • iPad 上开两个窗口:这就是 SceneDelegate 存在的意义,单窗口 iPhone App 用不到
  • 后台定位只跑了几秒:没开 Location updates 后台模式,也没用 beginBackgroundTask 借时间

常见误解(FAQ)

❌ 误区一:「applicationDidEnterBackground 里可以慢慢做网络同步」

不行。这个方法约 5 秒内必须返回,否则系统认为无响应直接强杀。要做超过 5 秒的事,必须用 beginBackgroundTask 借约 30 秒,且借来的时间也只够「收尾」,不是给你跑长任务。

❌ 误区二:「SwiftUI 项目没有生命周期,不用管」

错。SwiftUI 用 @main App + @Environment(\.scenePhase) 把原生状态抽象成了 active/inactive/background,本质还是那套状态机。需要原生钩子(推送注册、ATT 时机)时用 UIApplicationDelegateAdaptor 桥回 AppDelegate。不懂底层,遇到「切后台丢数据」照样查不出根因。

❌ 误区三:「SceneDelegate 必须用,不然项目不对」

不必。它是为 iPad / Mac Catalyst 多窗口准备的。iPhone 单窗口 App 只在 AppDelegate 管生命周期完全没问题,Flutter / RN 默认也是如此。把 SceneDelegate 当「必选项」反而容易画蛇添足。

❌ 误区四:「应用被挂起(Suspended)后还能跑点代码」

不能。Suspended 状态代码完全停止,内存数据保留但不可访问。唤醒只能靠:用户返回、后台推送(content-available)、后台定位事件、或 BGTaskScheduler 调度。别把关键逻辑寄托在「挂起期间还能执行」。

❌ 误区五:「把保存逻辑全写进 applicationWillTerminate 就稳了」

危险。应用被系统因内存压力回收时,这个方法不会调用;用户从多任务上划关闭时才会。所以保存要靠「数据变化即存 + 进后台存 + 定期自动存」多重保险,而不是押宝在终止回调。

一句话总结

iOS 生命周期是一台「省电优先」的状态机:Active 是你能交互的全部,Background 只有约 30 秒的收尾窗口,Suspended 后代码彻底停摆——AppDelegate 管应用、SceneDelegate 管窗口、SwiftUI 用 scenePhase 抽象它,而 Flutter / RN 的 paused / background 就是这套原生状态的映射。记住「进后台那一刻是保存数据的最后安全时机」,你写的跨端应用才不会在用户切个后台回来就「忘了自己是谁」。

本文由作者按照 CC BY 4.0 进行授权