如何在swift中监视文件夹中的新文件,而不进行轮询(效率非常低)?我听说过像kqueue和FSEvents这样的API - 但我不确定是否可以在swift中实现它们?
答案 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都是完全异步的。这意味着在您收到通知时,实际的文件系统状态可能会有所不同。然后,精确或准确的通知并不是真正有用,而且仅用于标记脏标志。
我最终为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
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