//
// AppDelegate.swift
// Ice
//
import SwiftUI
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
private weak var appState: AppState?
// MARK: NSApplicationDelegate Methods
func applicationWillFinishLaunching(_ notification: Notification) {
guard let appState else {
Logger.appDelegate.warning("Missing app state in applicationWillFinishLaunching")
return
}
// Assign the delegate to the shared app state.
appState.assignAppDelegate(self)
// Allow the app to set the cursor in the background.
appState.setsCursorInBackground = true
}
func applicationDidFinishLaunching(_ notification: Notification) {
guard let appState else {
Logger.appDelegate.warning("Missing app state in applicationDidFinishLaunching")
return
}
// Dismiss the windows.
appState.dismissSettingsWindow()
appState.dismissPermissionsWindow()
// Hide the main menu to make more space in the menu bar.
if let mainMenu = NSApp.mainMenu {
for item in mainMenu.items {
item.isHidden = true
}
}
// Perform setup after a small delay to ensure that the settings window
// has been assigned.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
guard !appState.isPreview else {
return
}
// If we have the required permissions, set up the shared app state.
// Otherwise, open the permissions window.
switch appState.permissionsManager.permissionsState {
case .hasAllPermissions, .hasRequiredPermissions:
appState.performSetup()
case .missingPermissions:
appState.activate(withPolicy: .regular)
appState.openPermissionsWindow()
}
}
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
// Deactivate and set the policy to accessory when all windows are closed.
appState?.deactivate(withPolicy: .accessory)
return false
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
// MARK: Other Methods
/// Assigns the app state to the delegate.
func assignAppState(_ appState: AppState) {
guard self.appState == nil else {
Logger.appDelegate.warning("Multiple attempts made to assign app state")
return
}
self.appState = appState
}
/// Opens the settings window and activates the app.
@objc func openSettingsWindow() {
guard let appState else {
Logger.appDelegate.error("Failed to open settings window")
return
}
// Small delay makes this more reliable.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
appState.activate(withPolicy: .regular)
appState.openSettingsWindow()
}
}
}
// MARK: - Logger
private extension Logger {
static let appDelegate = Logger(category: "AppDelegate")
}
我没有swift开发经验。请尽可能详细地解释上述代码。
gemini answer
好的,我们来详细解释一下这段 Swift 代码。假设你没有任何 Swift 开发经验,我会用最通俗易懂的方式来讲解。
这段代码定义了一个名为 “Ice” 的 macOS 应用程序的 AppDelegate
。你可以把 AppDelegate
想象成这个应用程序的**“总管”或“事件经理”**。当 app 的生命周期中发生关键事件时,比如 app 启动、关闭窗口、或者即将退出时,操作系统就会通知这个“总管”,由它来负责处理。
核心功能总结
简单来说,这段代码为 “Ice” 这个 app 定义了启动时的核心行为。它的主要工作包括:
- 启动与初始化:处理 app 启动时的一系列设置流程。
- 权限检查:检查 app 是否获得了正常运行所必需的系统权限。如果没有,它会主动弹出窗口,引导用户去授权。
- 窗口管理:控制设置窗口和其他窗口的显示与隐藏。
- 后台运行:确保即使用户关闭了所有可见窗口,app 依然能在后台持续运行。这通常是菜单栏工具的典型特征。
代码逐段详解
让我们逐个部分来看代码。
类的定义
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
private weak var appState: AppState?
@MainActor
:这是一个现代 Swift 的特性,它告诉系统,这个类里面的所有代码都必须在主用户界面(UI)线程上运行。这对于任何可能影响用户界面的操作都至关重要,因为在后台线程更新 UI 会导致程序崩溃或显示异常。@MainActor
确保了所有操作都是平稳和安全的。final class AppDelegate
:这里声明了一个名为AppDelegate
的类(class)。class
是创建对象的蓝图。final
关键字表示其他类不能继承自AppDelegate
,这是一种代码优化。: NSObject, NSApplicationDelegate
:这部分定义了AppDelegate
的“身份”。NSObject
:是苹果开发框架中大多数类的基础构建块,提供了一些底层的核心功能。NSApplicationDelegate
:这是最重要的部分。它是一个**“协议”(protocol),就像一份合同或工作描述**。遵守了这个协议,AppDelegate
类就承诺会实现一些特定的函数,以便在发生应用级事件时,macOS 系统可以调用它们。
private weak var appState: AppState?
:这是一个属于类的属性(可以理解为变量)。private
:表示这个appState
属性只能在AppDelegate
这个类内部访问。appState: AppState?
:声明了一个名为appState
的变量,它将持有一个AppState
类型的对象。AppState
对象(在项目的其他地方定义)很可能保存了整个应用程序所有重要的数据和状态。问号?
表示这个属性是**“可选的”(Optional)**——它可能持有一个AppState
对象,也可能是nil
(空的),因为它在 app 刚启动时可能还没被赋值。weak
:这是一个非常重要的内存管理关键字,中文叫**“弱引用”。它意味着AppDelegate
对AppState
对象的引用是“弱”的。可以这样比喻:如果AppState
是一个实体,那AppDelegate
只是拿着一张写有它地址的便利贴**。如果AppState
这个实体被销毁了,这张便利贴上的地址(弱引用)就会自动变为空 (nil
)。这可以防止一种叫做**“循环引用”(或内存泄漏)**的问题,即两个对象互相强引用对方,导致谁也无法从内存中被释放。
NSApplicationDelegate 方法 (生命周期事件)
这些是 macOS 系统在特定时间点会自动调用的函数。
applicationWillFinishLaunching
(应用即将完成启动)
func applicationWillFinishLaunching(_ notification: Notification) {
// ...
appState.assignAppDelegate(self)
appState.setsCursorInBackground = true
}
这个函数在 app 启动后,但在完成所有设置(比如恢复窗口)之前被调用。
appState.assignAppDelegate(self)
:它告诉中心的appState
对象:“你好,我是这个 app 的总管(delegate)。这是我的引用 (self
)。” 这样就把 app 的两个核心部分连接了起来。appState.setsCursorInBackground = true
:这行代码似乎启用了一个功能,允许该应用程序即使在非激活状态(在后台)时也能改变鼠标光标的样式。
applicationDidFinishLaunching
(应用已完成启动)
func applicationDidFinishLaunching(_ notification: Notification) {
// ...
}
这是最重要的设置函数。它在应用程序完全启动并准备就绪后被调用。它按步骤执行以下操作:
appState.dismissSettingsWindow()
和appState.dismissPermissionsWindow()
:立即关闭设置和权限窗口,以防它们从上一次会话中残留。这确保了 app 每次都以一个干净的状态启动。- 隐藏主菜单:
这段代码会找到屏幕顶部的 app 主菜单(通常包含“文件”、“编辑”、“视图”等项目),并将其中的每一个项目都隐藏起来。这是一个强有力的信号,表明 “Ice” 是一个菜单栏工具应用。它的主要交互界面是一个位于屏幕右上角菜单栏中的小图标,因此不需要传统的主菜单,从而节省了菜单栏的空间。if let mainMenu = NSApp.mainMenu { for item in mainMenu.items { item.isHidden = true } }
- 延迟设置 (
DispatchQueue.main.asyncAfter
):
这行代码告诉 app:“等待 0.1 秒,然后执行花括号里的代码。” 这种微小的延迟是 UI 编程中的常见技巧,用于确保其他所有部分(如窗口系统)都已完全初始化,以防止出现“竞态条件”(race condition)。DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // ... 0.1秒后执行这里的代码 }
- 权限检查:在延迟执行的代码块中,它会检查 app 的权限状态。
- 如果权限充足 (
.hasAllPermissions
或.hasRequiredPermissions
),它会调用appState.performSetup()
。这个函数很可能包含了 app 的核心逻辑(例如创建菜单栏图标、启动后台任务等)。 - 如果缺少权限 (
.missingPermissions
),它会把应用程序激活到前台 (appState.activate(...)
),并显式地打开权限窗口 (appState.openPermissionsWindow()
),以引导用户去授予必需的系统访问权限。
- 如果权限充足 (
applicationShouldTerminateAfterLastWindowClosed
(最后一个窗口关闭后是否应终止)
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
appState?.deactivate(withPolicy: .accessory)
return false
}
当用户关闭一个应用的最后一个可见窗口时,macOS 会问这个问题:“现在应该退出应用吗?”
return false
:这个函数返回false
,它告诉系统:“不,不要退出。” 这是菜单栏应用的经典行为。我们希望即使关闭了设置窗口,它也能继续在后台运行。appState?.deactivate(withPolicy: .accessory)
:当最后一个窗口关闭时,这行代码将 app 的“策略”更改为**“附件”(accessory)**应用。这意味着它将不再显示在 Dock 栏或Cmd+Tab
应用切换器中。它变成了一个纯粹的后台/菜单栏进程。
其他方法
这些是为 AppDelegate
编写的自定义辅助函数。
assignAppState
func assignAppState(_ appState: AppState) {
// ...
self.appState = appState
}
这个方法在 applicationWillFinishLaunching
中被调用。它唯一的工作就是接收 AppState
对象,并将其存储到 self.appState
属性中。它还包含一个安全检查,如果某个东西试图多次赋值,就会记录一个警告日志。
openSettingsWindow
@objc func openSettingsWindow() {
// ...
appState.activate(withPolicy: .regular)
appState.openSettingsWindow()
}
这个函数设计为从其他地方调用,比如当用户点击菜单栏中的 app 图标时。
@objc
:这个关键字使该函数能够被苹果一些较旧的、使用 Objective-C 语言运行时的框架所调用。这对于处理按钮点击等事件通常是必需的。appState.activate(withPolicy: .regular)
:在打开窗口之前,它将 app 的策略改回**“常规”(regular)**。这使得 app 在设置窗口打开期间,能像一个普通应用一样再次出现在 Dock 栏和Cmd+Tab
切换器中。appState.openSettingsWindow()
:这个命令会真正地向用户显示设置窗口。为了可靠性,该函数同样使用了 0.1 秒的微小延迟。
Logger 扩展
private extension Logger {
static let appDelegate = Logger(category: "AppDelegate")
}
这只是一个方便的小工具。它为 Logger
类本身添加了一个预先配置好的实例。这让程序员可以写出像 Logger.appDelegate.warning("发生了一些事")
这样的清晰日志,并且输出的日志会自动标记上 “AppDelegate” 的分类,让调试过程变得更加容易。