iOS App Lifecycle 生命週期解析

開發 App 時,了解「程式現在是剛啟動?還是在背景?還是快被系統殺掉了?」是非常重要的。SwiftUI 簡化了這一切,但理解底層的運作機制依然必要。

SwiftUI App Life Cycle

在純 SwiftUI 專案中,我們不再看到 AppDelegate.swiftSceneDelegate.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
            }
        }
    }
}

當使用者點擊網址 (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)")
    }