Skip to content

Instantly share code, notes, and snippets.

@nevyn
Created June 16, 2011 16:58
Show Gist options
  • Save nevyn/1029683 to your computer and use it in GitHub Desktop.
Save nevyn/1029683 to your computer and use it in GitHub Desktop.
Oneliner KVO.
typedef void(^SPDependsCallback)();
/**
* Add a dependency from an object to another object.
* Registers that your object depends on the given objects and their key paths,
* and invokes the callback when the values of any of the given key paths
* changes.
*
* If an owner and association name is given, the dependency object is
* associated with the owner under the given name, and automatically deallocated
* if another dependency with the same name is given, or if the owner object
* dies.
*
* If the automatic association described above is not used, you must retain
* the returned dependency object until the dependency becomes invalid.
*
* @param callback Called when the association changes. Always called once immediately
* after registration.
* @example
* __block __typeof(self) selff; // weak reference
* NSArray *dependencies = [NSArray arrayWithObjects:foo, @"bar", @"baz", a, @"b", nil]
* SPAddDependency(self, @"modifyThing", dependencies, ^ {
* selff.thing = foo.bar*3 + foo.baz - a.b;
* });
*/
id SPAddDependency(id owner, NSString *associationName, NSArray *dependenciesAndNames, SPDependsCallback callback);
/**
* Like SPAddDependency, but can be called varg style without an explicit array object.
* End with the callback and then nil.
*/
id SPAddDependencyV(id owner, NSString *associationName, ...);
/**
* Shortcut for SPAddDependencyV
*/
#define $depends(associationName, object, keypath, ...) do {\
__block __typeof(self) selff = self; /* Weak reference*/ \
SPAddDependencyV(self, associationName, object, keypath, __VA_ARGS__, nil);\
} while(0)
#import "SPDepends.h"
#import "SPKVONotificationCenter.h"
@interface SPDependency : NSObject
@property(copy) SPDependsCallback callback;
@property(assign) id owner;
@property(retain) NSMutableArray *subscriptions;
@end
@implementation SPDependency
@synthesize callback = _callback, owner = _owner;
@synthesize subscriptions = _subscriptions;
-initWithDependencies:(NSArray*)pairs callback:(SPDependsCallback)callback owner:(id)owner;
{
self.callback = callback;
self.owner = owner;
self.subscriptions = [NSMutableArray array];
SPKVONotificationCenter *nc = [SPKVONotificationCenter defaultCenter];
NSEnumerator *en = [pairs objectEnumerator];
id object = [en nextObject];
id next = [en nextObject];
for(;;) {
SPKVObservation *subscription = [nc addObserver:self toObject:object forKeyPath:next options:0 selector:@selector(somethingChanged)];
[_subscriptions addObject:subscription];
next = [en nextObject];
if(!next) break;
if(![next isKindOfClass:[NSString class]]) {
object = next;
next = [en nextObject];
}
}
self.callback();
return self;
}
-(void)dealloc;
{
self.subscriptions = nil;
self.owner = nil;
self.callback = nil;
[super dealloc];
}
-(void)somethingChanged;
{
self.callback();
}
@end
static void *dependenciesKey = &dependenciesKey;
id SPAddDependency(id owner, NSString *associationName, NSArray *dependenciesAndNames, SPDependsCallback callback)
{
id dep = [[[SPDependency alloc] initWithDependencies:dependenciesAndNames callback:callback owner:owner] autorelease];
if(owner && associationName) {
NSMutableDictionary *dependencies = objc_getAssociatedObject(owner, dependenciesKey);
if(!dependencies) dependencies = [NSMutableDictionary dictionary];
[dependencies setObject:dep forKey:associationName];
objc_setAssociatedObject(owner, dependenciesKey, dependencies, OBJC_ASSOCIATION_RETAIN);
}
return dep;
}
id SPAddDependencyV(id owner, NSString *associationName, ...)
{
NSMutableArray *dependenciesAndNames = [NSMutableArray array];
va_list va;
va_start(va, associationName);
id object = va_arg(va, id);
id peek = va_arg(va, id);
do {
[dependenciesAndNames addObject:object];
object = peek;
peek = va_arg(va, id);
} while(peek != nil);
return SPAddDependency(owner, associationName, dependenciesAndNames, object);
}
#import <Foundation/Foundation.h>
@interface SPKVObservation : NSObject
-(id)unregister;
@end
@interface SPKVONotificationCenter : NSObject
+(id)defaultCenter;
-(SPKVObservation*)addObserver:(id)observer toObject:(id)observed forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options;
// selector should have the following signature:
// - (void)observeChange:(NSDictionary*)change onObject:(id)target forKeyPath:(NSString *)keyPath
-(SPKVObservation*)addObserver:(id)observer toObject:(id)observed forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options selector:(SEL)sel;
@end
#import "SPKVONotificationCenter.h"
#import <libkern/OSAtomic.h>
#import <objc/message.h>
// Inspired by http://www.mikeash.com/svn/MAKVONotificationCenter/MAKVONotificationCenter.m
static NSString *SPKVOContext = @"SPKVObservation";
typedef void (*SPKVOCallback)(id, SEL, NSDictionary*, id, NSString *);
@interface SPKVObservation ()
@property(nonatomic, assign) id observer;
@property(nonatomic, assign) id observed;
@property(nonatomic, copy) NSString *keyPath;
@property(nonatomic) SEL selector;
@end
@implementation SPKVObservation
@synthesize observer = _observer, observed = _observed, selector = _sel, keyPath = _keyPath;
-(id)initWithObserver:(id)observer observed:(id)observed keyPath:(NSString*)keyPath selector:(SEL)sel options:(NSKeyValueObservingOptions)options;
{
_observer = observer;
_observed = observed;
_sel = sel;
self.keyPath = keyPath;
[_observed addObserver:self forKeyPath:keyPath options:options context:SPKVOContext];
return self;
}
-(void)dealloc;
{
[self unregister];
[_keyPath release];
[super dealloc];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if(context != SPKVOContext) return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
if(_sel)
((SPKVOCallback)objc_msgSend)(_observer, _sel, change, object, keyPath);
else
[_observer observeValueForKeyPath:keyPath ofObject:object change:change context:self];
}
-(id)unregister;
{
[_observed removeObserver:self forKeyPath:_keyPath];
_observed = nil;
return self;
}
@end
@implementation SPKVONotificationCenter
+ (id)defaultCenter
{
static SPKVONotificationCenter *center = nil;
if(!center)
{
SPKVONotificationCenter *newCenter = [self new];
if(!OSAtomicCompareAndSwapPtrBarrier(nil, newCenter, (void *)&center))
[newCenter release];
}
return center;
}
-(SPKVObservation*)addObserver:(id)observer toObject:(id)observed forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options;
{
return [self addObserver:observer toObject:observed forKeyPath:keyPath options:options selector:NULL];
}
-(SPKVObservation*)addObserver:(id)observer toObject:(id)observed forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options selector:(SEL)sel;
{
SPKVObservation *helper = [[[SPKVObservation alloc] initWithObserver:observer observed:observed keyPath:keyPath selector:sel options:options] autorelease];
return helper;
}
@end
#import <Foundation/Foundation.h>
#import "SPDepends.h"
@interface Foo : NSObject
@property(copy) NSString *foo;
@property(copy) NSString *bar;
@end
@implementation Foo
@synthesize foo, bar;
@end
@interface Listener : NSObject
@property(copy) NSString *thing;
@end
@implementation Listener
@synthesize thing;
-(void)main:(Foo*)b;
{
Foo *a = [[Foo new] autorelease];
$depends(@"fooing", a, @"foo", b, @"foo", @"bar", ^ {
selff.thing = [NSString stringWithFormat:@"a{%@, %@} b{%@, %@}", a.foo, a.bar, b.foo, b.bar];
NSLog(@"%@", selff.thing);
});
a.foo = @"Hello";
a.bar = @"there";
}
@end
int main (int argc, const char * argv[])
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
Listener *listener = [[Listener new] autorelease];
Foo *b = [Foo new];
[listener main:b];
b.foo = @"beautiful";
b.bar = @"world";
[pool drain];
// Note: does not crash; depends_own adds the dependency object to the listener
// as an associated object, so it'll be torn down together with the parent.
b.foo = @"cruel";
[b release];
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment