如何快速监控文件夹中的新文件?
Posted
技术标签:
【中文标题】如何快速监控文件夹中的新文件?【英文标题】:How to monitor a folder for new files in swift? 【发布时间】:2014-06-10 20:13:50 【问题描述】:如何在没有轮询的情况下快速监控文件夹中的新文件(效率非常低)?我听说过诸如 kqueue 和 FSEvents 之类的 API,但我不确定是否可以快速实现它们?
【问题讨论】:
FSEventStreamCreate
从 Swift 中丢失,不允许纯 Swift 实现。但是,您可以创建一个 Obj-C 包装类并使用 Swift 对其进行操作。我不得不对 CommonCrypto 进行类似的哈希处理。
注意:这是一个关于这个的旧讨论:***.com/questions/7720246/…
我看到了一个我正在努力使用的 FSEventStreamCreate 的 Swift 版本(由于完全缺乏 Swift 知识......)developer.apple.com/library/mac/documentation/Darwin/Reference/…
我发现blog.beecomedigital.com/2015/06/27/… 看起来很有希望,但不幸的是不能很好地处理内存。我只需要知道添加了哪个文件,但似乎没有一个简单的解决方案。
我找到了这个github.com/gurinderhans/SwiftFSWatcher。它甚至允许为不同的地方创建多个monitors
。
【参考方案1】:
GCD 似乎是要走的路。 NSFilePresenter
类不能正常工作。它们有问题,坏了,Apple 在过去的 4 年里一直不愿意修复它们。可能会被弃用。
这是一篇很好的帖子,描述了这项技术的要点。
"Handling Filesystem Events with GCD",大卫·哈姆里克。
从网站引用的示例代码。我将他的 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);
作为参考,这是作者发布的另一个QA:
Grand Central Dispatch (GCD) dispatch source flags Monitoring a directory in Cocoa/Cocoa Touch如果您有兴趣观看目录,这里有另一篇描述它的帖子。
"Monitoring a Folder with GCD" 在 Cocoanetics 上。 (不幸的是,我找不到作者的名字。我很抱歉缺少署名)
唯一明显的区别是获取文件描述符。这使得目录的事件通知仅文件描述符。
_fileDescriptor = open(path.fileSystemRepresentation(), O_EVTONLY)
更新
之前我声称FSEvents
API 不工作,但我错了。该 API 运行良好,如果您有兴趣观看深度文件树,那么它的简单性可能比 GCD 更好。
无论如何,FSEvents 不能在纯 Swift 程序中使用。因为它需要传递 C 回调函数,而 Swift 目前不支持它(Xcode 6.1.1)。然后我不得不退回到 Objective-C 并再次包装它。
此外,任何此类 API 都是完全异步的。这意味着在您收到通知时,实际的文件系统状态可能会有所不同。那么精确或准确的通知并没有真正的帮助,仅对标记脏标志有用。
更新 2
我最终为 Swift 编写了一个围绕 FSEvents
的包装器。
这是我的工作,希望对您有所帮助。
【讨论】:
不错。我试图“听”一次被写入的文件夹,但因为无法让FSEvents
正常工作而放弃了。我一直在等待由相机驱动程序写入非常大的图像文件,并且在文件“准备好”打开、以黑色图像结束等的正确时刻得到通知。另外,我看到了一些广播的消息不一致,很难解释它们。也许我没有正确理解文档,也没有遇到好的示例代码。肯定会尝试 GCD。
谢谢。我刚刚通过直接从 GitHub 克隆来使用您的框架,并关注您的视频并构建!第一次工作。所以,现在我只需要学习一些 Swift 来使用它! :-)
嘿@Eonil 你知道我如何检测文件何时更新/修改吗?我看到了您的近似值,并且我尝试了更多我发现的方法,但是当文件更新时没有触发。
NSFilePresenter
不是一个类,它是一个协议。它本身没有任何功能。它通常与NSFileCoordinator
一起使用,并且此类不是用于监视目录,而是用于在单个进程内以及跨进程边界协调文件系统上的读/写 I/O 操作,并且使用 NSFileCoordinator
可以完美地工作。跨度>
【参考方案2】:
我改编了 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()
【讨论】:
用 Swift 4.2 测试过,仍然很好用。谢谢! 2021 年。代码有效。很棒的文件监视器!看起来只有一个没有子目录的目录。只是我搜索的想法!非常感谢!【参考方案3】:最简单的解决方案是使用 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()
【讨论】:
链接已损坏。 是的,苹果已经删除了这个链接( Swift 3 版本镜像于:github.com/robovm/apple-ios-samples/blob/master/…【参考方案4】: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")
【讨论】:
【参考方案5】:用于目录监视器的 Swift 5 版本,带有 GCD,来自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】:我尝试过使用这几行代码。到目前为止似乎有效。
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()
)
【讨论】:
很棒的代码。下面是一个监控桌面的例子: let theURL = NSURL("~/Desktop/".stringByExpandingTildeInPath) 您的代码仅在添加或删除文件时有效。不是文件更改。无论如何都很棒。我需要监控文件更改,所以我一直在寻找。 FSEvent 似乎是我正在寻找的。span> 【参考方案7】:我发现我目前正在使用的最简单的方法是这个很棒的库:https://github.com/eonist/FileWatcher
From README
安装:
CocoaPodspod "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
【讨论】:
但是效率高吗? 非常适合我。没有性能问题。你的用例是什么?【参考方案8】:我遇到了任何答案中都没有提到的问题。由于我的应用程序使用 UIDocumentBrowserViewController(即 Apple 自己的 Files 应用程序)来管理其文档,因此我无法控制用户的习惯。我使用SKQueue 监控所有文件以保持元数据同步,但在某个时候应用程序开始崩溃。
事实证明,一个应用程序可以同时打开的文件描述符上限为 256 个,即使只是为了监控也是如此。我最终将 SKQueue 和 Apple 的 Directory Monitor(您可以在当前线程的 this answer 中找到的参考)结合起来创建了一个名为 SFSMonitor 的类,它使用 Dispatch Sources 监视整个文件或目录队列。
我在this SO thread 中详细介绍了我的发现和我现在使用的做法。
【讨论】:
它是否监控子目录? 每个目录和子目录都作为一个单独的实体进行监控。假设您将一个子目录复制到一个受监控的目录中:您将收到该更改的通知,但您不会知道该子目录中发生的任何事情。您必须检查添加了什么,当您意识到它是一个子目录时,将其添加到您的监视列表中。换句话说 - 观察不是递归的,就原始目录而言,子目录只是另一个条目。【参考方案9】:您可以将 UKKQueue 添加到您的项目中。请参阅http://zathras.de/angelweb/sourcecode.htm,它很容易使用。 UKKQueue 是用 Objective C 编写的,但你可以从 swift 中使用它
【讨论】:
【参考方案10】:根据您的应用需求,您或许可以使用简单的解决方案。
我实际上在生产产品中使用了 kqueue;我对性能并不疯狂,但它确实有效,所以我并没有想太多,直到我找到了一个可以更好地满足我的需求的小技巧,此外,它使用的资源更少,这对于性能密集型来说很重要程式。
再次,如果您的项目允许,您可以做的是,每次切换到应用程序时,您只需检查文件夹作为逻辑的一部分,而不必使用 kqueue 定期检查文件夹。这很有效,而且使用的资源要少得多。
【讨论】:
我认为 Atom.io 在断言文件是否已通过 git 推送时会这样做。必须在应用程序之间来回切换才能看到变化,这非常烦人。这对我来说是一种 UI/UX 反模式。以上是关于如何快速监控文件夹中的新文件?的主要内容,如果未能解决你的问题,请参考以下文章
Google Cloud Storage:如何使用 gsutil 获取存储桶/文件夹中的新文件列表