Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save petejkim/5907363 to your computer and use it in GitHub Desktop.

Select an option

Save petejkim/5907363 to your computer and use it in GitHub Desktop.

Revisions

  1. nwaite created this gist Jan 6, 2010.
    41 changes: 41 additions & 0 deletions NSObject+ProcObservation.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    //
    // 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
    156 changes: 156 additions & 0 deletions NSObject+ProcObservation.m
    Original file line number Diff line number Diff line change
    @@ -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
    18 changes: 18 additions & 0 deletions NSObject+ProcObservation.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    # 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