如何在macOS上监听文件的打开与关闭(Swift实现)
监听文件打开与关闭的Swift实现方案
结论:可以实现,但FSEventStream和普通kqueue的文件系统事件监听无法直接捕获文件打开/关闭操作——这类事件属于进程的文件描述符操作,而非文件系统元数据变更。下面提供两种可行方案:
方案一:使用EndpointSecurity框架(推荐)
EndpointSecurity是Apple官方提供的系统级监控框架,能够精准捕获进程的文件open和close操作。需注意:使用该框架需要开启开发者模式或申请SIP豁免,且应用需获得对应系统权限。
实现步骤
- 向项目添加EndpointSecurity框架依赖
- 创建ES客户端并注册文件打开/关闭事件
- 在回调中过滤目标文件的操作事件
Swift示例代码
import EndpointSecurity import Dispatch class FileOpenCloseMonitor { private var client: ESClient? private let targetFilePaths: [String] private let queue = DispatchQueue(label: "com.example.FileMonitorQueue") init(targetFilePaths: [String]) { self.targetFilePaths = targetFilePaths } func startMonitoring() throws { // 初始化ES客户端,指定事件处理回调 let client = try ESClient(dispatchQueue: queue) { [weak self] event in guard let self = self else { return } // 处理文件打开事件 if case .open(let openEvent) = event { let filePath = openEvent.target.file.path if self.targetFilePaths.contains(filePath) { print("文件被打开:\(filePath),进程:\(openEvent.process.executablePath)") } } // 处理文件关闭事件 if case .close(let closeEvent) = event { let filePath = closeEvent.target.file.path if self.targetFilePaths.contains(filePath) { print("文件被关闭:\(filePath),进程:\(closeEvent.process.executablePath)") } } } // 订阅open和close事件类型 try client.subscribe(to: [.open, .close]) self.client = client } func stopMonitoring() { client?.unsubscribe() client = nil } } // 使用示例 do { let monitor = FileOpenCloseMonitor(targetFilePaths: ["/Users/yourname/test.txt"]) try monitor.startMonitoring() RunLoop.main.run() // 保持程序运行以接收事件 } catch { print("监控启动失败:\(error)") }
注意事项
- 需在
Info.plist中添加权限说明(如NSAppleEventsUsageDescription) - 运行时需在「系统偏好设置→安全性与隐私→隐私→完全磁盘访问」中开启应用权限
- 仅支持macOS 10.15及以上版本
方案二:改进kqueue的VNODE事件监听(局限性较大)
kqueue的EVFILT_VNODE可监听文件状态变化,但无法直接捕获关闭事件,仅能通过写入、扩展等事件间接推断文件的打开/修改行为,仅适用于特定场景。
Swift示例代码
import Foundation class KQueueFileMonitor { private var kqueue: Int32 = -1 private var fileDescriptors: [Int32: String] = [:] private let queue = DispatchQueue(label: "com.example.KQueueMonitor") init() throws { kqueue = kqueue() guard kqueue != -1 else { throw NSError(domain: "KQueueError", code: errno, userInfo: nil) } } func addFile(path: String) throws { let fd = open(path, O_EVTONLY) guard fd != -1 else { throw NSError(domain: "FileOpenError", code: errno, userInfo: nil) } var event = kevent() event.ident = UInt(fd) event.filter = Int16(EVFILT_VNODE) event.flags = UInt16(EV_ADD | EV_CLEAR) event.fflags = NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_ATTRIB let result = kevent(kqueue, &event, 1, nil, 0, nil) guard result != -1 else { throw NSError(domain: "KQueueEventError", code: errno, userInfo: nil) } fileDescriptors[fd] = path startMonitoringLoop() } private func startMonitoringLoop() { queue.async { [weak self] in guard let self = self else { return } var event = kevent() while true { let count = kevent(self.kqueue, nil, 0, &event, 1, nil) guard count > 0 else { continue } if let path = self.fileDescriptors[Int32(event.ident)] { switch event.fflags { case NOTE_WRITE, NOTE_EXTEND: print("文件可能被打开并修改:\(path)") case NOTE_DELETE: print("文件被删除:\(path)") default: print("文件状态变化:\(path),flags: \(event.fflags)") } } } } } deinit { for fd in fileDescriptors.keys { close(fd) } if kqueue != -1 { close(kqueue) } } } // 使用示例 do { let monitor = try KQueueFileMonitor() try monitor.addFile(path: "/Users/yourname/test.txt") RunLoop.main.run() } catch { print("监控启动失败:\(error)") }
局限性
- 无法准确区分打开与关闭事件,仅能间接推断
- 无法监控只读打开的文件(不会触发WRITE/EXTEND事件)
为什么你的FSEventStream代码无法捕获打开/关闭?
你提供的代码中,kFSEventStreamCreateFlagFileEvents仅能监听文件的创建、删除、修改等元数据变更事件,而文件打开/关闭操作不会修改文件系统元数据(如修改时间、权限),因此FSEventStream无法捕获这类事件。
内容的提问来源于stack exchange,提问作者Lupurus
相关产品推荐
相关产品推荐

