Initial KVO implementation, not yet working.

This commit is contained in:
Ross Boucher committed 2008-09-21 14:57:06 -07:00
1 parent 7f59ce782b
commit ce04c97053
3 files changed
+232 -7

No files matched your search

+6 -4
View File
@@ -152,12 +152,13 @@ CPUnknownUserInfoKey = @"CPUnknownUserInfoKey";
- (id)valueForKey:(CPString)aKey
{
var selector = [isa _accessorForKey:aKey];
var theClass = [self class],
selector = [theClass _accessorForKey:aKey];
if (selector)
return objj_msgSend(self, selector);
if([isa accessInstanceVariablesDirectly])
if([theClass accessInstanceVariablesDirectly])
{
var ivar = [self _ivarForKey:aKey];
@@ -227,12 +228,13 @@ CPUnknownUserInfoKey = @"CPUnknownUserInfoKey";
- (void)setValue:(id)aValue forKey:(CPString)aKey
{
var selector = [isa _modifierForKey:aKey];
var theClass = [self class],
selector = [theClass _modifierForKey:aKey];
if (selector)
return objj_msgSend(self, selector, aValue);
if([isa accessInstanceVariablesDirectly])
if([theClass accessInstanceVariablesDirectly])
{
var ivar = [self _ivarForKey:aKey];
+225 -2
View File
@@ -20,9 +20,10 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
import "CPArray.j"
import "CPDictionary.j"
import "CPObject.j"
@implementation CPObject (KeyValueObserving)
- (void)willChangeValueForKey:(CPString)aKey
@@ -36,4 +37,226 @@ import "CPObject.j"
}
@end
- addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
{
if (!anObserver || !aPath)
return;
[[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext];
}
- removeObserver:(id)anObserver forKeyPath:(CPString)aPath
{
if (!anObserver || !aPath)
return;
[[KVOProxyMap objectForKey:[self hash]] _removeObserver:anObserver forKeyPath:aPath];
}
- (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
{
return YES;
}
@end
var KVOProxyMap = [CPDictionary dictionary];
//rule of thumb: _ methods are called on the real proxy object, others are called on the "fake" proxy object (aka the real object)
@implementation _CPKVOProxy : CPObject
{
id _targetObject;
Class _nativeClass;
CPDictionary _observerInfos;
CPDictionary _replacementMethods;
}
+ (id)proxyForObject:(CPObject)anObject
{
var proxy = [KVOProxyMap objectForKey:[anObject hash]];
if (proxy)
return proxy;
proxy = [[self alloc] initWithTarget:anObject];
[proxy _replaceSetters];
anObject.isa = proxy.isa;
[KVOProxyMap setObject:proxy forKey:[anObject hash]];
return proxy;
}
- (id)initWithTarget:(id)aTarget
{
self = [super init];
_targetObject = aTarget;
_nativeClass = [aTarget class];
_observerInfos = [CPDictionary dictionary];
_replacementMethods = [CPDictionary dictionary];
return self;
}
- (void)_replaceSetters
{
var currentClass = [_targetObject class];
while (currentClass && currentClass != currentClass.super_class)
{
var methodList = currentClass.method_list,
count = methodList.length;
for (var i=0; i<count; i++)
{
var newMethod = _kvoMethodForMethod(methodList[i]);
if (newMethod)
[_replacementMethods setObject:newMethod forKey:methodList[i].name];
}
currentClass = currentClass.super_class;
}
}
- (id)class
{
return [KVOProxyMap objectForKey:[self hash]]._nativeClass;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
var proxy = [_CPKVOProxy proxyForObject:self],
imp = class_getInstanceMethod(proxy._nativeClass, aSelector);
return imp ? YES : NO;
}
- (id)methodSignatureForSelector:(SEL)aSelector
{
//FIXME: this only works because we don't have method signatures
return [self respondsToSelector:aSelector];
}
- (IMP)methodForSelector:(SEL)aSelector
{
var proxy = [_CPKVOProxy proxyForObject:self],
imp = class_getInstanceMethod(proxy._nativeClass, aSelector),
replacement = [proxy._replacementMethods objectForKey:aSelector];
return replacement ? replacement : imp;
}
- (void)forwardInvocation:(CPInvocation)anInvocation
{
var proxy = [_CPKVOProxy proxyForObject:self],
method = [self methodForSelector:[anInvocation selector]];
if (method)
method.apply(self, anInvocation._arguments); //FIXME
else
[super forwardInvocation:anInvocation];
}
- (void)_addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
{
if (!anObserver)
return;
var info = [_observerInfos objectForKey:[anObserver hash]];
if (!info)
{
info = _CPKVOInfoMake(anObserver);
[_observerInfos setObject:info forKey:[anObserver hash]];
}
if (aPath.indexOf('.') != CPNotFound)
return print("WHOA, don't go crazy...");
else
[info.keyPaths setObject:_CPKVOInfoRecordMake(options, aContext) forKey:aPath];
}
- (void)_removeObserver:(id)anObserver forKeyPath:(CPString)aPath
{
var info = [_observerInfos objectForKey:[anObserver hash]],
path = [info.keyPaths objectForKey:aPath];
if (path)
[info.keyPaths removeObjectForKey:aPath];
if (![info.keyPaths count])
[_observerInfos removeObjectForKey:anObserver];
if (![_observerInfos count])
{
_targetObject.isa = _nativeClass; //restore the original class
[KVOProxyMap removeObjectForKey:[_targetObject hash]];
}
}
- (void)willChangeValueForKey:(CPString)aKey
{
print("WILL CHANGE FOR: "+aKey);
}
- (void)didChangeValueForKey:(CPString)aKey
{
print("DID CHANGE FOR: "+aKey);
}
@end
var _CPKVOInfoMake = function _CPKVOInfoMake(anObserver)
{
return {
observer: anObserver,
keyPaths: [CPDictionary dictionary],
changes: [CPDictionary dictionary]
};
}
var _CPKVOInfoRecordMake = function _CPKVOInfoRecordMake(theOptions, aContext)
{
return {
options: theOptions ? theOptions : 0,
context: aContext ? aContext : [CPNull null]
};
}
var _kvoMethodForMethod = function _kvoMethodForMethod(theMethod)
{
var methodName = theMethod.name,
methodImplementation = theMethod.method_imp,
setterKey = kvoKeyForSetter(methodName);
if (setterKey)
{
var newMethodImp = function(self)
{
[self willChangeValueForKey:setterKey];
methodImplementation.apply(self, arguments);
[self didChangeValueForKey:setterKey];
}
return newMethodImp;
}
return nil;
}
var kvoKeyForSetter = function kvoKeyForSetter(selector)
{
if (selector.split(":").length > 2 || !([selector hasPrefix:@"set"] || [selector hasPrefix:@"_set"]))
return nil;
var keyIndex = selector.indexOf("set") + "set".length,
colonIndex = selector.indexOf(":");
return selector.charAt(keyIndex).toLowerCase() + selector.substring(keyIndex+1, colonIndex);
}
+1 -1
View File
@@ -267,7 +267,7 @@
*/
- (void)forward:(SEL)aSelector :(marg_list)args
{
var signature = [self methodSignatureForSelector:_cmd];
var signature = [self methodSignatureForSelector:aSelector];
if (signature)
{