macOS应用代理AppDelegate和NSApplicationDelegate
macOS应用代理AppDelegate和NSApplicationDelegate

macOS应用代理AppDelegate和NSApplicationDelegate

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/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注