Как контролировать папку для новых файлов в swift?

Как я могу контролировать папку для новых файлов в swift, без опроса (что очень неэффективно)? Я слышал об API, таких как kqueue и FSEvents, но я не уверен, что можно реализовать их в swift?

Ответы

Ответ 1

GCD, кажется, путь. Классы NSFilePresenter не работают должным образом. Они глючат, сломаны, и Apple не хочет их исправлять последние 4 года. Вероятно, не рекомендуется.

Здесь очень хорошая публикация, которая описывает основы этой техники.

"Обработка событий файловой системы с помощью GCD", Дэвид Хэмрик.

Пример кода, приведенный на сайте. Я перевел его код на Свифт.

    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, опубликованный автором:


Если вы заинтересованы в просмотре каталогов, вот еще одна публикация, которая описывает это.

"Мониторинг папки с помощью GCD" на Cocoanetics. (к сожалению, я не смог найти имя автора. Прошу прощения за отсутствие указания авторства)

Единственным заметным отличием является получение дескриптора файла. Это делает дескриптор файла только для уведомлений о событиях для каталога.

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

Обновление

Ранее я утверждал, что API FSEvents не работает, но я ошибался. API работает очень хорошо, и если вам интересно смотреть на глубокое файловое дерево, то он может быть лучше, чем GCD по своей простоте.

В любом случае, FSEvents не может быть использован в чистых программах Swift. Поскольку это требует передачи функции обратного вызова C, и Swift не поддерживает ее в настоящее время (Xcode 6.1.1). Затем мне пришлось вернуться к Objective-C и снова обернуть его.

Кроме того, любой API такого типа полностью асинхронный. Это означает, что фактическое состояние файловой системы может отличаться во время получения уведомлений. Тогда точное или точное уведомление не очень полезно, а полезно только для пометки грязного флага.

Обновление 2

Я наконец-то закончил писать обертку вокруг FSEvents для Swift. Здесь моя работа, и я надеюсь, что это будет полезно.

Ответ 2

Я адаптировал код Станислава Смиды, чтобы он работал с 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()
  }

}

Ответ 4

SKQueue - это Swift-оболочка вокруг kqueue. Вот пример кода, который следит за каталогом и уведомляет о событиях записи.

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

Я попытался пойти с этими несколькими строками. Пока что работает.

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()
})

Ответ 6

Вы можете добавить UKKQueue в свой проект. См. http://zathras.de/angelweb/sourcecode.htm, который прост в использовании. UKKQueue написан в Objective C, но вы можете использовать его из swift

Ответ 7

В зависимости от ваших потребностей приложения вы можете использовать простое решение.

Я фактически использовал kqueue в продуктовом продукте; Я не был сумасшедшим в производительности, но это сработало, поэтому я не слишком много думал об этом, пока не нашел приятный небольшой трюк, который работал еще лучше для моих нужд, плюс он использовал меньше ресурсов, которые могут быть важны для повышения производительности программы.

Что вы можете сделать, если ваш проект позволяет, - это то, что каждый раз, когда вы переключаетесь на свое приложение, вы можете просто проверить папку как часть своей логики, вместо того, чтобы периодически проверять папку с помощью kqueue. Это работает и использует гораздо меньше ресурсов.

Ответ 8

Самый простой метод, который я нашел в настоящее время, это замечательная библиотека: https://github.com/eonist/FileWatcher

ОТ 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