|
|
@@ -0,0 +1,156 @@ |
|
|
// |
|
|
// NSObject+ProcObservation.m |
|
|
// Version 1.0 |
|
|
// |
|
|
// Andy Matuschak |
|
|
// [email protected] |
|
|
// Public domain because I love you. Let me know how you use it. |
|
|
// |
|
|
|
|
|
#import "NSObject+ProcObservation.h" |
|
|
#import <dispatch/dispatch.h> |
|
|
#import <objc/runtime.h> |
|
|
#import <macruby/macruby.h> |
|
|
|
|
|
@interface AMRubyObserverTrampoline : NSObject |
|
|
{ |
|
|
__weak id observee; |
|
|
NSString *keyPath; |
|
|
id proc; |
|
|
NSOperationQueue *queue; |
|
|
NSKeyValueObservingOptions options; |
|
|
} |
|
|
|
|
|
- (AMRubyObserverTrampoline*)initObservingObject:(id)obj |
|
|
keyPath:(NSString*)newKeyPath |
|
|
options:(NSKeyValueObservingOptions)newOptions |
|
|
onQueue:(NSOperationQueue*)newQueue |
|
|
proc:(id)newProc; |
|
|
|
|
|
- (void)cancelObservation; |
|
|
|
|
|
@end |
|
|
|
|
|
|
|
|
@implementation AMRubyObserverTrampoline |
|
|
|
|
|
static NSString *AMRubyObserverTrampolineContext = @"AMRubyObserverTrampolineContext"; |
|
|
|
|
|
- (AMRubyObserverTrampoline*)initObservingObject:(id)obj |
|
|
keyPath:(NSString*)newKeyPath |
|
|
options:(NSKeyValueObservingOptions)newOptions |
|
|
onQueue:(NSOperationQueue*)newQueue |
|
|
proc:(id)newProc |
|
|
{ |
|
|
self = [super init]; |
|
|
if (self != nil) |
|
|
{ |
|
|
proc = newProc; |
|
|
keyPath = [newKeyPath copy]; |
|
|
queue = [newQueue retain]; |
|
|
observee = obj; |
|
|
options = newOptions; |
|
|
|
|
|
// Clear out our customized options before passing them on. |
|
|
// From MYUtilities. |
|
|
newOptions &= ~MYKeyValueObservingOptionOnce; |
|
|
|
|
|
[observee addObserver:self |
|
|
forKeyPath:keyPath |
|
|
options:newOptions |
|
|
context:AMRubyObserverTrampolineContext]; |
|
|
} |
|
|
return self; |
|
|
} |
|
|
|
|
|
- (void)observeValueForKeyPath:(NSString*)aKeyPath |
|
|
ofObject:(id)object |
|
|
change:(NSDictionary*)change |
|
|
context:(void*)context |
|
|
{ |
|
|
if (context != AMRubyObserverTrampolineContext) |
|
|
{ |
|
|
[super observeValueForKeyPath:aKeyPath ofObject:object change:change context:context]; |
|
|
return; |
|
|
} |
|
|
|
|
|
// Not sure if we really need this on the next run loop iteration. Seemed right. |
|
|
if (options & MYKeyValueObservingOptionOnce) |
|
|
[self performSelector:@selector(cancelObservation) withObject:nil afterDelay:0.0]; |
|
|
|
|
|
if (queue) |
|
|
[queue addOperationWithBlock:^{ [proc performRubySelector:@selector(call:) withArguments:object, change]; }]; |
|
|
else |
|
|
[proc performRubySelector:@selector(call:) withArguments:object, change]; |
|
|
} |
|
|
|
|
|
- (void)cancelObservation |
|
|
{ |
|
|
[observee removeObserver:self forKeyPath:keyPath]; |
|
|
} |
|
|
|
|
|
- (void)dealloc |
|
|
{ |
|
|
[self cancelObservation]; |
|
|
[proc release]; |
|
|
[keyPath release]; |
|
|
[queue release]; |
|
|
[super dealloc]; |
|
|
} |
|
|
|
|
|
@end |
|
|
|
|
|
|
|
|
static NSString *AMRubyObserverMapKey = @"org.andymatuschak.observerMapRuby"; |
|
|
|
|
|
|
|
|
@implementation NSObject (AMRubyBlockObservation) |
|
|
|
|
|
- (NSString *)addObserverForKeyPath:(NSString *)keyPath proc:(id)proc |
|
|
{ |
|
|
return [self addObserverForKeyPath:keyPath options:0 onQueue:nil proc:proc]; |
|
|
} |
|
|
|
|
|
- (NSString*)addObserverForKeyPath:(NSString*)keyPath |
|
|
options:(NSKeyValueObservingOptions)options |
|
|
proc:(id)proc |
|
|
{ |
|
|
return [self addObserverForKeyPath:keyPath options:options onQueue:nil proc:proc]; |
|
|
} |
|
|
|
|
|
- (NSString*)addObserverForKeyPath:(NSString*)keyPath |
|
|
onQueue:(NSOperationQueue*)queue |
|
|
proc:(id)proc |
|
|
{ |
|
|
return [self addObserverForKeyPath:keyPath options:0 onQueue:queue proc:proc]; |
|
|
} |
|
|
|
|
|
- (NSString*)addObserverForKeyPath:(NSString*)keyPath |
|
|
options:(NSKeyValueObservingOptions)options |
|
|
onQueue:(NSOperationQueue*)queue |
|
|
proc:(id)proc |
|
|
{ |
|
|
NSString *token = [[NSProcessInfo processInfo] globallyUniqueString]; |
|
|
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
|
|
if (!objc_getAssociatedObject(self, AMRubyObserverMapKey)) |
|
|
objc_setAssociatedObject(self, AMRubyObserverMapKey, [NSMutableDictionary dictionary], OBJC_ASSOCIATION_RETAIN); |
|
|
AMRubyObserverTrampoline *trampoline = [[[AMRubyObserverTrampoline alloc] initObservingObject:self keyPath:keyPath options:options onQueue:queue proc:proc] autorelease]; |
|
|
[objc_getAssociatedObject(self, AMRubyObserverMapKey) setObject:trampoline forKey:token]; |
|
|
}); |
|
|
return token; |
|
|
} |
|
|
|
|
|
- (void)removeObserverWithProcToken:(NSString *)token |
|
|
{ |
|
|
NSMutableDictionary *observationDictionary = objc_getAssociatedObject(self, AMRubyObserverMapKey); |
|
|
AMRubyObserverTrampoline *trampoline = [observationDictionary objectForKey:token]; |
|
|
if (!trampoline) |
|
|
{ |
|
|
NSLog(@"Tried to remove non-existent observer on %@ for token %@", self, token); |
|
|
return; |
|
|
} |
|
|
[trampoline cancelObservation]; |
|
|
[observationDictionary removeObjectForKey:token]; |
|
|
} |
|
|
|
|
|
@end |