Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atty303/708511 to your computer and use it in GitHub Desktop.
Save atty303/708511 to your computer and use it in GitHub Desktop.
//
// NSObject+BlockObservation.h
// Version 1.0
//
// Andy Matuschak
// andy@andymatuschak.org
// 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
//
// NSObject+BlockObservation.h
// Version 1.0
//
// Andy Matuschak
// andy@andymatuschak.org
// 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
@atty303
Copy link
Author

atty303 commented Nov 21, 2010

Modified headers that works on iOS.
Added support for KVO options.
Added support for KVO context. That can remove observers associated with a context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment