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

Swift/Cocoa实现macOS文件夹监听:仅监听文件增删(不含子文件夹)

macOS 文件夹变更监听(仅直接子项)实现方案

嘿,这个需求用系统原生的NSFilePresenter配合NSFileCoordinator完全能搞定,不用依赖任何第三方框架,我给你一步步拆解具体实现:

核心思路

NSFilePresenter是Apple提供的用于监听文件/文件夹变更的原生API,它能和NSFileCoordinator协同工作,精准捕获文件系统的各类变更事件。我们只需要实现该协议的关键方法,再过滤掉子文件夹内的变更,就能只监听目标文件夹的直接子项添加/移除事件。

完整代码实现

首先创建一个监听类,遵循NSFilePresenter协议:

import Foundation

class FolderMonitor: NSObject, NSFilePresenter {
    // 要监听的目标文件夹URL
    private let targetFolderURL: URL
    // 处理变更通知的操作队列(可根据需求指定主队列/后台队列)
    private let processingQueue: OperationQueue
    
    init(folderURL: URL) {
        self.targetFolderURL = folderURL
        self.processingQueue = OperationQueue()
        super.init()
        
        // 注册当前实例为文件监听者
        NSFileCoordinator.addFilePresenter(self)
    }
    
    deinit {
        // 销毁时移除监听,避免内存泄漏
        NSFileCoordinator.removeFilePresenter(self)
    }
    
    // MARK: - NSFilePresenter 协议必填方法
    var presentedItemURL: URL? {
        return targetFolderURL
    }
    
    var presentedItemOperationQueue: OperationQueue {
        return processingQueue
    }
    
    // MARK: - 监听子项添加事件
    func presentedSubitemDidAppear(at url: URL) {
        // 只处理目标文件夹的直接子项,过滤子文件夹内的变更
        guard url.deletingLastPathComponent() == targetFolderURL else {
            return
        }
        
        // 区分是文件还是文件夹(可选)
        var isDirectory: ObjCBool = false
        if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
            let itemType = isDirectory.boolValue ? "文件夹" : "文件"
            print("\(itemType)已添加: \(url.lastPathComponent)")
        }
        
        // 在这里添加你的业务逻辑,比如通知UI更新等
        // 如果需要更新UI,记得切换到主队列:
        // DispatchQueue.main.async { /* 更新UI代码 */ }
    }
    
    // MARK: - 监听子项移除事件
    func presentedSubitemDidDisappear(at url: URL) {
        guard url.deletingLastPathComponent() == targetFolderURL else {
            return
        }
        
        var isDirectory: ObjCBool = false
        if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
            let itemType = isDirectory.boolValue ? "文件夹" : "文件"
            print("\(itemType)已移除: \(url.lastPathComponent)")
        }
        
        // 处理移除后的业务逻辑
    }
    
    // 可选:监听文件夹自身属性变更(比如重命名、权限修改等)
    func presentedItemDidChange() {
        print("目标文件夹属性已变更")
    }
}

使用示例

在需要监听的地方初始化这个类,注意必须保持实例的强引用,否则监听会自动停止:

// 示例:监听用户文档目录
if let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
    let folderMonitor = FolderMonitor(folderURL: documentsDir)
    // 务必将folderMonitor存储在全局变量/类属性中,避免被销毁
}

关键注意点

  • 强引用监听实例:如果FolderMonitor实例被释放(比如作用域结束),deinit会触发并移除监听,导致后续无法接收变更通知。一定要把它存在全局变量或者类的属性里。
  • 过滤子文件夹变更:通过判断变更项的父URL是否等于目标文件夹URL,确保我们只处理直接子项的变化,忽略子文件夹内部的操作。
  • 操作队列选择:processingQueue可以指定为OperationQueue.main(如果需要直接更新UI),或者后台队列(处理耗时逻辑),根据你的业务需求调整。
  • 系统兼容性:这个方案适用于所有支持NSFilePresenter的macOS版本(macOS 10.7+),完全原生,不需要额外依赖。

内容的提问来源于stack exchange,提问作者eivindml

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:16:00