AXObserver 是 macOS 无障碍(Accessibility)API 中的一个核心类,用于监听辅助功能对象(通常是 App 窗口、UI 元素等)状态的变化。它属于 Application Services / Accessibility 框架中的一部分,定义在 AXObserver.h 中。
基本用途
1、监听某个应用窗口的位置是否发生变化;
2、监听某个控件(按钮、文本框等)值是否改变;
3、获取焦点改变通知(哪个 UI 元素获得焦点);
4、监控 UI 事件(比如按钮点击、菜单变化);
基本用法
import ApplicationServices
// 1. 定义回调函数
static let callback: AXObserverCallback = { observer, element, notification, context in
print("收到 AX 通知: \(notification)")
}
// 2. 获取目标 App 的 AXUIElement
let pid: pid_t = ... // 目标进程 ID
let appElement = AXUIElementCreateApplication(pid)
// 3. 创建 AXObserver
var observer: AXObserver?
AXObserverCreate(pid, callback, &observer)
// 4. 添加通知监听
AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)
// 5. 将 observer 添加到主运行循环中
let runLoopSource = AXObserverGetRunLoopSource(observer!)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode)
代码解析
1、引入macOS框架
import ApplicationServices
引入 macOS 系统级框架 ApplicationServices,它包含无障碍(Accessibility)接口,用于访问和监听其他 App 的 UI 状态。
2、定义回调函数
let callback: AXObserverCallback = { observer, element, notification, context in
print("收到 AX 通知: \(notification)")
}
AXObserver 使用 C 风格的回调机制,当被监听对象发出通知时,会调用这个函数。
参数含义:
observer: 当前的 AXObserver 对象。
element: 发送通知的 AXUIElement(通常是某个窗口或控件)。
notification: 通知类型(如窗口焦点变化、控件值改变等)。
context: 用户提供的上下文(可选,便于区分不同场景)。
3、获取目标App的AXUIElement
let pid: pid_t = ... // 目标进程 ID
let appElement = AXUIElementCreateApplication(pid)
AXUIElementCreateApplication(pid):获取目标 App 的根 AXUIElement 对象,pid 是目标 App 的进程 ID。
所有对目标 App 的监听操作,都以这个 appElement 为起点。
… 需要替换成实际监听的 App 的 PID,比如用 NSRunningApplication 获取Chrome浏览器的PID:
guard let ChromeApp = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == "com.google.Chrome" }) else { return }
let pid: pid_t = ChromeApp.processIdentifier // 目标进程 ID
let appElement = AXUIElementCreateApplication(pid)
4、创建AXObserver
var observer: AXObserver?
AXObserverCreate(pid, callback, &observer)
创建 AXObserver,创建后observer绑定AXObserver对象和回调函数,关联的是指定 pid 的进程。
callback类型为AXObserverCallback:
typealias AXObserverCallback = @convention(c) (
AXObserver,
AXUIElement,
CFString,
UnsafeMutableRawPointer?
) -> Void
对应的是C函数指针,因此需要使用@convention(c) 修饰的闭包或者全局函数。
&observer 是输出参数,函数调用成功后 observer 就变成有效对象了。
5、添加通知监听
AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)
给指定元素(appElement)添加通知监听。
kAXFocusedWindowChangedNotification 是系统定义的通知类型,表示“焦点窗口变化”时,会触发observer方法进行
最后一个参数是上下文指针,一般可传 nil。
注意:如果要监听多个通知、多个元素,可以多次调用这个函数。
6、添加到主运行循环中
let runLoopSource = AXObserverGetRunLoopSource(observer!)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode)
AXObserver 不会自动处理通知,必须把它的事件源添加到当前线程的 RunLoop 中,才能定期轮询事件。
通常在 UI 主线程运行(即 CFRunLoopGetCurrent())。
.defaultMode 是默认的运行模式。
7、实现效果
监听某个App(指定pid),当焦点窗口变化时,会触发回调,并在控制台打印通知名。
注意事项
1、必须获得辅助功能权限(如开启“屏幕录制”或“辅助功能”权限),可以在入口文件或AppDelegate中添加辅助功能的检测:
let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: true] as CFDictionary
let isTrusted = AXIsProcessTrustedWithOptions(options)
2、可通过代码判断当前权限状态:
if !AXIsProcessTrusted() {
print("请前往设置中授权辅助功能")
}
3、在Info.plist文件中添加NSAppleEventsUsageDescription权限:
<key>NSAppleEventsUsageDescription</key>
<string>需要访问其他应用以实现辅助功能</string>
4、监听其他应用时,需要 AXIsProcessTrusted() 返回为 true;
5、AXObserver 是 C API,Swift 中使用略显复杂,很多人封装后使用;
6、一旦 App 被关闭或重启,Observer 也需要重新创建。
7、类名和系统类型不能冲突,如果使用AXObserver作为类名,在调用AXObserverCreate方法时:
AXObserverCreate(pid, callback, &observer)
Xcode会报错:
Cannot convert value of type 'test.AXObserver' to expected argument type 'ApplicationServices.AXObserver'
常用通知
1、kAXFocusedWindowChangedNotification:焦点窗口发生变化;
AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)
2、kAXValueChangedNotification:控件的值变了;
3、kAXTitleChangedNotification:标题变化;
AXObserverAddNotification(observer!, appElement, kAXTitleChangedNotification as CFString, nil)
4、kAXWindowCreatedNotification:窗口被创建;
AXObserverAddNotification(observer!, appElement, kAXWindowCreatedNotification as CFString, nil)
5、kAXUIElementDestroyedNotification:UI 元素被销毁。
使用场景
1、开发 macOS 辅助工具,如聚焦辅助(自动获取焦点的文本框内容);
2、开发窗口管理工具(如 Rectangle、Amethyst);
3、获取 UI 状态变化用于自动化测试、UI 抓取工具;
4、监听其他应用的行为,比如微信收到消息后窗口闪动。
总结
经过实际的测试,发现只有关闭App Sandbox和Hardened Runtime的情况下:

才能实现AXObserver的监听。
默认情况下,AXObserver不会弹出辅助功能的授权,必须在入口文件或AppDelegate中添加检测代码:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// 检测“辅助功能”的代码
let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: true] as CFDictionary
let isTrusted = AXIsProcessTrustedWithOptions(options)
添加检测代码后,如果没有开启辅助功能,才会弹出“辅助功能访问”的提示:

否则,不会弹出辅助功能的授权。
当关闭App Sandbox和Hardened Runtime以及授权“辅助功能”后,才可以实现AXObserver的监听:

总体来讲,可能并不适用于Swift开发新的产品,因为现在的mac应用上架App Store都需要开启App Sandbox和Hardened Runtime,这里存在矛盾。
相关文章
1、macOS获取当前运行应用NSRunningApplication:https://fangjunyu.com/2025/07/25/macos%e8%8e%b7%e5%8f%96%e5%bd%93%e5%89%8d%e8%bf%90%e8%a1%8c%e5%ba%94%e7%94%a8nsrunningapplication/
完整代码
AXMonitor文件代码
import ApplicationServices
import AppKit
class AXMonitor {
var observer: AXObserver?
var runLoopSource:CFRunLoopSource?
// 定义回调函数
let callback: AXObserverCallback = { observer, element, notification, context in
print("Received AX notification: \(notification)")
}
func startMonitoringChrome() {
guard let chrome = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == "com.apple.Safari" }) else { return }
let pid: pid_t = chrome.processIdentifier // 目标进程 ID
let appElement = AXUIElementCreateApplication(pid)
// 创建 AXObserver
let result = AXObserverCreate(pid, callback, &observer)
if result != .success {
print("创建 observer 失败: \(result.rawValue)")
return
}
if observer != nil {
print("observer 不为 nil")
AXObserverAddNotification(observer!, appElement, kAXWindowCreatedNotification as CFString, nil)
AXObserverAddNotification(observer!, appElement, kAXTitleChangedNotification as CFString, nil)
AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)
// 将 observer 添加到主运行循环中
runLoopSource = AXObserverGetRunLoopSource(observer!)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode)
}
}
}