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

最近在开发中过程看到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 更新和用户交互的基础。

swift 复制代码
// 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 启动)
swift 复制代码
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 秒 时间执行任务。

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

swift 复制代码
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 的理想时机。

swift 复制代码
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)

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)

xml 复制代码
<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 框架 ,其生命周期由 NSApplicationNSApplicationDelegate 控制。

1. 状态模型(较宽松)

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

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

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

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 强调多窗口管理,每个窗口有自己的代理:

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

4. 与 iOS 的关键区别

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

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

1. SwiftUI 的统一生命周期

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

swift 复制代码
@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. 日志追踪

统一日志宏:

swift 复制代码
func logLifecycle(_ message: String) {
    print("[LIFECYCLE] \(Date()): \(message)")
}

并在各回调中调用。

4. Instruments 工具检测

使用 Energy LogAllocations 查看后台能耗与内存占用。


六、常见陷阱与解决方案

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

七、总结

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

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

作为开发者,我们应当:

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

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


延伸阅读与官方文档


📌 结语

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

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


相关推荐
xingxing_F4 小时前
DSync for Mac 文件对比同步工具
macos
2501_916008897 小时前
iOS 发布全流程详解,从开发到上架的流程与跨平台使用 开心上架 发布实战
android·macos·ios·小程序·uni-app·cocoa·iphone
非专业程序员9 小时前
iOS/Swift:深入理解iOS CoreText API
ios·swift
某柚啊10 小时前
iOS移动端H5键盘弹出时页面布局异常和滚动解决方案
前端·javascript·css·ios·html5
xingxing_F11 小时前
Swift Publisher for Mac 版面设计和编辑工具
开发语言·macos·swift
CHH321320 小时前
在 Mac/linux 的 VSCode 中使用Remote-SSH远程连接 Windows
linux·windows·vscode·macos
RollingPin21 小时前
iOS八股文之 RunLoop
ios·多线程·卡顿·ios面试·runloop·ios保活·ios八股文
心灵宝贝21 小时前
Mac 桌面动态壁纸软件|Live Wallpaper 4K Pro v19.7 安装包使用教程(附安装包)
macos