rastersize/CDEvents — Issue #9: Suggestion: Refactoring just a little
Author: ArvinB
Created: 2012-01-15 23:51:54+00:00
State: open

Hi Aron,

I actually wrote a wrapper of my own for kQueue and FSEvents. I dropped kQueue to avoid issues with my sandboxed app, but took the FSEvent code and refactored it. I was inspired by yours and SCEvent in how you handle your event class. So I borrowed a bit from it. Anyways, I am using a simple Category on NSObject that helps make things a little bit easier.

@implementation NSObject (CategoryNSObject)

#pragma mark Category Methods:

- (void) associateValue:(id)value withKey:(void *)aKey {
objc_setAssociatedObject(self, aKey, value, OBJC_ASSOCIATION_RETAIN);

}
- (id) associatedValueForKey:(void *)aKey {
return objc_getAssociatedObject(self, aKey);
}

------

Anyways, to share just a bit here is my code where maybe it can give you any ideas for future work or refactoring CDEvents:

HEADER:

#pragma mark - Watcher File System Event:

@interface WatcherFSEvent : NSObject {

@private
WatcherFSEventID _identifier;
WatcherFSFlags _flags;
NSURL * _url;
}

@property (readonly, nonatomic) WatcherFSEventID identifier;
@property (readonly, nonatomic) WatcherFSFlags flags;
@property (readonly, strong, nonatomic) NSURL * url;

+ (id) eventWithID:(WatcherFSEventID)anID
flags:(WatcherFSFlags)fsFlags
url:(NSURL *)aURL;

- (id) initWithID:(WatcherFSEventID)anID
flags:(WatcherFSFlags)fsFlags
url:(NSURL *)aURL;

- (NSString *) description;

@property (readonly, nonatomic) BOOL isGenericChange;
@property (readonly, nonatomic) BOOL mustRescanSubDirectories;
@property (readonly, nonatomic) BOOL isUserDropped;
@property (readonly, nonatomic) BOOL isKernelDropped;
@property (readonly, nonatomic) BOOL isEventIdsWrapped;
@property (readonly, nonatomic) BOOL isHistoryDone;
@property (readonly, nonatomic) BOOL isRootChanged;
@property (readonly, nonatomic) BOOL didVolumeMount;
@property (readonly, nonatomic) BOOL didVolumeUnmount;

@end

#pragma mark - Watcher File System Operation:

@interface WatcherFSOperation : NSOperation {

@private
WatcherFSRef _streamRef;
WatcherFSEventID _sinceIdentifier;
NSArray * _watchedURLs;
}

@property (readwrite, nonatomic) WatcherFSRef streamRef;
@property (readwrite, nonatomic) WatcherFSEventID sinceIdentifier;
@property (readwrite, strong, nonatomic) NSArray * watchedURLs;

- (id) initWithIdentifier:(WatcherFSEventID)eventID;

@end

#pragma mark - Watcher File System:

/*!
* @discussion Usage:
* Call startWatching: to begin watching
* a file system level URL (file://)
*
* Call stopWatching: to stop the file stream
*
* Call resumeWatching: to resume watching
* from a prior session (e.g. after app restarts)
*
* @example [[NSApp watcherFS] startWatching:arrayOfURLs
* block:^(WatcherFSEvent *event){
* << Do stuff here when event fires >>
* << Use WatcherFSEvent for more info >>
* }];
*/

typedef void (^WatcherFSBlock)(WatcherFSEvent *event);

@interface WatcherFileSystem : NSObject {

@private
NSOperationQueue * _fileSystemQueue;
}

@property (readonly, strong, nonatomic) NSOperationQueue * fileSystemQueue;

- (WatcherOPUUID *) startWatching:(NSArray *)urlArray
block:(WatcherFSBlock)block;

- (WatcherOPUUID *) resumeWatching:(NSArray *)urlArray
withEventID:(WatcherFSEventID)eventID
block:(WatcherFSBlock)block;

- (void) stopWatching:(WatcherOPUUID *)opUUID;
- (void) close;

@end

IMPLEMENTATION:

#import "Watcher.h"

#pragma mark Implemination (File System):

@implementation WatcherFileSystem

@synthesize fileSystemQueue = _fileSystemQueue;

#pragma mark Initialization:

- (id) init {

if ( (self = [super init]) ) {

_fileSystemQueue = [[NSOperationQueue alloc] init];

REPORT_ARGS( errReportArgInfoLoaded, @"Watcher (File System)" );

} return self;
}

- (void) close { [self.fileSystemQueue cancelAllOperations]; }

#pragma mark Watching Methods:

- (WatcherOPUUID *) startWatching:(NSArray *)urlArray
block:(WatcherFSBlock)block {

WatcherOPUUID * opUUID = [NSProcessInfo shortUUID];
WatcherFSOperation * operation = [[WatcherFSOperation alloc] init];
[operation associateValue:opUUID withKey:kWatcherFileSystemOPUUID];
[operation associateValue:[block copy] withKey:kWatcherFileSystemOPBlock];

operation.watchedURLs = urlArray;
[self.fileSystemQueue addOperation:operation];

return opUUID;
}

- (WatcherOPUUID *) resumeWatching:(NSArray *)urlArray
withEventID:(WatcherFSEventID)eventID
block:(WatcherFSBlock)block {

WatcherOPUUID * opUUID = [NSProcessInfo shortUUID];
WatcherFSOperation * operation = [[WatcherFSOperation alloc] initWithIdentifier:eventID];
[operation associateValue:opUUID withKey:kWatcherFileSystemOPUUID];
[operation associateValue:[block copy] withKey:kWatcherFileSystemOPBlock];

operation.watchedURLs = urlArray;
[self.fileSystemQueue addOperation:operation];

return opUUID;
}

- (void) stopWatching:(WatcherOPUUID *)opUUID {

NSArray * operations = [self.fileSystemQueue operations];

if ( [operations count] > 0 ) {
for ( NSOperation * op in operations ) {
WatcherOPUUID * aUUID = [op associatedValueForKey:kWatcherFileSystemOPUUID];
if ( [opUUID isEqualToString:aUUID] ) {
[op cancel]; break;
} } }
}

@end

#pragma mark - FS Operation (Callback):

@interface WatcherFSOperation ()
static void FSEventsCallback(
ConstFSEventStreamRef streamRef,
void *callbackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]);

- (void) disposeStream;
- (void) handleEvent:(WatcherFSEvent *)event;

@end

#pragma mark Implemination (FS Operation):

@implementation WatcherFSOperation

@synthesize streamRef = _streamRef,
sinceIdentifier = _sinceIdentifier,
watchedURLs = _watchedURLs;

#pragma mark Initialization:

- (id) init {
if ( (self = [super init]) ) {
self.sinceIdentifier = kFSEventStreamEventIdSinceNow;
} return self;
}

- (id) initWithIdentifier:(WatcherFSEventID)eventID {
if ( (self = [super init]) ) {
self.sinceIdentifier = eventID;
} return self;
}

#pragma mark Main Run Loop:

- (void) main {

@try {

if ( [self.watchedURLs count] == 0 ) {
[self cancel]; return;
}

NSMutableArray * fsArray = [NSMutableArray array];
for ( NSURL * aURL in self.watchedURLs )
[fsArray addObject:[aURL path]];

FSEventStreamContext callbackCTX = { 0, (void *)self, nil, nil, nil };

self.streamRef = FSEventStreamCreate( kCFAllocatorDefault,
&FSEventsCallback,
&callbackCTX,
(CFArrayRef)fsArray,
self.sinceIdentifier,
(CFTimeInterval)kWatcherEventLatency,
kWatcherFSCreateFlags );

FSEventStreamScheduleWithRunLoop( self.streamRef,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode );
fsArray = nil;

if ( !FSEventStreamStart( self.streamRef ) ) {

REPORT( errReportErrorWatcherFSStart );
[self disposeStream];
[self cancel]; return;
}

while ( !self.isCancelled )
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[[NSDate date] dateByAddingTimeInterval:0.5]];

[self disposeStream];

} @catch ( NSException *exception ) {
REPORT_ARGS( errReportArgErrorWatcherFSException, [exception description] );
}
}

#pragma mark Private Methods:

- (void) disposeStream {

if ( !self.streamRef ) return;

FSEventStreamStop ( self.streamRef );
FSEventStreamInvalidate( self.streamRef );
FSEventStreamRelease ( self.streamRef );
self.streamRef = nil;
}

- (void) handleEvent:(WatcherFSEvent *)event {

WatcherFSBlock block = [self associatedValueForKey:kWatcherFileSystemOPBlock];
block(event);
}

#pragma mark FS Callback:

static void FSEventsCallback(
ConstFSEventStreamRef streamRef,
void *callbackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]) {

WatcherFSOperation * watcher = (WatcherFSOperation *)callbackInfo;
NSArray * paths = (NSArray *)eventPaths;

for ( NSUInteger i = 0; i < numEvents; i++) {

NSString * path = [paths objectAtIndex:i];
path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSURL * eventURL = [NSURL URLWithString:path];

WatcherFSEvent * event = [WatcherFSEvent eventWithID:eventIds[i]
flags:eventFlags[i]
url:eventURL];
[watcher handleEvent:event];
}
}

@end

#pragma mark - Implemination (FS Event):

@implementation WatcherFSEvent

@synthesize identifier = _identifier,
flags = _flags,
url = _url;

#pragma mark Initialization:

+ (WatcherFSEvent *) eventWithID:(WatcherFSEventID)anID
flags:(WatcherFSFlags)fsFlags
url:(NSURL *)aURL {

return [[WatcherFSEvent alloc] initWithID:anID
flags:fsFlags
url:aURL];
}

- (id) initWithID:(WatcherFSEventID)anID
flags:(WatcherFSFlags)fsFlags
url:(NSURL *)aURL {

if ( (self = [super init]) ) {

_identifier = anID;
_flags = fsFlags;
_url = aURL;

} return self;
}

#pragma mark Getter Methods:

- (NSString *) description {

return [NSString stringWithFormat: @"id = %ld, URL = %@, flags = %ld",
(unsigned long)self.identifier,
[self.url path],
(unsigned long)self.flags];
}

- (BOOL) isGenericChange { return ( kFSEventStreamEventFlagNone == _flags ); }
- (BOOL) mustRescanSubDirectories { return ( ((self.flags) & (kFSEventStreamEventFlagMustScanSubDirs)) ? YES : NO ); }
- (BOOL) isUserDropped { return ( ((self.flags) & (kFSEventStreamEventFlagUserDropped)) ? YES : NO ); }
- (BOOL) isKernelDropped { return ( ((self.flags) & (kFSEventStreamEventFlagKernelDropped)) ? YES : NO ); }
- (BOOL) isEventIdsWrapped { return ( ((self.flags) & (kFSEventStreamEventFlagEventIdsWrapped)) ? YES : NO ); }
- (BOOL) isHistoryDone { return ( ((self.flags) & (kFSEventStreamEventFlagHistoryDone)) ? YES : NO ); }
- (BOOL) isRootChanged { return ( ((self.flags) & (kFSEventStreamEventFlagRootChanged)) ? YES : NO ); }
- (BOOL) didVolumeMount { return ( ((self.flags) & (kFSEventStreamEventFlagMount)) ? YES : NO ); }
- (BOOL) didVolumeUnmount { return ( ((self.flags) & (kFSEventStreamEventFlagUnmount)) ? YES : NO ); }

@end

DEFINITIONS:

#import

#pragma mark Watcher File System Definitions:

typedef NSString WatcherOPUUID;
typedef FSEventStreamRef WatcherFSRef;
typedef FSEventStreamEventId WatcherFSEventID;
typedef FSEventStreamEventFlags WatcherFSFlags;
typedef FSEventStreamCreateFlags WatcherFSCreate;

static NSString * const kWatcherFileSystemOPUUID = @"WatcherFSOPUUID";
static NSString * const kWatcherFileSystemOPBlock = @"WatcherFSOPBlock";

static NSTimeInterval const kWatcherEventLatency = 3.0;
static WatcherFSCreate const kWatcherFSCreateFlags = ( kFSEventStreamCreateFlagUseCFTypes |
kFSEventStreamCreateFlagWatchRoot );

Note: Though I compile with 10.7 SDK my target is 10.6 or greater. That is why several of the other event flags were not implemented. It also keeps things easier and events to fire less frequently.

One last thing, I have an instance of this class in the singleton of the app, but my macro for a singleton is like so:

/*!
* @function Singleton GCD Macro
*/
#ifndef SINGLETON_GCD
#define SINGLETON_GCD(classname)	\
\
+ (classname *)shared##classname {	\
\
static dispatch_once_t pred;	\
static classname * shared##classname = nil;	\
dispatch_once( &pred, ^{	\
shared##classname = [[self alloc] init];	\
});	\
return shared##classname;	\
}
#endif

Just declare the function in the head and call this macro in the implementation: SINGLETON_GCD(mySingletonClass)

Hope this helps you out, since you helped me out with your code.