如何在swift中监视新文件的文件夹?

时间:2014-06-10 20:13:51

标签: macos swift

如何在swift中监视文件夹中的新文件,而不进行轮询(效率非常低)?我听说过像kqueue和FSEvents这样的API - 但我不确定是否可以在swift中实现它们?

10 个答案:

答案 0 :(得分:10)

GCD似乎是要走的路。 NSFilePresenter个课程无法正常运作。他们的马车,破损,苹果在过去的4年里并不愿意修理它们。可能会被弃用。

这是一篇非常好的帖子,描述了这种技术的基本要素。

"Handling Filesystem Events with GCD" David Hamrick

网站引用的示例代码。我将他的C代码翻译成Swift。

    let fildes = open("/path/to/config.plist", O_RDONLY)

    let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    let source = dispatch_source_create(
        DISPATCH_SOURCE_TYPE_VNODE,
        UInt(fildes),
        DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_LINK | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
        queue)

    dispatch_source_set_event_handler(source,
        {
            //Reload the config file
        })

    dispatch_source_set_cancel_handler(source,
        {
            //Handle the cancel
        })

    dispatch_resume(source);

    ...

        // sometime later
        dispatch_source_cancel(source);

作为参考,这里是作者发布的另一个QAs:


如果你对观看目录感兴趣,可以在这里找到另一个描述它的帖子。

Cocoanetics 上的

"Monitoring a Folder with GCD"。 (不幸的是,我无法找到作者的姓名。我很抱歉缺少归因)

唯一明显的区别是获取文件描述符。这使得目录的仅事件通知文件描述符。

_fileDescriptor = open(path.fileSystemRepresentation(), O_EVTONLY)

更新

之前我声称FSEvents API不起作用,但我错了。 API工作得非常好,如果您对在深层文件树上观看感兴趣,那么它比GCD更简单。

无论如何,FSEvents不能用于纯Swift程序。因为它需要传递C回调函数,而Swift目前不支持它(Xcode 6.1.1)。然后我不得不回到Objective-C并重新包装它。

此外,任何此类API都是完全异步的。这意味着在您收到通知时,实际的文件系统状态可能会有所不同。然后,精确或准确的通知并不是真正有用,而且仅用于标记脏标志。

更新2

我最终为Swift写了FSEvents的包装器。 这是我的工作,我希望这会有所帮助。

答案 1 :(得分:5)

我修改了Stanislav Smida的代码,使其适用于Xcode 8和Swift 3

class DirectoryObserver {

    private let fileDescriptor: CInt
    private let source: DispatchSourceProtocol

    deinit {

      self.source.cancel()
      close(fileDescriptor)
    }

    init(URL: URL, block: @escaping ()->Void) {

      self.fileDescriptor = open(URL.path, O_EVTONLY)
      self.source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: self.fileDescriptor, eventMask: .all, queue: DispatchQueue.global())
      self.source.setEventHandler { 
          block()
      }
      self.source.resume()
  }

}

答案 2 :(得分:4)

最简单的解决方案是使用Apple的DirectoryMonitor.swift https://developer.apple.com/library/mac/samplecode/Lister/Listings/ListerKit_DirectoryMonitor_swift.html

var dm = DirectoryMonitor(URL: AppDelegate.applicationDocumentsDirectory)
dm.delegate = self
dm.startMonitoring()

答案 3 :(得分:2)

SKQueue是围绕kqueue的Swift包装器。以下是监视目录并通知写事件的示例代码。

class SomeClass: SKQueueDelegate {
  func receivedNotification(_ notification: SKQueueNotification, path: String, queue: SKQueue) {
    print("\(notification.toStrings().map { $0.rawValue }) @ \(path)")
  }
}

if let queue = SKQueue() {
  let delegate = SomeClass()

  queue.delegate = delegate
  queue.addPath("/some/file/or/directory")
  queue.addPath("/some/other/file/or/directory")
}

答案 4 :(得分:2)

我试过这几行。到目前为止似乎有效。

class DirectoryObserver {

    deinit {

        dispatch_source_cancel(source)
        close(fileDescriptor)
    }

    init(URL: NSURL, block: dispatch_block_t) {

        fileDescriptor = open(URL.path!, O_EVTONLY)
        source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, UInt(fileDescriptor), DISPATCH_VNODE_WRITE, dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT))
        dispatch_source_set_event_handler(source, { dispatch_async(dispatch_get_main_queue(), block) })
        dispatch_resume(source)
    }

    //

    private let fileDescriptor: CInt
    private let source: dispatch_source_t
}

请务必不要进入保留周期。如果您要在块中使用此实例的所有者,请安全地执行此操作。例如:

self.directoryObserver = DirectoryObserver(URL: URL, block: { [weak self] in

    self?.doSomething()
})

答案 5 :(得分:1)

带有GCD的Directory Monitor的Swift 5版本,最初来自Apple

import Foundation

/// A protocol that allows delegates of `DirectoryMonitor` to respond to changes in a directory.
protocol DirectoryMonitorDelegate: class {
    func directoryMonitorDidObserveChange(directoryMonitor: DirectoryMonitor)
}

class DirectoryMonitor {
    // MARK: Properties

    /// The `DirectoryMonitor`'s delegate who is responsible for responding to `DirectoryMonitor` updates.
    weak var delegate: DirectoryMonitorDelegate?

    /// A file descriptor for the monitored directory.
    var monitoredDirectoryFileDescriptor: CInt = -1

    /// A dispatch queue used for sending file changes in the directory.
    let directoryMonitorQueue =  DispatchQueue(label: "directorymonitor", attributes: .concurrent)

    /// A dispatch source to monitor a file descriptor created from the directory.
    var directoryMonitorSource: DispatchSource?

    /// URL for the directory being monitored.
    var url: URL

    // MARK: Initializers
    init(url: URL) {
        self.url = url
    }

    // MARK: Monitoring

    func startMonitoring() {
        // Listen for changes to the directory (if we are not already).
        if directoryMonitorSource == nil && monitoredDirectoryFileDescriptor == -1 {
            // Open the directory referenced by URL for monitoring only.
            monitoredDirectoryFileDescriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)

            // Define a dispatch source monitoring the directory for additions, deletions, and renamings.
            directoryMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: monitoredDirectoryFileDescriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: directoryMonitorQueue) as? DispatchSource

            // Define the block to call when a file change is detected.
            directoryMonitorSource?.setEventHandler{
                // Call out to the `DirectoryMonitorDelegate` so that it can react appropriately to the change.
                self.delegate?.directoryMonitorDidObserveChange(directoryMonitor: self)
            }

            // Define a cancel handler to ensure the directory is closed when the source is cancelled.
            directoryMonitorSource?.setCancelHandler{
                close(self.monitoredDirectoryFileDescriptor)

                self.monitoredDirectoryFileDescriptor = -1

                self.directoryMonitorSource = nil
            }

            // Start monitoring the directory via the source.
            directoryMonitorSource?.resume()
        }
    }

    func stopMonitoring() {
        // Stop listening for changes to the directory, if the source has been created.
        if directoryMonitorSource != nil {
            // Stop monitoring the directory via the source.
            directoryMonitorSource?.cancel()
        }
    }
}

答案 6 :(得分:1)

我遇到的任何答案都未提及的问题。由于我的应用程序使用UIDocumentBrowserViewController(即Apple自己的Files应用程序)来管理其文档,因此我无法控制用户的习惯。我当时使用SKQueue监视所有文件,以使元数据保持同步,并且在某个时刻该应用开始崩溃。

事实证明,应用程序可以同时打开256个文件描述符的上限,即使只是用于监视。我最终结合了SKQueue和Apple的Directory Monitor(可以在当前线程的this answer中找到该目录的引用)来创建一个名为SFSMonitor的类,该类通过使用以下命令监视整个文件或目录队列派遣来源。

我详细介绍了我的发现以及现在在this SO thread中使用的做法。

答案 7 :(得分:0)

您可以将UKKQueue添加到您的项目中。请参阅http://zathras.de/angelweb/sourcecode.htm它易于使用。 UKKQueue是用Objective C编写的,但你可以从swift

中使用它

答案 8 :(得分:0)

根据您的应用需求,您可以使用简单的解决方案。

我实际上在生产产品中使用了kqueue;我并没有因为性能而疯狂,但它确实有效,所以我没有想太多,直到我发现一个好的小技巧对我的需求更有效,而且,它使用的资源更少,这对于性能密集很重要程式。

如果您的项目允许,您可以做的是,每次切换到您的应用程序时,您只需将文件夹作为逻辑的一部分进行检查,而不必使用kqueue定期检查文件夹。这可以使用更少的资源。

答案 9 :(得分:0)

我发现我目前正在使用的最简单的方法是这个很棒的库:https://github.com/eonist/FileWatcher

From README

安装:

  • CocoaPods pod "FileWatcher"
  • 迦太基github "eonist/FileWatcher" "master"
  • 手动打开FileWatcherExample.xcodeproj
let filewatcher = FileWatcher([NSString(string: "~/Desktop").expandingTildeInPath])

filewatcher.callback = { event in
  print("Something happened here: " + event.path)
}

filewatcher.start() // start monitoring