AppDelegate(应用代理)是 macOS 和 iOS 应用中的一个关键角色,用于响应“应用程序生命周期事件”,比如:
1、应用启动完成。
2、应用进入后台 / 前台。
3、应用即将终止。
4、打开文件、接收通知等。
可以把它理解为“应用的总管”——系统会在重要时刻调用它的方法,可以在里面执行初始化或清理操作。
基本定义
import AppKit
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// 应用启动完成后会自动调用
print("应用启动完成")
}
func applicationWillTerminate(_ notification: Notification) {
// 应用即将退出时调用
print("应用即将退出")
}
}
在SwiftUI中,可以通过
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
把它注入到SwiftUI应用中。
注意事项:类必须继承自NSObject和NSApplicationDelegate协议。
NSObject协议
NSApplicationDelegate 协议是 Objective-C 导出的协议(@objc)。
所有要实现该协议的方法,必须具备 Objective-C 运行时能力。
NSObject 是开启 Objective-C 动态分发的前提(Swift 原生类不满足)。
NSApplicationDelegate 协议
这样系统才能识别该类是用于处理 App 生命周期的代理对象:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// 应用启动完成
}
func applicationWillTerminate(_ notification: Notification) {
// 应用即将退出
}
func application(_ app: NSApplication, open urls: [URL]) {
// 打开 URL
}
}
如果不遵循这两个协议,编译通过后,运行时系统可能不会识别这些方法,生命周期不会触发。
通知事件
因为 macOS 应用不仅仅是显示 UI,系统还需要通知生命周期事件。
以下是生命周期时间和对应的方法:
1、应用启动完成:applicationDidFinishLaunching(_:)
示例:
func applicationDidFinishLaunching(_ notification: Notification) {
// 初始化菜单栏图标
statusBarController = StatusBarController()
}
使用场景:启动一个纯状态栏应用,它不显示主窗口,而是只在启动完成后创建状态栏图标。
2、应用进入前台:applicationWillBecomeActive(_:)
应用即将进入“活跃状态”,通常意味着点击了 Dock 中的图标,或从后台重新进入前台。
func applicationWillBecomeActive(_ notification: Notification) {
print("App 将进入前台")
}
使用场景:切换应用回来后,希望刷新某个界面数据,或播放启动动画等。
3、应用进入后台:applicationWillResignActive(_:)
应用即将离开“活跃状态”,可能是最小化窗口、切换到其他 App、锁屏等。
func applicationWillResignActive(_ notification: Notification) {
print("App 进入后台(非活跃)")
}
使用场景:想要在用户切走前保存当前编辑状态,或者暂停音视频播放。
4、应用将要退出:applicationWillTerminate(_:)
应用即将退出时调用。用户强退(Force Quit)或系统崩溃不会触发。
func applicationWillTerminate(_ notification: Notification) {
print("App 即将退出,执行清理逻辑")
}
使用场景:写入用户数据、关闭数据库连接、保存设置等。
5、打开文件:application(_:openFile:)
用户通过「打开方式」(Open With)或拖入 Dock 启动 App,并传入文件。
func application(_ sender: NSApplication, openFile filename: String) -> Bool {
print("用户打开了文件:\(filename)")
return true
}
使用场景:开发一个图像处理工具,用户双击 .jpg 文件后,App 自动打开并加载该图像。
6、处理 URL:application(_:open:)
通过自定义 URL Scheme 启动 App,比如 myapp://some/path。
func application(_ app: NSApplication, open urls: [URL]) {
for url in urls {
print("App 被 URL 启动:\(url.absoluteString)")
}
}
开发一个 Markdown 应用,支持通过链接启动并跳转到某个笔记:noteapp://open?id=123。
7、接收通知:application(_:didReceiveRemoteNotification:)
App 收到远程推送通知时触发,前提是实现了推送注册。
func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
print("收到远程通知:\(userInfo)")
}
开发一个任务提醒 App,当有新任务时,后台服务器推送提醒,用户可以点击通知打开 App。
在 SwiftUI 中使用 AppDelegate
虽然 SwiftUI 使用的是 @main 和 App 协议,但你仍然可以使用 AppDelegate 以获得更多控制。
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
在SwiftUI代码中,通过@NSApplicationDelegateAdaptor可以让SwiftUI应用拥有一个传统的AppDelegate(生命周期代理对象)。
启动过程
1、系统启动 app 时,会调用 main() 函数(被 Swift 的 @main 标记的 struct 自动生成)。
2、SwiftUI 的 App 协议负责初始化运行环境。
3、SwiftUI 会自动创建并注册一个 NSApplication.shared 实例。
4、然后 SwiftUI 将标记为 @NSApplicationDelegateAdaptor(AppDelegate.self) 的类,创建系统唯一的 AppDelegate 实例。
5、最后调用 AppDelegate.applicationDidFinishLaunching(…)。
注意:不在要AppDelegate中使用单例模式。
class AppDelegate: NSObject, NSApplicationDelegate {
static let shared = AppDelegate() // 不要用单例!
private override init() {}
}
在启动的过程中,系统有一个真正正在运行的AppDelegate实例(SwiftUI自动创建的)。如果自己创建一个单例,那么单例和AppDelegate实例会是两个对象,这也就意味着无法通过单例控制AppDeleagte实例。
因此,应该避免在AppDelegate中使用单例,否则会因为假对象访问nil导致崩溃问题。
总结
AppDelegate是应用生命周期的管理者,可以响应启动、退出、通知、打开文件等系统事件。
macOS使用NSApplicationDelegate、iOS使用UIApplicationDelegate,在SwiftUI中可以通过@NSApplicationDelegateAdaptor 或 @UIApplicationDelegateAdaptor 注入。
在处理AppKit的状态栏、菜单栏等复杂的场景时,仍然推荐使用AppDelegate。
扩展知识
SwiftUI不通过AppDelegate创建AppKit类
使用AppDelegate
在使用AppKit类(如NSStatusItem)时,通常需要配置AppDelegate。
例如通过NSStatusBar配置状态栏菜单:
import AppKit
import SwiftUI
class StatusBarController {
private var statusItem: NSStatusItem!
init() {
// 创建系统菜单栏图标
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem.button {
button.image = NSImage(named: "templateIcon")
}
// 创建菜单
let menu = NSMenu()
let openItem = NSMenuItem(title: "打开 App", action: #selector(openApp), keyEquivalent: "o")
openItem.target = self
menu.addItem(openItem)
...
statusItem.menu = menu
}
}
在SwiftUI中,使用AppDelegate初始化:
import AppKit
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarController: StatusBarController?
func applicationDidFinishLaunching(_ notification: Notification) {
statusBarController = StatusBarController()
}
}
在入口文件中使用:
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
通过AppDelegate可以实现状态栏的配置,全部代码可以在《macOS状态栏图标(系统右上角)NSStatusBar》文章中查看。
不使用AppDelegate
实际上,也可以不通过AppDelegate创建NSStatusBar,只是创建时机必须准确,负责菜单栏可能不会显示或行为异常。
在NSStatusBar中,继承ObservableObject协议,使得SwiftUI可以通过@StateObject进行管理生命周期:
import AppKit
import SwiftUI
class StatusBarController:ObservableObject {
private var statusItem: NSStatusItem!
init() {
...
}
}
在SwiftUI中,不使用AppDelegate,而是使用@StateObject创建StatusBarController实例。
@main
struct ImageSlimApp: App {
@StateObject private var statusBar = StatusBarController()
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
SwiftUI 的 @main struct 会自动初始化一个 NSApplication 实例(macOS 下)。
在 SwiftUI 初始化 @StateObject 时(早于视图出现),此时 NSApplication.shared 已经处于运行状态。
所以,只要没有太早访问 NSStatusBar.system(比如在全局静态变量中),这时候初始化 StatusBarController 是安全的。
NSStatusBar.system.statusItem(…) 会将菜单图标插入 macOS 的系统菜单栏。
AppDelegate 的作用
在上一个扩展知识中,可以了解到不使用AppDelegate仍然可以实现AppKit类,那么AppDelegate的作用到底是什么?
AppDelegate 最主要的作用就是控制创建的时机,保证初始化时机稳定且可靠。
AppDelegate 的完整职责包括:
1、控制 App 启动:applicationDidFinishLaunching。
2、接收退出事件:applicationWillTerminate。
3、接收通知推送:didReceiveRemoteNotification。
4、接收打开文件事件:application(_:openFile:)。
5、接收 URL 事件:application(_:open:)。
6、全局菜单、服务管理:validateMenuItem 等。
7、设置 Dock 图标行为:applicationShouldHandleReopen。
这些功能,目前SwiftUI还不能完全替代。
如果 App 只是创建一个状态栏图标,并不需要响应复杂的系统事件,那么确实可以不用 AppDelegate。
为什么不使用AppDelegate也可以实现AppKit?
为什么不使用AppDelegate,在上面的扩展知识中,SwiftUI仍然可以实现状态栏图标的创建?
这和macOS系统内部的机制有关,关键是:
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
这行代码的核心机制:
NSStatusBar.system 是一个系统全局对象。
statusItem(…) 会将按钮/菜单注册到系统的「Menu Extras 区域」。
本质是插入了一个 NSStatusItem 实例到系统 UI 中。
macOS 会主动渲染这个图标和对应的行为。
这不需要做任何额外工作,系统检测到插入了 NSStatusItem,它就会显示。
因此,在StatusBarController类的初始化阶段,通过调用NSStatusBar.system.statusItem(…) 向操作系统注册了一个“请求”:
class StatusBarController:ObservableObject {
private var statusItem: NSStatusItem!
init() {
// 创建系统菜单栏图标
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem.button {
button.image = NSImage(named: "templateIcon")
}
...
}
}
系统将这个 item 加入它维护的 UI 列表中,它自然就出现在状态栏了。
相关文章
macOS状态栏图标(系统右上角)NSStatusBar:https://fangjunyu.com/2025/06/24/macos%e7%8a%b6%e6%80%81%e6%a0%8f%e5%9b%be%e6%a0%87%ef%bc%88%e7%b3%bb%e7%bb%9f%e5%8f%b3%e4%b8%8a%e8%a7%92%ef%bc%89nsstatusbar/