iOS App Lifecycle 生命週期解析
開發 App 時,了解「程式現在是剛啟動?還是在背景?還是快被系統殺掉了?」是非常重要的。SwiftUI 簡化了這一切,但理解底層的運作機制依然必要。
SwiftUI App Life Cycle
在純 SwiftUI 專案中,我們不再看到 AppDelegate.swift 和 SceneDelegate.swift。取而代之的是遵循 App 協議的結構體。
監聽狀態變化 (ScenePhase)
我們可以透過 @Environment(\.scenePhase) 來得知 App 目前的狀態。
@main
struct MyApp: App {
// 監聽場景狀態
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { newPhase in
switch newPhase {
case .active:
print("App 處於活躍狀態 (前景,可互動)")
// 可以在這裡暫停遊戲、重啟計時器
case .inactive:
print("App 處於非活躍狀態 (例如拉下通知中心時)")
// 這裡仍然可以接收事件,但通常是進入背景前的過渡
case .background:
print("App 進入背景")
// 這裡應該儲存資料、釋放資源
@unknown default:
break
}
}
}
}
處理 Deep Link (onOpenURL)
當使用者點擊網址 (myapp://...) 打開 App 時,我們使用 onOpenURL 來處理。
WindowGroup {
ContentView()
.onOpenURL { url in
print("收到 URL: \(url)")
// 解析 URL 並導航到對應頁面
}
}
老朋友 AppDelegate
雖然 SwiftUI 簡化了流程,但有些舊的 API (例如 Push Notification 的註冊、第三方 SDK 的初始化、強制橫屏) 仍然依賴 AppDelegate。我們可以使用 @UIApplicationDelegateAdaptor 把他找回來。
class AppDelegate: NSObject, UIApplicationDelegate {
// App 啟動完成
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("AppDelegate: App 啟動完成")
// 初始化 Firebase, Facebook SDK 等...
return true
}
// 註冊推播 Token
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("Device Token: \(deviceToken)")
}
// 處理推播註冊失敗
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
}
@main
struct MyApp: App {
// 注入 AppDelegate
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
視圖生命週期 (View Lifecycle)
除了 App 層級,每個 View 也有自己的生命週期。
初始化 vs 出現 (init vs onAppear)
新手常見的誤區是以為 init() 適合用來發送網路請求。這是錯誤的。
SwiftUI 的 View 是 Struct,會頻繁地被銷毀和重建。init() 可能會被呼叫非常多次。
正確做法:
- init(): 只做基本的屬性配置。
- onAppear: 發送網路請求、啟動動畫。
struct MyView: View {
init() {
print("View 初始化 (可能執行多次)")
}
var body: some View {
Text("Hello")
.onAppear {
print("View 出現 (只執行一次,直到 View 被移除)")
// 適合:API Request
}
.onDisappear {
print("View 消失")
// 適合:取消訂閱、停止動畫
}
.task {
// 最佳實踐:異步任務
// View 消失時會自動 cancel
await loadData()
}
}
}
資料流生命週期 (onChange)
當 State 改變時,我們使用 onChange 來執行副作用 (Side Effect)。
@State private var count = 0
Button("Plus") { count += 1 }
.onChange(of: count) { newValue in
print("Count 變成了 \(newValue)")
}