iOS 应用生命周期深度解析
iOS 应用是一台状态机:Not Running → Inactive → Active → Background → Suspended,AppDelegate/SceneDelegate/SwiftUI App 三种入口管理它,跨端开发者看懂才能正确处理数据保存与后台任务。
一句话概括
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/ RNbackground里没存草稿,用户一切后台数据就飞了 - 切后台回来网络断了:
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 就是这套原生状态的映射。记住「进后台那一刻是保存数据的最后安全时机」,你写的跨端应用才不会在用户切个后台回来就「忘了自己是谁」。