You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在macOS上监听文件的打开与关闭(Swift实现)

监听文件打开与关闭的Swift实现方案

结论:可以实现,但FSEventStream和普通kqueue的文件系统事件监听无法直接捕获文件打开/关闭操作——这类事件属于进程的文件描述符操作,而非文件系统元数据变更。下面提供两种可行方案:

方案一:使用EndpointSecurity框架(推荐)

EndpointSecurity是Apple官方提供的系统级监控框架,能够精准捕获进程的文件open和close操作。需注意:使用该框架需要开启开发者模式或申请SIP豁免,且应用需获得对应系统权限。

实现步骤

  1. 向项目添加EndpointSecurity框架依赖
  2. 创建ES客户端并注册文件打开/关闭事件
  3. 在回调中过滤目标文件的操作事件

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 07:17:05