macOS核心对象NSApplication
macOS核心对象NSApplication

macOS核心对象NSApplication

NSApplication 是 macOS 应用程序的核心对象,代表整个正在运行的 App 实例。

它属于 AppKit 框架,用于管理 macOS App 的生命周期、事件分发、窗口管理、菜单控制等核心功能。

例如,在commands菜单中,添加NSApp.orderFrontStandardAboutPanel()方法:

CommandMenu("关于") {
    Button("关于 ImageSlim") {
        NSApp.orderFrontStandardAboutPanel()
    }
}

在点击按钮后,调用NSApplication的orderFrontStandardAboutPanel方法,并弹出应用程序的相关信息。

基本用法

一、基础方法(生命周期与事件)

1、NSApp / NSApplication.shared:获取当前 App 实例(单例)。

let NSApp = NSApplication.shared

2、run():启动应用主事件循环(通常自动调用)。

NSApp.run()

启动主事件循环(Run Loop)。

通常不需要手动调用,会在 App 启动时由系统自动调用。

只有在完全手动控制 NSApplication 生命周期(如命令行工具包装成 GUI)时才用到。

3、terminate(_:):退出应用。

NSApp.terminate(nil)

请求终止应用(用户点击菜单“退出应用”时也会触发)。

实际退出前,会先调用 applicationShouldTerminate(_:) → applicationWillTerminate(_:)。

4、stop(_:):停止主事件循环。

NSApp.stop(nil)

停止主事件循环,相当于“暂停” App,但不等于退出。

通常用于嵌入式 GUI 工具或调试工具场景。

与 run() 成对使用。

5、reply(toApplicationShouldTerminate:):响应是否允许终止。

NSApp.reply(toApplicationShouldTerminate: true) // or false

用于异步终止确认,例如在 applicationShouldTerminate(_:) 返回 .terminateLater 时,执行保存操作后用它回复是否允许退出。

例如:

func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
    saveDataAsync {
        NSApp.reply(toApplicationShouldTerminate: true)
    }
    return .terminateLater
}

saveDataAsync { … }:假设是自定义的函数,异步保存数据(比如保存文件、Core Data、云同步等)。

{ NSApp.reply(…) }:在保存完成后告诉系统:“我已经完成处理,现在可以退出”

6、sendEvent(_:):事件分发入口(如鼠标、键盘等)。

override func sendEvent(_ event: NSEvent) {
    super.sendEvent(event)
    // 自定义事件处理逻辑(如截屏、全局快捷键等)
}

用于拦截和扩展所有事件(键盘、鼠标、触控板等)。

常在 NSApplication 子类中重写,来添加全局快捷键支持、日志记录等。

7、nextEvent(matching:):获取下一个事件。

let event = NSApp.nextEvent(matching: .any, until: nil, inMode: .default, dequeue: true)

获取下一个事件。

用于自定义事件处理循环(很少手动使用,除非你要实现完全自定义的事件机制)。

通常用于 Kiosk 模式、全屏游戏、命令行嵌套 GUI 工具等极端场景。

二、App 状态相关

1、isActive:App 是否当前激活(在前台)。

NSApp.isActive

表示 App 是否在前台。

如果用户当前正在使用该 App(窗口处于最前),返回 true。

适合用于:控制播放、暂停、停止刷新、自动退出登录等行为。

2、isRunning:App 是否正在运行。

NSApp.isRunning

表示 App 是否已进入运行状态。

一般在 App 启动完成后始终为 true。

很少需要显式判断这个属性,主要用于低层调试或特殊工具类。

3、isHidden:是否被隐藏。

NSApp.isHidden

判断 App 是否被用户通过 ⌘H(Command+H)或菜单手动隐藏。

true 表示当前 App 窗口隐藏,但进程仍运行。

可以配合 NSWorkspace.shared.notificationCenter 监听隐藏/显示事件。

4、activationPolicy:App 的激活策略(如 .regular, .accessory, .prohibited)。

if NSApp.activationPolicy == .accessory {
    print("这是一个菜单栏辅助工具 App")
}

表示 App 的“激活策略”,影响它是否能显示 Dock 图标、菜单栏等。

常用于工具类 App、菜单栏 App、后台服务 App等场景。

可选值(NSApplication.ActivationPolicy):

1、.regular:默认值,正常 App,显示 Dock 图标和菜单栏。

2、.accessory:辅助 App,无 Dock 图标、无菜单栏(菜单栏可自建)。

3、.prohibited:后台 App,不参与前台显示(如守护进程)。

可动态设置 activationPolicy,但通常需配合 activate(ignoringOtherApps:) 使用才能生效。

也可以在App启动时,设置激活策略:

class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        NSApp.setActivationPolicy(.accessory)  // 设置为辅助App
    }
}

@main
struct ImageSlimApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

如果想要右上角弹出菜单栏,则需要手动添加NSStatusBar的菜单项。

5、windows:当前所有窗口。

let allWindows = NSApp.windows

当前 App 所有窗口的数组(包括隐藏、非主窗口等)。

可遍历所有窗口进行控制,如批量关闭、最小化等。

6、mainWindow / keyWindow:主窗口 / 焦点窗口。

NSApp.mainWindow
NSApp.keyWindow

mainWindow:用户操作的主窗口(点击时变主),通常是主 UI 窗口。

keyWindow:当前键盘事件聚焦的窗口(可能是弹窗)。

它们常用于获取当前焦点窗口、判断窗口类型、展示弹窗等场景。

三、用户交互控制

1、hide(_:):隐藏 App。

NSApp.hide(nil)

相当于用户按下 ⌘H。

会让 App 所有窗口全部隐藏,但进程仍在后台运行。

2、unhide(_:):显示 App。

NSApp.unhide(nil)

重新显示通过 hide(_:) 隐藏的所有窗口。

常与 activate(…) 搭配使用。  

3、activate(ignoringOtherApps:):激活 App(让它跳到最前面)。

NSApp.activate(ignoringOtherApps: true)

让当前 App 进入前台,成为活动应用。

ignoringOtherApps: true 表示强制抢占焦点。

4、requestUserAttention(_:):请求用户注意(如在 Dock 上跳动)。

NSApp.requestUserAttention(.criticalRequest)  // 或 .informationalRequest

只有App 未处于前台时,让 Dock 图标“跳动”以吸引用户。如果 App 处于前台时,Dock 图标不会“跳动”。

.criticalRequest 跳得更明显。

5、orderFrontStandardAboutPanel(_:):显示标准「关于本应用」面板。

NSApp.orderFrontStandardAboutPanel(nil)

macOS 自动生成的关于窗口(包含 App 名称、版本号、版权等信息)

信息来源于 Info.plist 的:CFBundleName、CFBundleShortVersionString、NSHumanReadableCopyright。

可以自定义内容:

NSApp.orderFrontStandardAboutPanel(options: [
    NSApplication.AboutPanelOptionKey.credits: NSAttributedString(string: "开发者:方君宇"),
    NSApplication.AboutPanelOptionKey.applicationVersion: "1.0.0",
])

6、orderFrontPreferencesPanel(_:):显示偏好设置(需手动实现)。

NSApp.orderFrontPreferencesPanel(nil)

只是一个“入口”,系统本身不会自动生成 UI。

需要手动将某个设置窗口打开。

可以在 Menu 中绑定此方法,然后实现它:

extension AppDelegate {
    @objc func orderFrontPreferencesPanel(_ sender: Any?) {
        preferencesWindow.makeKeyAndOrderFront(nil)
    }
}

四、菜单栏相关

1、mainMenu:顶部菜单栏。

NSApp.mainMenu

表示 App 顶部菜单栏(整个 ⌘ 菜单)。

可以获取或设置整个菜单栏结构。

可以配合NSMenu设置菜单栏。

2、servicesMenu:系统服务子菜单。

NSApp.servicesMenu

指向 App 的 “服务” 菜单(通常在“App名称”菜单中)。

服务菜单会显示 macOS 的通用服务(如“打开URL”、“新建邮件”等)。

如果不设置它,macOS 会自动管理。

3、helpMenu:帮助菜单。

NSApp.helpMenu

访问并自定义菜单栏中最右侧的 “帮助”菜单。

五、Dock 图标控制

1、dockTile:控制 Dock 上的图标及 badge 等。

let dockTile = NSApp.dockTile

设置图标上的红点数字(badge):

dockTile.badgeLabel = "3"

移除Badge:

dockTile.badgeLabel = nil

设置 Dock 图标的自定义内容视图:

let label = NSTextField(labelWithString: "🐷")
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 32)

NSApp.dockTile.contentView = label
NSApp.dockTile.display() // 一定要手动刷新

强制刷新 Dock 图标内容。

2、applicationIconImage:设置 Dock 图标。

NSApp.applicationIconImage = NSImage(named: "MyAppIcon")

更换 Dock 图标(例如运行期间换图标)。

注意:更换的是应用运行期间的 Dock 图标,而不是应用图标。

比 setApplicationIconImage(_:) 更推荐使用。

3、setApplicationIconImage(_:):更新图标(已弃用,改用上面属性)。

NSApp.setApplicationIconImage(myImage) // ⚠️ Deprecated

已被 applicationIconImage 属性替代。

在新项目中不建议使用。

六、代理与通知

1、NSApplicationDelegate:处理生命周期事件,如启动、终止、打开文件等。

常见方法有:

func applicationDidFinishLaunching(_ notification: Notification)
func applicationWillTerminate(_ notification: Notification)
func applicationDidBecomeActive(_ notification: Notification)
func applicationDidResignActive(_ notification: Notification)
func application(_ sender: NSApplication, openFile filename: String) -> Bool

使用方式:

class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        print("App 启动完成")
    }
    
    func applicationWillTerminate(_ notification: Notification) {
        print("App 即将退出")
    }
}

2、.didFinishLaunchingNotification:启动完成通知。

NotificationCenter.default.addObserver(
    forName: NSApplication.didFinishLaunchingNotification,
    object: nil,
    queue: .main
) { _ in
    print("启动完成(通知方式)")
}

等价于 applicationDidFinishLaunching(_:) 的通知形式。

如果不能接入 delegate,也可以监听这个通知。

3、.willTerminateNotification:即将退出通知。

NotificationCenter.default.addObserver(
    forName: NSApplication.willTerminateNotification,
    object: nil,
    queue: .main
) { _ in
    print("App 即将退出,保存数据")
}

保存数据、释放资源的好时机。

系统关机、用户手动退出、Cmd+Q 都会触发。

4、.didBecomeActiveNotification:激活通知。

NotificationCenter.default.addObserver(
    forName: NSApplication.didBecomeActiveNotification,
    object: nil,
    queue: .main
) { _ in
    print("App 被激活(前台)")
}

App 从后台切换到前台。

或首次启动时。

可用于刷新 UI、恢复动画等。

5、.didResignActiveNotification:失去焦点通知。

NotificationCenter.default.addObserver(
    forName: NSApplication.didResignActiveNotification,
    object: nil,
    queue: .main
) { _ in
    print("App 进入后台(失去活跃)")
}

用户切换到其他 App。

或打开系统对话框、弹窗等。

七、其他有用功能

1、打开文件:openFile(_:) / openFiles(_:)(通常由 delegate 实现)。

需要配置Info.plist以及delegate,双击支持的文件时,macOS唤起App,并调用 openFiles()。

2、打开 URL:openURLs(_:)(10.15+)。

需要配置Info.plist以及delegate,用户在浏览器打开 myapp://something 时,macOS 会调用 openURLs(_:),类似深层链接。

这两个方法需要通过 delegate 实现,复杂一些,这里不做多解释。

NSApp创建过程

在 SwiftUI 中,NSApplicationMain() 是由 Swift 编译器自动调用的:

@main
struct MyApp: App { ... }

Swift 会调用 _startApp()(内部封装了 NSApplicationMain 的逻辑),并:

1、创建 NSApplication.shared。

2、设置 NSApp。

3、调用 SwiftUI 生命周期逻辑。

4、自动注册 @NSApplicationDelegateAdaptor 的 delegate。

注意:NSApp是单例,所以不应该手动创建多个NSApplication实例。

此外,还可以自定义NSApplication类:

@objc(MyApplication)
class MyApplication: NSApplication {
    override func sendEvent(_ event: NSEvent) {
        print("Intercepted:", event)
        super.sendEvent(event)
    }
}

在 Info.plist 中修改:

<key>NSPrincipalClass</key>
<string>MyApplication</string>

但是,在SwiftUI中并不会显式调用NSApplication.run(),它内部使用了更高层的抽象(调用了 _startApp() 等内部逻辑),某些事件不会通过重写的 sendEvent(_:) 方法分发。

因此需要手动调用run()以触发sendEvent(_:)。这部分比较复杂,不在这里赘述。

和 @NSApplicationDelegateAdaptor 关系

当创建Appdelegate后,在入口文件声明 @NSApplicationDelegateAdaptor:

@main
struct ImageSlimApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    ...
}

SwiftUI在App初始化前,会创建自己指定的AppDelegate实例。

调用:

NSApp.delegate = appDelegate

在NSApp.run()执行前,触发:

appDelegate.applicationDidFinishLaunching(_:)

在退出前也会触发 applicationWillTerminate。

所以,@NSApplicationDelegateAdaptor的作用就是注册并持有一个生命周期代理(AppDelegate),同时自动接管 NSApp.delegate。

相关文章

SwiftUI macOS的commands菜单栏修饰符:https://fangjunyu.com/2025/06/19/swiftui-macos%e7%9a%84commands%e8%8f%9c%e5%8d%95%e6%a0%8f%e4%bf%ae%e9%a5%b0%e7%ac%a6/

   

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

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

发表回复

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