Cocoaでディレクトリ監視

ディレクトリ監視、つまりファイルのリネーム、移動、削除、作成等々の、
監視を行いたいケースはあると思います。

しかしどうにも使いやすいNSなんちゃらのクラスは見当たりません。
しかし、CベースのAPIならばあるようです。
CoreServicesフレームワークを今回は利用します。

まずはCoreServices.frameworkをライブラリに追加します。
そしてヘッダーはこちらです。

```objc
#import <CoreServices/CoreServices.h>
```

で、メインプログラムなのですが、
１、FSEventStreamCreate
２、FSEventStreamScheduleWithRunLoop
３、FSEventStreamStart
の３ステップで可能です。

こちらのAPIを使うのですが、このままだとObjective-cに組み込みにくいため、
ラッパーを作成します。

以下はデリゲートパターンでの実装です。
こういうのを実装する時にはblocksでもいいのですが、
長いスパンで動作する通知系においては、
なんだかんだで循環参照やら気を配らなくていいデリゲートパターンが一番良い気がします。
iOSでは試していません。
ぶっちゃけiOSでディレクトリ監視なんてほとんど使わないとおもいますが・・・。

最低限の機能しかないので必要に応じてカスタマイズすることを想定しています。

ヘッダー(DirectoryObserver.h)

```objc
#import <Foundation/Foundation.h>

@protocol DirectoryObserverDelegate <NSObject>
- (void)onDirectoryChanged:(id)sender directories:(NSArray *)directories;
@end

@interface DirectoryObserver : NSObject
- (id)initWithDirectoryPath:(NSString *)path;

- (void)startObserving;
- (void)stopObserving;

@property (weak, nonatomic) id<DirectoryObserverDelegate> delegate;
@end

```

実装(DirectoryObserver.m)

```objc
#import "DirectoryObserver.h"

#import <CoreServices/CoreServices.h>

@interface DirectoryObserver()
- (void)onDirectoryChanged:(NSArray *)directories;
@end

void fs_event_callback(ConstFSEventStreamRef streamRef,
                       void *clientCallBackInfo,
                       size_t numEvents,
                       void *eventPaths,
                       const FSEventStreamEventFlags eventFlags[],
                       const FSEventStreamEventId eventIds[])
{
    char **paths = eventPaths;
    NSMutableArray *directories = [NSMutableArray array];
    for (int i = 0 ; i < numEvents ; i++) {
        char *path = paths[i];
        [directories addObject:[NSString stringWithUTF8String:path]];
    }

    DirectoryObserver *observer = (__bridge DirectoryObserver *)clientCallBackInfo;
    [observer onDirectoryChanged:directories];
}

@implementation DirectoryObserver
{
    NSString *_path;
    FSEventStreamRef _stream;
}
- (id)initWithDirectoryPath:(NSString *)path
{
    if(self = [super init])
    {
        _path = path;
    }
    return self;
}
- (void)startObserving
{
    if(_stream)
        return;
    
    FSEventStreamContext context = {0};
    context.info = (__bridge void *)self;
    CFTimeInterval latency = 0.1; /*監視間隔*/
    _stream = FSEventStreamCreate(kCFAllocatorDefault,
                                  fs_event_callback, /*コールバック*/
                                  &context, /*ユーザーデータのため*/
                                  (__bridge CFArrayRef)@[_path], /*監視するパス*/
                                  kFSEventStreamEventIdSinceNow, /*よくわかりません*/
                                  latency,
                                  kFSEventStreamCreateFlagNone); /*ひとまずNoneで*/
    
    FSEventStreamScheduleWithRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    FSEventStreamStart(_stream);
}
- (void)stopObserving
{
    if(_stream == NULL)
        return;
    
    FSEventStreamStop(_stream);
    FSEventStreamUnscheduleFromRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    FSEventStreamRelease(_stream);
    _stream = NULL;
}
- (void)onDirectoryChanged:(NSArray *)directories
{
    if(self.delegate)
       [self.delegate onDirectoryChanged:self directories:directories];
}
@end
```