Skip to content

Instantly share code, notes, and snippets.

@nolanw
Created January 6, 2010 08:32
Show Gist options
  • Save nolanw/270137 to your computer and use it in GitHub Desktop.
Save nolanw/270137 to your computer and use it in GitHub Desktop.
Slight alteration of Any Matuschak's BlockObservation for MacRuby.
//
// NSObject+ProcObservation.h
// Version 1.0
//
// Andy Matuschak
// [email protected]
// Public domain because I love you. Let me know how you use it.
//
// NTW 2009-Oct-21: Added selectors with an options argument.
// NTW 2009-Oct-30: Transplanted new observation key from MYUtilities's KVUtils.
// NTW 2010-Jan-06: Changed to accept Ruby proc instead of C block. Got rid of block token typedef so MacRuby isn't confused. See NSObject_ProcObservation.rb for handy Ruby interface.
#import <Cocoa/Cocoa.h>
// Or MYKeyValueObservingOptionOnce with your observation options to cause the
// observer to remove itself upon the first change notification.
// Idea and line of code from MYUtilities.
enum {
MYKeyValueObservingOptionOnce = 1<<31
};
@interface NSObject (AMRubyBlockObservation)
- (NSString *)addObserverForKeyPath:(NSString *)keyPath proc:(id)proc;
- (NSString*)addObserverForKeyPath:(NSString*)keyPath
options:(NSKeyValueObservingOptions)options
proc:(id)proc;
- (NSString*)addObserverForKeyPath:(NSString*)keyPath
onQueue:(NSOperationQueue*)queue
proc:(id)proc;
- (NSString*)addObserverForKeyPath:(NSString*)keyPath
options:(NSKeyValueObservingOptions)options
onQueue:(NSOperationQueue*)queue
proc:(id)proc;
- (void)removeObserverWithProcToken:(NSString *)token;
@end
//
// 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
# NSObject+ProcObservation.rb
# Public Domain
#
# Created by Nolan Waite on 10-01-06.
# Copyright 2010 Nolan Waite. All rights reserved.
# Replicated from NSObject+ProcObservation.h
MYKeyValueObservingOptionsOnce = 1<<31
class NSObject
def observe(keyPath, options=0, &block)
self.addObserverForKeyPath(keyPath, options:options, proc:block)
end
def stopObserving(token)
self.removeObserverWithProcToken token
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment