Forked from andymatuschak/NSObject+BlockObservation.h
Last active
August 29, 2015 14:19
-
-
Save fjolnir/4d7a4a8810be0fd627db to your computer and use it in GitHub Desktop.
Made the thing build under ARC
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; | |
typedef NSString AMBlockToken; | |
typedef void (^AMBlockTask)(id obj, NSDictionary *change); | |
@interface NSObject (AMBlockObservation) | |
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath task:(AMBlockTask)task; | |
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath onQueue:(NSOperationQueue *)queue task:(AMBlockTask)task; | |
- (void)removeObserverWithBlockToken:(AMBlockToken *)token; | |
@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; | |
AMBlockTask task; | |
NSOperationQueue *queue; | |
dispatch_once_t cancellationPredicate; | |
} | |
- (AMObserverTrampoline *)initObservingObject:(id)obj keyPath:(NSString *)keyPath onQueue:(NSOperationQueue *)queue task:(AMBlockTask)task; | |
- (void)cancelObservation; | |
@end | |
@implementation AMObserverTrampoline | |
static void *AMObserverTrampolineContext = &AMObserverTrampolineContext; | |
- (AMObserverTrampoline *)initObservingObject:(id)obj keyPath:(NSString *)newKeyPath onQueue:(NSOperationQueue *)newQueue task:(AMBlockTask)newTask | |
{ | |
if (!(self = [super init])) return nil; | |
task = [newTask copy]; | |
keyPath = [newKeyPath copy]; | |
queue = newQueue; | |
cancellationPredicate = 0; | |
observee = obj; | |
[obj addObserver:self forKeyPath:keyPath options:0 context:AMObserverTrampolineContext]; | |
return self; | |
} | |
- (void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context | |
{ | |
if (context == AMObserverTrampolineContext) | |
{ | |
if (queue) | |
[queue addOperationWithBlock:^{ task(object, change); }]; | |
else | |
task(object, change); | |
} | |
} | |
- (void)cancelObservation | |
{ | |
dispatch_once(&cancellationPredicate, ^{ | |
[observee removeObserver:self forKeyPath:keyPath]; | |
observee = nil; | |
}); | |
} | |
- (void)dealloc | |
{ | |
[self cancelObservation]; | |
} | |
@end | |
static void *AMObserverMapKey = &AMObserverMapKey; | |
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 task:(AMBlockTask)task | |
{ | |
return [self addObserverForKeyPath:keyPath onQueue:nil task:task]; | |
} | |
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath onQueue:(NSOperationQueue *)queue task:(AMBlockTask)task | |
{ | |
AMBlockToken *token = [[NSProcessInfo processInfo] globallyUniqueString]; | |
dispatch_sync(AMObserverMutationQueueCreatingIfNecessary(), ^{ | |
NSMutableDictionary *dict = objc_getAssociatedObject(self, AMObserverMapKey); | |
if (!dict) | |
{ | |
dict = [NSMutableDictionary new]; | |
objc_setAssociatedObject(self, AMObserverMapKey, dict, OBJC_ASSOCIATION_RETAIN); | |
} | |
AMObserverTrampoline *trampoline = [[AMObserverTrampoline alloc] initObservingObject:self keyPath:keyPath onQueue:queue task:task]; | |
[dict setObject:trampoline forKey:token]; | |
}); | |
return token; | |
} | |
- (void)removeObserverWithBlockToken:(AMBlockToken *)token | |
{ | |
dispatch_sync(AMObserverMutationQueueCreatingIfNecessary(), ^{ | |
NSMutableDictionary *observationDictionary = objc_getAssociatedObject(self, AMObserverMapKey); | |
AMObserverTrampoline *trampoline = [observationDictionary objectForKey:token]; | |
if (!trampoline) | |
{ | |
NSLog(@"[NSObject(AMBlockObservation) removeObserverWithBlockToken]: Ignoring attempt to remove non-existent observer on %@ for token %@.", self, token); | |
return; | |
} | |
[trampoline cancelObservation]; | |
[observationDictionary removeObjectForKey:token]; | |
// 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