程序与视图生命周期
程序级 scenePhase + 视图级 onAppear/task:时机、顺序、流程图;并对照 HarmonyOS UIAbility / 组件钩子。
1先分清两层(别混)
iOS 里日常说的「生命周期」其实是两套:程序/场景级管前后台;视图级管某一屏出现与消失。混用会导致「回前台请求挂在错误地方」。
| 层 | iOS(SwiftUI 主线) | ≈ HarmonyOS | 典型用途 |
|---|---|---|---|
| 程序/Ability | @main App + scenePhase | UIAbility 回调 | 停定位、刷 Token、持久化 |
| 窗口/页面容器 | WindowGroup / Scene | onWindowStageCreate + 页面栈 | 加载根界面 |
| 视图/组件 | onAppear / .task | aboutToAppear / onPageShow | 拉列表、订通知 |
口诀:进程还活着 ≠ 某个 View 还在屏幕上。进后台时页面可能仍在导航栈里;反之,换 Tab 会触发视图 disappear,但 App 仍是
.active。2程序生命周期总流程
冷启动 → 前台交互 →(可多次)进后台再回前台 → 最终被系统回收。SwiftUI 里你主要观察 ScenePhase;底层仍是 UIKit 的 Scene/App 在驱动。
点图标启动
→
Inactive
→
Active
→
Home 键/上滑
→
Background
3关键 API:ScenePhase(SwiftUI)
@Environment(\.scenePhase) 是现代 SwiftUI App 观察前后台的主入口。三个值,调用时机如下。
| 值 | 含义 | 常见触发 | 你该做什么 |
|---|---|---|---|
.active | 前台且可交互 | 启动完成、从后台回前台、关掉控制中心 | 恢复刷新、重连 WebSocket、开定位 |
.inactive | 过渡:在前台但暂不可交互 | 来电、下拉通知中心、App 切换动画中 | 暂停游戏循环;一般别在这里狂发网络 |
.background | 已进后台 | 回主屏、切到别的 App | 停传感器、存草稿、取消非必要任务 |
import SwiftUI
@main
struct MyApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { oldPhase, newPhase in
print("scenePhase: \(oldPhase) → \(newPhase)")
switch newPhase {
case .active:
// 回前台:可静默刷新未读数
break
case .inactive:
// 过渡态:短暂停动画
break
case .background:
// 进后台:立即落盘
TodoStore.shared.save()
@unknown default:
break
}
}
}
}
冷启动时常见顺序:先经历
inactive,再进 active。从后台回来也常是 background → inactive → active。用 print 自己跑一遍最踏实。4程序侧:典型调用顺序
冷启动(用户点图标)
- 进程创建,执行
@mainApp 初始化 - 创建
WindowGroup,构建根View scenePhase→.inactive- 根视图出现(触发视图级
onAppear/.task) scenePhase→.active
进后台再回前台
.active→.inactive(开始离开).inactive→.background- (系统可能 Suspend;你收不到单独回调)
- 用户再点图标:
.background→.inactive→.active - 注意:栈里的 View 不一定再走一遍
onAppear
5对照 HarmonyOS:UIAbility
你会的 Stage 模型里,程序级几乎全在 UIAbility。和 iOS scenePhase 不是一一改名,但职责同构。
| 时机 | iOS(SwiftUI) | HarmonyOS UIAbility |
|---|---|---|
| Ability/进程创建 | @main App init | onCreate |
| 窗口就绪、加载 UI | WindowGroup { RootView() } | onWindowStageCreate → loadContent |
| 到前台 | scenePhase == .active | onForeground |
| 到后台 | .background | onBackground |
| 窗口销毁 | Scene 结束(较少手写) | onWindowStageDestroy |
| Ability 销毁 | 进程被杀(无稳定对称回调) | onDestroy |
| 再入(single) | 通常仍是同一 Scene | onNewWant |
SwiftUI
@main
struct MyApp: App {
@Environment(\.scenePhase) private var phase
var body: some Scene {
WindowGroup { ContentView() }
.onChange(of: phase) { _, p in
if p == .background { save() }
if p == .active { refresh() }
}
}
}
ArkTS
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) { /* 初始化一次 */ }
onWindowStageCreate(windowStage) {
windowStage.loadContent('pages/Index')
}
onForeground() { /* ≈ active */ }
onBackground() { /* ≈ background */ }
onDestroy() { /* 释放 */ }
}
鸿蒙把「创建窗口」和「前后台」拆成多个明确回调;iOS SwiftUI 把前后台收成
scenePhase,窗口创建则藏在 WindowGroup 声明里。6视图生命周期:关键方法
SwiftUI 没有 UIKit 那么多 viewWillAppear 回调;日常就这几只「钩子」。理解时机比背名字重要。
| API | 调用时机 | 会不会多次 | 适合做什么 |
|---|---|---|---|
.onAppear | 视图出现在层级中(即将/已经显示) | 会;导航返回、Tab 切回常再进 | 轻量逻辑、埋点;请求建议用 .task |
.onDisappear | 视图从层级移除/离开屏幕 | 会 | 取消订阅、停动画(别在这里改关键导航状态) |
.task { } | 出现时启动异步任务 | 随身份/参数变化可重跑 | 首选拉数;视图消失时自动取消 |
.task(id:) { } | id 变化时取消旧任务并重跑 | 按 id | 详情页随 itemId 换数据 |
.onChange(of:) | 某状态值变化 | 每次变化 | 响应式副作用(慎防连环触发) |
struct DetailView: View {
let itemId: String
@State private var title = "加载中…"
var body: some View {
Text(title)
.onAppear { print("appear \(itemId)") }
.onDisappear { print("disappear \(itemId)") }
.task(id: itemId) {
// itemId 变了会取消上一次 fetch
title = await api.fetchTitle(itemId)
}
}
}
7视图生命周期流程图与调用顺序
父子同屏时:大体是父先 appear,再子 appear;消失时常见子先 disappear,再父(以实际 print 为准,导航动画可能导致额外次数)。
push 详情
→
Detail.onAppear
→
.task 拉数
→
pop
→
onDisappear + 取消
验证方法:在父子 View 里
print(#function) / 自定义标签,真机或模拟器走一遍导航。Preview 也会触发 appear,调试时别被吓到。8对照 HarmonyOS:组件 / 页面钩子
| 时机 | SwiftUI | ArkUI 组件 | ArkUI 页面 |
|---|---|---|---|
| 创建后、首帧前 | (无对等;用 init / 状态默认值) | aboutToAppear(在首帧 build 前) | 同左 |
| 显示时 | onAppear | 配合首次 build | onPageShow(每次显示) |
| 隐藏时 | onDisappear | aboutToDisappear | onPageHide |
| 异步加载 | .task | 在 aboutToAppear 里异步(勿写进 build) | 同左 |
SwiftUI
struct ListPage: View {
@State private var items: [Item] = []
var body: some View {
List(items) { Text($0.name) }
.task { items = await api.list() }
.onDisappear { /* 可选清理 */ }
}
}
ArkTS
@ComponentV2
struct ListPage {
@Local items: Item[] = []
aboutToAppear(): void {
this.load() // 禁止在 build() 里打网络
}
aboutToDisappear(): void {
// 取消监听;勿乱改 @Param 链路状态
}
async load() {
this.items = await api.list()
}
build() {
List() { ForEach(this.items, (it: Item) => {
ListItem() { Text(it.name) }
}, (it: Item) => it.id) }
}
}
鸿蒙官方顺序记忆:
Parent aboutToAppear → Parent build → Child aboutToAppear → Child build → onPageShow。iOS 没有对称的「build 回调」,但「父先于子出现」的直觉可迁移。9什么逻辑放哪一层
scenePhase == .background
全局落盘、停定位/音频、断开长连接。
scenePhase == .active
静默刷新会话、检查版本、恢复连接。
View .task
本页数据加载;随离开自动取消。
View onDisappear
页面级订阅、计时器、NotificationCenter。
| 需求 | 放这里 | 别放这里 |
|---|---|---|
| 进待办页拉列表 | 该页 .task | App.onChange(scenePhase) |
| 回前台刷新未读角标 | scenePhase → .active | 每个子 View 的 onAppear 都打一遍 |
| 杀进程前尽量存盘 | .background + 关键删时即时 save | 只靠 onDisappear(后台不一定再走) |
10补充:UIKit 视图控制器(知道即可)
纯 SwiftUI 项目很少手写;但调试、混排、看崩溃栈时会撞到。和鸿蒙「页面钩子」更像这一层。
loadView
→
viewDidLoad
→
viewWillAppear
→
viewDidAppear
→
Will/Did Disappear
viewDidLoad 只来一次;viewWillAppear 每次显示都可能来。SwiftUI 的 onAppear 更接近「appear 系」,不要幻想有一次性格的 viewDidLoad 等价物——用 Store 里的「已加载」标记代替。11真实坑
onAppear 里请求发了两次 / 多次
原因:Preview + 真机、动画、身份变化。处理:改用
.task;或 Store 内做 in-flight / 缓存;调试时先关掉 Canvas Preview。进后台后 onDisappear 没走,数据没存
页面还在栈上,只是场景进了 background。关键数据在
scenePhase == .background 存,平时改完也即时 save。回前台想刷新,但写在 onAppear 不触发
因为 View 没被移除。听
scenePhase → .active,或用 scenePhase 变化去通知 Store。和鸿蒙一样在「build/body」里打网络
两端都禁止。iOS 放
.task;鸿蒙放 aboutToAppear / 页面级异步,结果写入状态再驱动 UI。12一张速查表收工
| 你想… | iOS | HarmonyOS |
|---|---|---|
| 监听前后台 | scenePhase | onForeground / onBackground |
| 进页拉数 | .task | aboutToAppear + async |
| 离页取消 | .task 自动取消 / onDisappear | aboutToDisappear |
| 每次页面显示 | 不一定有;用 scenePhase 或自己标记 | onPageShow |
| 窗口加载根页 | WindowGroup | onWindowStageCreate |
先画清「程序层还是视图层」,再选钩子;用流程图 + 一次 print 验证,比死记更稳。