Comment surveiller un dossier pour les nouveaux fichiers dans Swift, sans interrogation (ce qui est très inefficace)? J'ai entendu parler d'API telles que kqueue et FSEvents - mais je ne suis pas sûr qu'il soit possible de les implémenter rapidement?
GCD semble être la voie à suivre. Les classes NSFilePresenter
ne fonctionnent pas correctement. Ils sont défectueux, cassés et Apple ne veut pas les réparer depuis 4 ans. Susceptible d'être obsolète.
Voici un très joli commentaire décrivant les bases de cette technique.
"Gestion des événements du système de fichiers avec GCD" , par David Hamrick .
Exemple de code cité sur le site. J'ai traduit son code C en 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);
Pour référence, voici un autre QA posté par l'auteur:
Si vous souhaitez regarder des annuaires, voici un autre message qui le décrit.
"Surveillance d'un dossier avec GCD" on Cocoanetics . (malheureusement, je n'ai pas trouvé le nom de l'auteur. Je suis désolé de ne pas avoir attribué d'attribution)
La seule différence notable consiste à obtenir un descripteur de fichier. Cela rend le descripteur de fichier de notification d'événement uniquement pour un répertoire.
_fileDescriptor = open(path.fileSystemRepresentation(), O_EVTONLY)
Auparavant, j'ai prétendu que FSEvents
API ne fonctionnait pas, mais j'avais tort. L'API fonctionne très bien, et si vous souhaitez regarder sur une arborescence de fichiers profonde, elle peut être meilleure que GCD par sa simplicité.
Quoi qu'il en soit, FSEvents ne peut pas être utilisé dans des programmes Swift purs. Parce qu'il nécessite la transmission de la fonction de rappel C et que Swift ne la prend actuellement pas en charge (Xcode 6.1.1). Ensuite, j'ai dû revenir à Objective-C et l'envelopper à nouveau.
En outre, toutes les API de ce type sont entièrement asynchrones. Cela signifie que l'état du système de fichiers peut être différent au moment où vous recevez les notifications. Dans ce cas, une notification précise ou exacte n’est pas vraiment utile, elle n’est utile que pour marquer un drapeau sale.
J'ai finalement fini par écrire une enveloppe autour de FSEvents
pour Swift. Voici mon travail et j'espère que cela vous sera utile.
La solution la plus simple consiste à utiliser 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()
J'ai adapté le code de Stanislav Smida pour qu'il fonctionne avec Xcode 8 et 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()
}
}
J'ai essayé d'aller avec ces quelques lignes. Jusqu'ici semble fonctionner.
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
}
Assurez-vous de ne pas entrer dans le cycle de conservation. Si vous souhaitez utiliser le propriétaire de cette instance en bloc, faites-le en toute sécurité. Par exemple:
self.directoryObserver = DirectoryObserver(URL: URL, block: { [weak self] in
self?.doSomething()
})
SKQueue est un wrapper Swift autour de kqueue. Voici un exemple de code qui surveille un répertoire et notifie les événements d'écriture.
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")
}
Vous pouvez ajouter UKKQueue à votre projet. Voir http://zathras.de/angelweb/sourcecode.htm il est facile à utiliser. UKKQueue est écrit en Objective C, mais vous pouvez l’utiliser à partir de Swift.
Selon les besoins de votre application, vous pourrez peut-être utiliser une solution simple.
J'ai effectivement utilisé kqueue dans un produit de production; Je n'étais pas folle de la performance, mais cela a fonctionné. Je n'y ai donc pas trop réfléchi avant de trouver un petit truc sympa qui fonctionnait encore mieux pour mes besoins. De plus, il utilisait moins de ressources, ce qui peut être important pour les performances intensives. programmes.
Si votre projet le permet, ce que vous pouvez faire encore une fois, c’est que chaque fois que vous passez à votre application, vous pouvez simplement vérifier le dossier dans le cadre de votre logique, au lieu d’avoir à le vérifier périodiquement à l’aide de kqueue. Cela fonctionne et utilise beaucoup moins de ressources.