Skip to content

Commit

Permalink
Initial KVO implementation, not yet working.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Boucher committed Sep 21, 2008
1 parent 7f59ce7 commit ce04c97
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 7 deletions.
10 changes: 6 additions & 4 deletions Foundation/CPKeyValueCoding.j
Expand Up @@ -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];

Expand Down Expand Up @@ -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];

Expand Down
227 changes: 225 additions & 2 deletions Foundation/CPKeyValueObserving.j
Expand Up @@ -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
Expand All @@ -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);
}
2 changes: 1 addition & 1 deletion Foundation/CPObject.j
Expand Up @@ -267,7 +267,7 @@
*/
- (void)forward:(SEL)aSelector :(marg_list)args
{
var signature = [self methodSignatureForSelector:_cmd];
var signature = [self methodSignatureForSelector:aSelector];

if (signature)
{
Expand Down

0 comments on commit ce04c97

Please sign in to comment.