mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-30 03:48:49 +00:00
Initial KVO implementation, not yet working.
This commit is contained in:
1 parent
7f59ce782b
commit
ce04c97053
3 files changed
+232
-7
No files matched your search
@@ -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];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -267,7 +267,7 @@
|
||||
*/
|
||||
- (void)forward:(SEL)aSelector :(marg_list)args
|
||||
{
|
||||
var signature = [self methodSignatureForSelector:_cmd];
|
||||
var signature = [self methodSignatureForSelector:aSelector];
|
||||
|
||||
if (signature)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user