深入解析 iOS 与 macOS 应用程序生命周期(完整指南)

699 阅读9分钟

最近在开发中过程看到appDelegate中的一些方法,突然想到了之前了解但没有梳理过的app的生命周期的一些方法,因此在这里简单的记录并和大家分享一下iOS与macOS应用程序的生命周期。

在 Apple 生态系统中,无论是 iPhone 上的轻量级 App 还是 Mac 上功能完整的桌面应用,其行为都受到一套精密设计的“应用程序生命周期”机制控制。这套机制不仅决定了应用何时启动、暂停、恢复或终止,还深刻影响着内存管理、后台执行策略、多任务处理能力以及用户体验流畅度。

本文将从底层原理到实践层面,全面剖析 iOS 和 macOS 应用程序生命周期 的每一个关键环节,涵盖状态模型、代理方法调用顺序、多场景支持、后台任务调度、调试技巧,并对比两个平台的设计哲学差异。


一、为什么需要理解应用生命周期?

许多看似随机的问题——如数据丢失、定时器未停止、音频中断、定位服务异常退出——往往源于对生命周期回调的误用或忽略。

掌握生命周期意味着你可以:

  • 在正确时机初始化资源
  • 避免内存泄漏
  • 实现优雅的数据持久化
  • 提升电池效率
  • 支持多窗口与多任务交互(尤其在 iPadOS/macOS)
  • 适配 SwiftUI 和 UIKit/AppKit 混合架构

二、iOS 应用程序生命周期详解

1. 核心概念:UIApplication 与 Run Loop

iOS 应用基于 事件驱动模型,由 UIApplication 单例对象主导整个生命周期。它依赖于 Main Run Loop 来接收并分发事件(触摸、网络回调、定时器等)。

📌 Run Loop 是什么?
它是一个循环线程结构,持续监听输入源(Input Sources),并在有事件时唤醒线程执行任务。它是所有 UI 更新和用户交互的基础。

// UIApplication 主循环简化示意(非真实实现)
func run() {
    while isRunning {
        let event = nextEvent()
        if let e = event {
            processEvent(e)
        }
    }
}

2. iOS 应用五大状态详解

状态描述可执行操作是否会被系统终止
Not Running进程未启动或已被杀死无是
Inactive运行但不接收事件(来电、通知中心、Control Center 弹出)可继续运行代码,但不处理 UI 事件否
Active正在前台运行,完全响应用户输入全功能运行否
Background进入后台,系统给予约 3 秒宽限期执行清理任务可申请延长后台执行时间(最多 3 分钟)是(超时后)
Suspended被挂起,不消耗 CPU,保留在内存中不执行任何代码是(低内存时)

⚠️ 注意:

  • “Suspended” ≠ “Terminated”。挂起的应用仍驻留内存,可快速恢复。
  • 系统可在任何时候终止 Suspended 或 Background 中的任务,不会调用 applicationWillTerminate。

3. 生命周期方法详解(AppDelegate)

didFinishLaunchingWithOptions(_:)

这是应用的第一个入口点。在此处应完成:

  • 初始化第三方 SDK(Firebase、Analytics)
  • 设置根视图控制器
  • 检查启动选项(例如通过 URL Scheme 启动)
func application(_ application: UIApplication, 
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    guard let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem else {
        // 正常启动
        setupRootViewController()
        return true
    }

    // 处理 3D Touch 快捷方式启动
    handleShortcutItem(shortcutItem)
    return true
}

applicationDidBecomeActive(_:)

应用进入 Active 状态,表示可以正常交互。

建议操作:

  • 恢复动画、AVPlayer 播放
  • 重启 CADisplayLink / Timer
  • 检查登录状态是否过期

避免:

  • 执行耗时同步操作(阻塞主线程)

applicationWillResignActive(_:)

应用即将失去焦点。常见触发场景:

  • 接到来电
  • 用户打开通知中心/控制中心
  • 切换到其他应用(双击 Home 键)
  • Face ID 验证中断

建议操作:

  • 暂停游戏、视频播放
  • 暂停计时器
  • 模糊敏感界面(安全考虑)

applicationDidEnterBackground(_:)

应用已进入后台。此时只有约 3 秒 时间执行任务。

若需更长时间运行(如上传文件、同步数据),必须请求后台任务:

var backgroundTask: UIBackgroundTaskIdentifier = .invalid

func applicationDidEnterBackground(_ application: UIApplication) {
    backgroundTask = application.beginBackgroundTask { [weak self] in
        // 超时回调:必须在此结束任务
        self?.endBackgroundTask()
    }

    // 执行后台任务
    performLongRunningTask { [weak self] in
        self?.endBackgroundTask()
    }
}

private func endBackgroundTask() {
    if backgroundTask != .invalid {
        UIApplication.shared.endBackgroundTask(backgroundTask)
        backgroundTask = .invalid
    }
}

📌 Tips:

  • 每个后台任务最长 ~3 分钟(具体时间由系统动态调整)
  • 使用 BGProcessingTaskRequest(iOS 13+)进行低优先级后台处理(需声明 capability)

applicationWillEnterForeground(_:)

应用即将回到前台。这是刷新 UI 的理想时机。

func applicationWillEnterForeground(_ application: UIApplication) {
    // 刷新首页内容
    NotificationCenter.default.post(name: .appWillEnterForeground, object: nil)
}

applicationWillTerminate(_:)

⚠️ 重要警告:该方法仅在非挂起状态下被调用!
即:当应用处于 Inactive 或 Background 状态时被手动杀死才会触发。大多数情况下(挂起后被系统清理),此方法不会执行。

因此,不要依赖它来保存关键数据!

正确做法:

  • 在 applicationDidEnterBackground 中保存
  • 使用自动保存机制(Core Data 自动保存、UserDefaults.flush())
  • 监听特定事件即时保存

4. iOS 13+ 多场景(Scene-Based Lifecycle)

随着 iPadOS 支持多窗口、Mac Catalyst 的推出,Apple 引入了 Scene 架构,将 UI 与生命周期解耦。

关键组件:

  • UIScene:代表一个独立的 UI 实例(如一个窗口)
  • UISceneSession:持久化场景信息(用于恢复)
  • UISceneDelegate:管理单个场景的生命周期
  • NSUserActivity:跨设备 Handoff 和场景恢复

Scene 生命周期方法(SceneDelegate.swift)

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 }

        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = createInitialViewController()
        self.window = window
        window.makeKeyAndVisible()

        // 处理通过场景启动的快捷方式或 URL
        handleConnectionOptions(connectionOptions)
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        print("Scene became active")
    }

    func sceneWillResignActive(_ scene: UIScene) {
        print("Scene will resign active")
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // 场景进入后台(可能还有其他场景在前台)
        (scene as? UIWindowScene)?.windows.forEach { $0.resignFirstResponder() }
    }
}

多场景配置(Info.plist)

<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
            </dict>
        </array>
    </dict>
</dict>

💡 提示:即使你的 App 不支持多窗口,iOS 13+ 也会默认创建一个场景。


三、macOS 应用程序生命周期详解

macOS 使用 AppKit 框架,其生命周期由 NSApplication 和 NSApplicationDelegate 控制。

1. 状态模型(较宽松)

状态说明
Not Running未运行
Inactive窗口未聚焦(其他应用在前台)
Active当前应用拥有焦点

❗ macOS 没有“挂起”状态。应用可在后台无限期运行(除非用户主动退出或系统因内存压力终止)。

2. 生命周期方法(AppDelegate.swift)

import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationDidFinishLaunching(_ notification: Notification) {
        // 创建主窗口
        let mainWindow = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
            styleMask: [.titled, .closable, .miniaturizable, .resizable],
            backing: .buffered,
            defer: false
        )
        mainWindow.center()
        mainWindow.title = "My Mac App"
        mainWindow.makeKeyAndOrderFront(nil)
        
        NSApp.activate(ignoringOtherApps: true)
    }

    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        if !flag {
            // 如果没有可见窗口,重新打开主窗口
            showMainWindow()
        }
        return true
    }

    func applicationWillTerminate(_ notification: Notification) {
        // 清理资源、保存偏好设置
        UserDefaults.standard.synchronize()
    }

    func applicationDidResignActive(_ notification: Notification) {
        // 当前应用失去焦点
    }

    func applicationDidBecomeActive(_ notification: Notification) {
        // 当前应用获得焦点
    }
}

3. 窗口生命周期(NSWindowDelegate)

macOS 强调多窗口管理,每个窗口有自己的代理:

extension MainWindowController: NSWindowDelegate {
    func windowWillClose(_ notification: Notification) {
        // 保存窗口位置、大小
        UserDefaults.standard.set(window?.frame, forKey: "MainWindowFrame")
    }
}

4. 与 iOS 的关键区别

特性iOSmacOS
后台运行严格限制几乎无限制
终止机制系统可随时终止用户主动退出为主
多实例通常单实例可打开多个文档窗口
生命周期粒度应用级 + 场景级应用级 + 窗口级
用户期望快速启动、节省电量持续可用、功能完整

四、跨平台开发注意事项(SwiftUI & Catalyst)

1. SwiftUI 的统一生命周期

使用 SwiftUI 时,可通过 @UIApplicationDelegateAdaptor 或直接使用声明式生命周期:

@main
struct MyApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: phase) { newPhase in
            switch newPhase {
            case .active:
                print("App is active")
            case .inactive:
                print("App is inactive")
            case .background:
                print("App in background")
            @unknown default:
                break
            }
        }
    }
}

2. Mac Catalyst 适配要点

  • 启用 Target > General > Mac > Mac Catalyst
  • 处理鼠标悬停、右键菜单、键盘快捷键
  • 调整布局以适应更大屏幕
  • 注意后台任务权限差异

五、调试生命周期问题的实用技巧

1. 使用 Xcode 生命周期断点

在 AppDelegate 方法中添加断点,观察调用顺序。

2. 模拟状态变化

Xcode → Debug → Simulate Background Fetch / Terminate App

3. 日志追踪

统一日志宏:

func logLifecycle(_ message: String) {
    print("[LIFECYCLE] \(Date()): \(message)")
}

并在各回调中调用。

4. Instruments 工具检测

使用 Energy Log 和 Allocations 查看后台能耗与内存占用。


六、常见陷阱与解决方案

问题原因解决方案
数据未保存依赖 willTerminate改为 didEnterBackground 或实时保存
定时器持续运行未在 resignActive 中暂停使用 invalidate() 并在 becomeActive 重建
音频中断未处理 AVAudioSession注册中断通知并恢复播放
内存警告后崩溃未释放缓存实现 didReceiveMemoryWarning
多场景重复初始化未检查 session restoration ID使用 session.persistentIdentifier 去重

七、总结

平台设计哲学生命周期特点开发建议
iOS移动优先、资源受限、用户体验至上严格状态管理、后台限制、自动挂起快速响应、及时保存、合理使用后台任务
macOS功能完整、多任务、长期运行松散状态、自由后台、多窗口注重窗口管理、支持文档模型、优化长期运行性能

尽管 iOS 和 macOS 在生命周期实现上存在差异,但其核心理念一致:让应用在合适的时机做合适的事。

作为开发者,我们应当:

  • 理解状态转换逻辑
  • 正确使用生命周期钩子
  • 善用工具调试
  • 面向未来设计(支持多场景、Catalyst、SwiftUI)

只有这样,才能构建出既高效又可靠的跨平台应用。


延伸阅读与官方文档


📌 结语
应用生命周期不仅是技术细节,更是设计思维的体现。掌握它,你就能更好地驾驭 Apple 平台的强大能力,为用户带来无缝、流畅、可靠的体验。

如果你觉得这篇文章有价值,请分享给你的团队!也欢迎在评论区提出疑问或分享你的最佳实践。