Created
June 16, 2011 16:58
-
-
Save nevyn/1029683 to your computer and use it in GitHub Desktop.
Oneliner KVO.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 *)¢er)) | |
[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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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