Skip to content

Instantly share code, notes, and snippets.

@kkazuo
Created May 19, 2012 16:44
Show Gist options
  • Save kkazuo/2731451 to your computer and use it in GitHub Desktop.
Save kkazuo/2731451 to your computer and use it in GitHub Desktop.
KVOHelper. Easy Key-Value Observing
// AUTHOR: Kazuo Koga
// LICENSE: MIT
#import <Foundation/Foundation.h>
@interface NSObject (KVOHelper)
- (id)addObserveBlockForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options with:(void (^)(NSDictionary *change))block;
- (void)removeObserveBlockForKeyPath:(NSString *)keyPath withID:(id)contextID;
+ (NSString *)descriptionOfObserveBlock;
@end
// AUTHOR: Kazuo Koga
// LICENSE: MIT
#import "NSObject+KVOHelper.h"
@interface KVOHelperKey : NSObject
@property(nonatomic) NSObject *target;
@property(nonatomic, copy) NSString *keyPath;
@end
@implementation KVOHelperKey
@synthesize target = _target;
@synthesize keyPath = _keyPath;
- (NSString *)description
{
return [[NSString alloc] initWithFormat:@"<%@ (%@:%p, %@)>", self.class, self.target.class, self.target, self.keyPath];
}
- (NSUInteger)hash
{
return (self.target.hash ^ self.keyPath.hash);
}
- (BOOL)isEqual:(id)object
{
KVOHelperKey *other = object;
return [self.target isEqual:other.target] && [self.keyPath isEqualToString:other.keyPath];
}
@end
@class KVOHelperContext;
@interface KVOHelperContextRef : NSObject
@property(nonatomic, unsafe_unretained) KVOHelperContext *context;
@end
@implementation KVOHelperContextRef
@synthesize context = _context;
@end
@interface KVOHelperContext : NSObject
@property(nonatomic) KVOHelperContextRef *ref;
@property(nonatomic) id block;
@end
@implementation KVOHelperContext
@synthesize ref = _ref;
@synthesize block = _block;
- (void)dealloc
{
_ref.context = nil;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == (__bridge void *)self) {
((void (^)(NSDictionary *))self.block)(change);
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
@end
@implementation NSObject (KVOHelper)
static NSMutableDictionary *globalObservers(void)
{
static NSMutableDictionary *observers = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
observers = [NSMutableDictionary new];
});
return observers;
}
- (id)addObserveBlockForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options with:(void (^)(NSDictionary *change))block
{
NSParameterAssert(NSThread.currentThread.isMainThread);
NSParameterAssert(keyPath);
NSParameterAssert(block);
KVOHelperContext *context = [KVOHelperContext new];
context.ref = [KVOHelperContextRef new];
context.ref.context = context;
context.block = [block copy];
KVOHelperKey *key = [KVOHelperKey new];
key.target = self;
key.keyPath = keyPath;
NSMutableDictionary *allObservers = globalObservers();
NSMutableSet *observers = [allObservers objectForKey:key];
if (!observers) {
observers = [NSMutableSet new];
[allObservers setObject:observers forKey:key];
}
[observers addObject:context];
[self addObserver:context forKeyPath:keyPath options:options context:(__bridge void *)context];
return context.ref;
}
- (void)removeObserveBlockForKeyPath:(NSString *)keyPath withID:(id)contextID
{
NSParameterAssert(NSThread.currentThread.isMainThread);
NSParameterAssert(keyPath);
KVOHelperKey *key = [KVOHelperKey new];
key.target = self;
key.keyPath = keyPath;
NSMutableDictionary *allObservers = globalObservers();
NSMutableSet *observers = [allObservers objectForKey:key];
if (observers) {
if (contextID) {
id context = ((KVOHelperContextRef *)contextID).context;
if (context) {
[self removeObserver:context forKeyPath:keyPath context:(__bridge void *)context];
[observers removeObject:context];
if (observers.count <= 0) {
[allObservers removeObjectForKey:key];
}
}
}
else {
[observers enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
[self removeObserver:obj forKeyPath:keyPath];
}];
[allObservers removeObjectForKey:key];
}
}
}
+ (NSString *)descriptionOfObserveBlock
{
return [globalObservers() description];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment