Forked from andymatuschak/NSObject+BlockObservation.h
Created
November 21, 2010 06:27
-
-
Save atty303/708511 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// NSObject+BlockObservation.h | |
// Version 1.0 | |
// | |
// Andy Matuschak | |
// [email protected] | |
// Public domain because I love you. Let me know how you use it. | |
// | |
#import <Foundation/Foundation.h> | |
typedef NSString AMBlockToken; | |
typedef void (^AMBlockTask)(id obj, NSDictionary *change, id context); | |
@interface NSObject (AMBlockObservation) | |
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath | |
options:(NSKeyValueObservingOptions)options | |
context:(id)context | |
onQueue:(NSOperationQueue *)queue | |
task:(AMBlockTask)task; | |
- (void)removeObserversWithContext:(id)context; | |
@end |
This file contains hidden or 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
// | |
// NSObject+BlockObservation.h | |
// Version 1.0 | |
// | |
// Andy Matuschak | |
// [email protected] | |
// Public domain because I love you. Let me know how you use it. | |
// | |
#import "NSObject+BlockObservation.h" | |
#import <dispatch/dispatch.h> | |
#import <objc/runtime.h> | |
@interface AMObserverTrampoline : NSObject | |
{ | |
__weak id observee; | |
NSString *keyPath; | |
__weak id context; | |
AMBlockTask task; | |
NSOperationQueue *queue; | |
dispatch_once_t cancellationPredicate; | |
} | |
@property (nonatomic, readonly) id context; | |
- (AMObserverTrampoline *)initObservingObject:(id)obj | |
keyPath:(NSString *)newKeyPath | |
options:(NSKeyValueObservingOptions)newOptions | |
context:(id)newContext | |
onQueue:(NSOperationQueue *)newQueue | |
task:(AMBlockTask)newTask; | |
- (void)cancelObservation; | |
@end | |
@implementation AMObserverTrampoline | |
static NSString *AMObserverTrampolineContext = @"AMObserverTrampolineContext"; | |
@synthesize context; | |
- (AMObserverTrampoline *)initObservingObject:(id)obj | |
keyPath:(NSString *)newKeyPath | |
options:(NSKeyValueObservingOptions)newOptions | |
context:(id)newContext | |
onQueue:(NSOperationQueue *)newQueue | |
task:(AMBlockTask)newTask | |
{ | |
if (!(self = [super init])) return nil; | |
task = [newTask copy]; | |
keyPath = [newKeyPath copy]; | |
context = newContext; | |
queue = [newQueue retain]; | |
observee = obj; | |
cancellationPredicate = 0; | |
[observee addObserver:self forKeyPath:keyPath options:newOptions context:AMObserverTrampolineContext]; | |
return self; | |
} | |
- (void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)rawContext | |
{ | |
if (rawContext == AMObserverTrampolineContext) | |
{ | |
if (queue) | |
[queue addOperationWithBlock:^{ task(object, change, context); }]; | |
else | |
task(object, change, context); | |
} | |
} | |
- (void)cancelObservation | |
{ | |
dispatch_once(&cancellationPredicate, ^{ | |
[observee removeObserver:self forKeyPath:keyPath]; | |
observee = nil; | |
}); | |
} | |
- (void)dealloc | |
{ | |
[self cancelObservation]; | |
[task release]; | |
[keyPath release]; | |
[queue release]; | |
[super dealloc]; | |
} | |
@end | |
static NSString *AMObserverMapKey = @"org.andymatuschak.observerMap"; | |
static dispatch_queue_t AMObserverMutationQueue = NULL; | |
static dispatch_queue_t AMObserverMutationQueueCreatingIfNecessary() | |
{ | |
static dispatch_once_t queueCreationPredicate = 0; | |
dispatch_once(&queueCreationPredicate, ^{ | |
AMObserverMutationQueue = dispatch_queue_create("org.andymatuschak.observerMutationQueue", 0); | |
}); | |
return AMObserverMutationQueue; | |
} | |
@implementation NSObject (AMBlockObservation) | |
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath | |
options:(NSKeyValueObservingOptions)options | |
context:(id)context | |
onQueue:(NSOperationQueue *)queue | |
task:(AMBlockTask)task | |
{ | |
AMBlockToken *token = [[NSProcessInfo processInfo] globallyUniqueString]; | |
dispatch_sync(AMObserverMutationQueueCreatingIfNecessary(), ^{ | |
NSMutableDictionary *dict = objc_getAssociatedObject(self, AMObserverMapKey); | |
if (!dict) | |
{ | |
dict = [[NSMutableDictionary alloc] init]; | |
objc_setAssociatedObject(self, AMObserverMapKey, dict, OBJC_ASSOCIATION_RETAIN); | |
[dict release]; | |
} | |
AMObserverTrampoline *trampoline = [[AMObserverTrampoline alloc] initObservingObject:self keyPath:keyPath options:options context:context onQueue:queue task:task]; | |
[dict setObject:trampoline forKey:token]; | |
[trampoline release]; | |
}); | |
return token; | |
} | |
- (void)removeObserversWithContext:(id)context | |
{ | |
dispatch_sync(AMObserverMutationQueueCreatingIfNecessary(), ^{ | |
NSMutableDictionary *observationDictionary = objc_getAssociatedObject(self, AMObserverMapKey); | |
[observationDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { | |
AMObserverTrampoline *trampoline = obj; | |
if ([trampoline context] == context) { | |
[trampoline cancelObservation]; | |
[observationDictionary removeObjectForKey:key]; | |
} | |
}]; | |
// Due to a bug in the obj-c runtime, this dictionary does not get cleaned up on release when running without GC. | |
if ([observationDictionary count] == 0) | |
objc_setAssociatedObject(self, AMObserverMapKey, nil, OBJC_ASSOCIATION_RETAIN); | |
}); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Modified headers that works on iOS.
Added support for KVO options.
Added support for KVO context. That can remove observers associated with a context.