Posts match “ objective-c ” tag:

Different Ways to Monitor File Changes

很多應用程式都會需要用到 file monitoring,最常遇到的情況就是偵測 config file 有沒有更動,如果有,就 reload config file 來載入新的設定,這樣的作法可以很方便地修改值,不論是在測試的時候設定 debug information,或是讓使用者開啟一些暗黑功能。常見的方式有:kqueue, fsevent, and GCD (Grand Central Dispatch)。

kqueue

可以參考這篇

fsevent

CDEvents 提供了不錯的 API,但 fsevent 我傾向是用在偵測大量檔案變化的時候使用。asepsis 就有用到 fsevent C API 來實作偵測系統內所有 .DS_Store 檔案的動作 (雖然它是用 C 實作 XD)。

GCD

GCD 的方式是參考這篇修改而來,已經放到 Omazing 裡了。

OMZFileMonitor.h
#import <Foundation/Foundation.h>

@interface OMZFileMonitor : NSObject

+ (instancetype)fileMonitorWithPath:(NSString *)path withUpdateBlock:(void (^)(NSString *path))block;

- (id)initWithPath:(NSString *)path withUpdateBlock:(void (^)(NSString *path))block;

- (void)start;
- (void)stop;

@end
OMZFileMonitor.m
#import "OMZFileMonitor.h"

@interface OMZFileMonitor () {
    dispatch_source_t _source;
}

@property (nonatomic, copy) void (^block)(NSString *path);

- (void)_monitorPath:(NSString *)path withUpdateBlock:(void (^)(NSString *path))block;

@end

@implementation OMZFileMonitor

+ (instancetype)fileMonitorWithPath:(NSString *)path withUpdateBlock:(void (^)(NSString *path))block
{
    return [[self alloc] initWithPath:path withUpdateBlock:block];
}

- (id)initWithPath:(NSString *)path withUpdateBlock:(void (^)(NSString *))block
{
    if (path.length == 0 || block == NULL) return nil;

    if (!(self = [super init])) return nil;

    [self _monitorPath:path withUpdateBlock:block];

    return self;
}

- (void)dealloc
{
    if (_source) {
#if !OS_OBJECT_USE_OBJC
        dispatch_release(_source);
#endif
        _source = NULL;
    }
}

- (void)start
{
    if (_source) dispatch_resume(_source);
}

- (void)stop
{
    if (_source) dispatch_suspend(_source);
}

- (void)_monitorPath:(NSString *)path withUpdateBlock:(void (^)(NSString *path))block
{
    NSParameterAssert(path.length > 0);
    NSParameterAssert(block != NULL);

    self.block = block;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    int fd = open([path UTF8String], O_EVTONLY);

#if !OS_OBJECT_USE_OBJC
    if (_source) dispatch_release(_source);
#endif
    _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
                                     fd,
                                     DISPATCH_VNODE_DELETE|DISPATCH_VNODE_WRITE|DISPATCH_VNODE_EXTEND|
                                     DISPATCH_VNODE_ATTRIB|DISPATCH_VNODE_LINK |DISPATCH_VNODE_RENAME|
                                     DISPATCH_VNODE_REVOKE,
                                     queue);

    __weak __typeof__(self) weakSelf = self;
    dispatch_source_set_event_handler(_source, ^{
        __strong __typeof__(self) self = weakSelf;

        unsigned long flags = dispatch_source_get_data(_source);
        if (flags & DISPATCH_VNODE_DELETE) {
            dispatch_source_cancel(_source);
            [self _monitorPath:path withUpdateBlock:self.block];
        }

        if (self.block) self.block(path);
    });

    dispatch_source_set_cancel_handler(_source, ^{
        close(fd);
    });
}

@end