Skip to content

Instantly share code, notes, and snippets.

@nilium
Created January 31, 2014 02:31
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 nilium/8725662 to your computer and use it in GitHub Desktop.
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.
#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 */
#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