Created
January 31, 2014 02:31
-
-
Save nilium/8725662 to your computer and use it in GitHub Desktop.
Weird idea for converting KVO changes to messages specific to the key-path. Not at all an optimal idea.
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
#ifndef __guard_QKVOForwarding_h__ | |
#define __guard_QKVOForwarding_h__ | |
#import <Foundation/Foundation.h> | |
@interface NSObject (QKVOForwarding) | |
+ (NSUInteger)maxCachedSelectorCountForForwarding; | |
+ (void)setMaxCachedSelectorCountForForwarding:(NSUInteger)count; | |
// Forward observed changes to key paths from | |
// -observeValueForKeyPath:ofObject:change:context: | |
// Returns true if the change was forwarded to a selector, otherwise false, in | |
// which case the change is considered unhandled. | |
- (BOOL) | |
forwardUpdateForKeyPath:(NSString *)path | |
ofObject:(id)obj | |
change:(NSDictionary *)change | |
context:(void *)context; | |
@end | |
#endif /* end __guard_QKVOForwarding_h__ include guard */ |
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 "QKVOForwarding.h" | |
static const NSUInteger QKVOForwardingCacheCostLimit = | |
((4 * 1024 * 1024) / (NSUInteger)sizeof(unichar)); | |
static dispatch_once_t g_kvoForwardingInitToken; | |
static NSCache *g_selectorCache = nil; | |
static NSCharacterSet *g_dotChar = nil; | |
static NSRange QInitialRange = { 0, 1 }; | |
static | |
NSString * | |
capitalizeKeyString(NSString *key) | |
{ | |
NSString *initial = [[key substringWithRange:QInitialRange] uppercaseString]; | |
return [key stringByReplacingCharactersInRange:QInitialRange withString:initial]; | |
} | |
static void (^const QInitKVOForwardingBlock)() = ^{ | |
NSCache *g_selectorCache = [NSCache new]; | |
[g_selectorCache setCountLimit:32]; | |
[g_selectorCache setTotalCostLimit:QKVOForwardingCacheCostLimit]; | |
g_dotChar = [NSCharacterSet characterSetWithCharactersInString:@"."]; | |
}; | |
static | |
SEL | |
selectorForKeyPath(NSString *path) | |
{ | |
dispatch_once(&g_kvoForwardingInitToken, QInitKVOForwardingBlock); | |
SEL result = NSSelectorFromString([g_selectorCache objectForKey:path]); | |
if (!result) { | |
NSArray *keys = [path componentsSeparatedByCharactersInSet:g_dotChar]; | |
NSMutableArray *capKeys = [NSMutableArray arrayWithCapacity:[keys count]]; | |
for (NSString *key in keys) { | |
[capKeys addObject:capitalizeKeyString(key)]; | |
} | |
NSString *capPath = [capKeys componentsJoinedByString:@""]; | |
keys = nil; | |
capKeys = nil; | |
NSString *tform = | |
#ifdef QKVOFORWARDING_PASS_FROM_TO | |
[NSString stringWithFormat:@"observe%@OfObject:changedFrom:toValue:", | |
#else | |
[NSString stringWithFormat:@"observe%@OfObject:changed:", | |
#endif | |
capPath]; | |
capPath = nil; | |
result = NSSelectorFromString(tform); | |
[g_selectorCache setObject:tform forKey:path cost:[tform length]]; | |
} | |
return result; | |
} | |
@implementation NSObject (QKVOForwarding) | |
+ (NSUInteger)maxCachedSelectorCountForForwarding | |
{ | |
dispatch_once(&g_kvoForwardingInitToken, QInitKVOForwardingBlock); | |
@synchronized(g_selectorCache) { | |
return [g_selectorCache countLimit]; | |
} | |
} | |
+ (void)setMaxCachedSelectorCountForForwarding:(NSUInteger)count | |
{ | |
dispatch_once(&g_kvoForwardingInitToken, QInitKVOForwardingBlock); | |
@synchronized(g_selectorCache) { | |
[g_selectorCache setCountLimit:count]; | |
} | |
} | |
- (BOOL) | |
forwardUpdateForKeyPath:(NSString *)path | |
ofObject:(id)obj | |
change:(NSDictionary *)change | |
context:(void *)context | |
{ | |
SEL sel = selectorForKeyPath(path); | |
if (![self respondsToSelector:sel]) { | |
return NO; | |
} | |
#ifdef QKVOFORWARDING_PASS_FROM_TO | |
// Implemented using NSInvocation for observeXOfObject:changedFrom:toValue: | |
NSMethodSignature *sig = [self methodSignatureForSelector:sel]; | |
if (!sig) { | |
return NO; | |
} | |
NSInvocation *invoker = [NSInvocation invocationWithMethodSignature:sig]; | |
invoker.target = self; | |
invoker.selector = sel; | |
[invoker setArgument:obj atIndex:2]; | |
[invoker setArgument:change[NSKeyValueChangeOldKey] atIndex:3]; | |
[invoker setArgument:change[NSKeyValueChangeNewKey] atIndex:4]; | |
[invoker invoke]; | |
#else | |
[self performSelector:sel withObject:obj withObject:change]; | |
#endif | |
return YES; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment