NSFileCoordinator/NSFilePresenterを使ってディレクトリ監視

# はじめに
ちょっとディレクトリの監視をしたかったのでNSFileCoordinator/NSFilePresenterについて少し調べました。

[ファイルシステムプログラミングガイド](https://developer.apple.com/jp/documentation/FileSystemProgrammingGuide.pdf)を読んだところ、ディレクトリを監視するには以下の二つの方法があるそうです。

* FSEvents
* ファイルコーディネータ/ファイルプレゼンタ

その中でFSEventは、CベースのAPIなので使うのがダルそうです。
[swiftラッパーのソース](https://github.com/Eonil/FileSystemEvents)も見つけたのですが、あんまりメンテされてなさそうだったので、ファイルコーディネータ/ファイルプレゼンタを使ってみることにしました。

ちょっとファイル監視するくらいなら簡単にできます。

# 実装
ここでは、NSFileCoordinator/NSFilePresenterを使って指定したディレクトリ内のファイルの追加、更新、削除を監視してみます。

## 流れ
1. ファイル/ディレクトリを管理するオブジェクトにNSFilePresenterプロトコルを指定する。
2. NSFileCoordinatorのaddFilePresenter:クラスを呼び出してオブジェクトを登録する。
3. NSFilePresenterのメソッド内にそれぞれの処理を書く
4. 管理が必要なくなるタイミングでNSFileCoordinatorのremoveFilePresenterを呼び出してファイルプレゼンタの登録を解除する。

## ソース
ちょっと動作させてみてわからなかったところもあるのですが、サンプルを書いてみました。
デスクトップ上のファイルの監視をしてみます。

*追記: 長らく手付かずでしたがswift4で書き直していだだきました。どうもありがとうございます！！*

```swift:ViewController.swift

import Cocoa

class ViewController: NSViewController, NSFilePresenter {
  
  var isPresenting = false
  var presentedItemURL: URL? {
    return FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Desktop", isDirectory: true)
  }
  
  let presentedItemOperationQueue = OperationQueue()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    addFilePresenterIfNeeded()
    
    // Do any additional setup after loading the view.
  }
  
  deinit {
    removeFilePresenterIfNeeded()
  }
  
  // ファイルプレゼンタをシステムに登録
  func addFilePresenterIfNeeded() {
    if !isPresenting {
      isPresenting = true
      NSFileCoordinator.addFilePresenter(self)
    }
  }
  
  // ファイルプレゼンタをシステムの登録から解除
  func removeFilePresenterIfNeeded() {
    if isPresenting {
      isPresenting = false
      NSFileCoordinator.removeFilePresenter(self)
    }
  }
  
  // 提示された項目の内容または属性が変更されたことを伝える。
  func presentedItemDidChange() {
    print("Change item.")
  }
  
  // ファイルまたはファイルパッケージの新しいバージョンが追加されたことをデリゲートに通知する
  func presentedItemDidGainVersion(version: NSFileVersion) {
    print("Update file at \(version.modificationDate!).")
  }
  
  // ファイルまたはファイルパッケージのバージョンが消えたことをデリゲートに通知する
  func presentedItemDidLoseVersion(version: NSFileVersion) {
    print("Lose file version at \(version.modificationDate!).")
  }
  
  // ディレクトリ内のアイテムが新しいバージョンになった（更新された）時の通知
  func presentedSubitem(at url: URL, didGain version: NSFileVersion) {
    
    var isDir = ObjCBool(false)
    if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir) {
      if Bool(isDir.boolValue) {
        print("Update directory (\(url.path)) at \(version.modificationDate!).")
      } else {
        print("Update file (\(url.path)) at \(version.modificationDate!).")
      }
    }
  }
  
  // ディレクトリ内のアイテムが削除された時の通知
  func presentedSubitem(at url: URL, didLose version: NSFileVersion) {
    print("looooooooooooose")
    var isDir = ObjCBool(false)
    if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir) {
      if Bool(isDir.boolValue) {
        print("Lose directory version (\(url.path)) at \(version.modificationDate!).")
      } else {
        print("Lose file version (\(url.path)) at \(version.modificationDate!).")
      }
    }
  }
  
  // ファイル/ディレクトリの内容変更の通知
  func presentedSubitemDidChange(at url: URL) {
    if FileManager.default.fileExists(atPath: url.path) {
      print("Add subitem (\(url.path)).")
    } else {
      print("Remove subitem (\(url.path)).")
    }
    
  }
  
  // ファイル/ディレクトリが移動した時の通知
  func presentedSubitemAtURL(oldURL: NSURL, didMoveToURL newURL: NSURL) {
    var isDir = ObjCBool(false)
    if FileManager.default.fileExists(atPath: newURL.path!, isDirectory: &isDir) {
      if Bool(isDir.boolValue) {
        print("Move directory from (\(oldURL.path!)) to (\(newURL.path!).")
      } else {
        print("Move file from (\(oldURL.path!)) to (\(newURL.path!)).")
      }
    }
  }
  
  // MARK: 何したら呼ばれるのか
  
  // 何したら呼ばれるのか
  func accommodatePresentedItemDeletionWithCompletionHandler(completionHandler: (NSError?) -> Void) {
    print("accommodatePresentedItemDeletionWithCompletionHandler")
  }
  
  // 何したら呼ばれるのか
  private func accommodatePresentedSubitemDeletionAtURL(url: URL, completionHandler: @escaping (NSError?) -> Void) {
    print("accommodatePresentedSubitemDeletionAtURL")
    print("url: \(url.path)")
  }
  
  // 何したら呼ばれるのか
  func presentedSubitemDidAppear(at url: URL) {
    print("presentedSubitemDidAppearAtURL")
    print("url: \(url.path)")
  }
}
```
# 参考
* [ファイルシステムプログラミングガイド](https://developer.apple.com/jp/documentation/FileSystemProgrammingGuide.pdf)
* [NSFilePresenter Protocol Reference](https://developer.apple.com/library/mac/documentation/Foundation/Reference/NSFilePresenter_protocol/)
* [NSFilePresenterクラス - Cocoa API解説(iOS/OS X)](http://cocoaapi.hatenablog.com/entry/10010918/Foundation_NSFilePresenter)


```