implemented missing features of KVO for unordered to-many relationships in CPKeyValueCoding.j

This commit is contained in:
Daniel Stolzenberg committed 2010-10-27 19:08:34 +02:00
1 parent dfba974572
commit 4a142b5c02
1 file changed
+146 -103
+146 -103
View File
@@ -57,7 +57,7 @@
{
if (!anObserver || !aPath)
return;
//CPLog.warn(anObserver+" is adding itself as an observer to object: "+self+" forKeyPath: "+aPath+" context: "+aContext);
[[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext];
}
@@ -87,28 +87,36 @@
- (void)applyChange:(CPDictionary)aChange toKeyPath:(CPString)aKeyPath
{
var changeKind = [aChange objectForKey:CPKeyValueChangeKindKey];
var changeKind = [aChange objectForKey:CPKeyValueChangeKindKey],
oldValue = [aChange objectForKey:CPKeyValueChangeOldKey],
newValue = [aChange objectForKey:CPKeyValueChangeNewKey],
indexes = [aChange objectForKey:CPKeyValueChangeIndexesKey];
if(newValue === [CPNull null])
newValue = nil;
if (changeKind === CPKeyValueChangeSetting)
[self setValue:newValue forKeyPath:aKeyPath];
//decide if this is a unordered or ordered to-many relationship
if([newValue isKindOfClass: [CPSet class]] || [oldValue isKindOfClass: [CPSet class]])
{
var value = [aChange objectForKey:CPKeyValueChangeNewKey];
[self setValue:value === [CPNull null] ? nil : value forKeyPath:aKeyPath];
if (changeKind === CPKeyValueChangeInsertion)
[[self mutableSetValueForKeyPath:aKeyPath] unionSet:newValue];
else if (changeKind === CPKeyValueChangeRemoval)
[[self mutableSetValueForKeyPath:aKeyPath] minusSet:newValue];
else if (changeKind === CPKeyValueChangeReplacement)
[[self mutableSetValueForKeyPath:aKeyPath] setSet: newValue];
}
else
{
if (changeKind === CPKeyValueChangeInsertion)
[[self mutableArrayValueForKeyPath:aKeyPath] insertObjects:newValue atIndexes:indexes];
else if (changeKind === CPKeyValueChangeRemoval)
[[self mutableArrayValueForKeyPath:aKeyPath] removeObjectsAtIndexes:indexes];
else if (changeKind === CPKeyValueChangeReplacement)
[[self mutableArrayValueForKeyPath:aKeyPath] replaceObjectAtIndexes:indexes withObjects:newValue];
}
else if (changeKind === CPKeyValueChangeInsertion)
[[self mutableArrayValueForKeyPath:aKeyPath]
insertObjects:[aChange objectForKey:CPKeyValueChangeNewKey]
atIndexes:[aChange objectForKey:CPKeyValueChangeIndexesKey]];
else if (changeKind === CPKeyValueChangeRemoval)
[[self mutableArrayValueForKeyPath:aKeyPath]
removeObjectsAtIndexes:[aChange objectForKey:CPKeyValueChangeIndexesKey]];
else if (changeKind === CPKeyValueChangeReplacement)
[[self mutableArrayValueForKeyPath:aKeyPath]
replaceObjectAtIndexes:[aChange objectForKey:CPKeyValueChangeIndexesKey]
withObjects:[aChange objectForKey:CPKeyValueChangeNewKey]];
}
@end
@@ -187,6 +195,26 @@ CPKeyValueMinusSetMutation = 2;
CPKeyValueIntersectSetMutation = 3;
CPKeyValueSetSetMutation = 4;
//FIXME: "secret" dict ivar-keys are workaround to support unordered to-many relationships without too many modifications
_CPKeyValueChangeSetMutationObjectsKey = @"_CPKeyValueChangeSetMutationObjectsKey";
_CPKeyValueChangeSetMutationKindKey = @"_CPKeyValueChangeSetMutationKindKey";
_CPKeyValueChangeSetMutationNewValueKey = @"_CPKeyValueChangeSetMutationNewValueKey";
var _changeKindForSetMutationKind = function(mutationKind)
{
switch(mutationKind)
{
case CPKeyValueUnionSetMutation:
return CPKeyValueChangeInsertion;
case CPKeyValueMinusSetMutation:
return CPKeyValueChangeRemoval;
case CPKeyValueIntersectSetMutation:
return CPKeyValueChangeRemoval;
case CPKeyValueSetSetMutation:
return CPKeyValueChangeReplacement;
}
}
var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
DependentKeysKey = "$KVODEPENDENT",
KVOProxyKey = "$KVOPROXY";
@@ -211,25 +239,22 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
if (proxy)
return proxy;
proxy = [[self alloc] initWithTarget:anObject];
[proxy _replaceClass];
anObject[KVOProxyKey] = proxy;
return proxy;
return [[self alloc] initWithTarget:anObject];
}
- (id)initWithTarget:(id)aTarget
{
self = [super init];
_targetObject = aTarget;
_nativeClass = [aTarget class];
_observersForKey = {};
_changesForKey = {};
_observersForKeyLength = 0;
if(self = [super init])
{
_targetObject = aTarget;
_nativeClass = [aTarget class];
_observersForKey = {};
_changesForKey = {};
_observersForKeyLength = 0;
[self _replaceClass];
aTarget[KVOProxyKey] = self;
}
return self;
}
@@ -256,7 +281,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
//copy in the methods from our model subclass
var methodList = _CPKVOModelSubclass.method_list;
if ([_targetObject isKindOfClass:[CPDictionary class]])
methodList = methodList.concat(_CPKVOModelDictionarySubclass.method_list);
methodList = methodList.concat(_CPKVOModelDictionarySubclass.method_list);
var count = methodList.length,
i = 0;
@@ -270,7 +295,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
_targetObject.isa = kvoClass;
}
- (void)_replaceSetterForKey:(CPString)aKey
- (void)_replaceModifiersForKey:(CPString)aKey
{
if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey])
return;
@@ -287,11 +312,11 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
"replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":", _kvoReplaceManyMethodForMethod,
"removeObjectFrom" + capitalizedKey + "AtIndex:", _kvoRemoveMethodForMethod,
"remove" + capitalizedKey + "AtIndexes:", _kvoRemoveManyMethodForMethod,
"add" + capitalizedKey + "Object:", _kvoUnionMethodForMethod,
"add" + capitalizedKey + ":", _kvoUnionManyMethodForMethod,
"remove" + capitalizedKey + "Object:", _kvoMinusMethodForMethod,
"remove" + capitalizedKey + ":", _kvoMinusManyMethodForMethod,
"intersect" + capitalizedKey + ":", _kvoIntersectManyMethodForMethod
"add" + capitalizedKey + "Object:", _kvoUnionMethodForMethod,
"add" + capitalizedKey + ":", _kvoUnionManyMethodForMethod,
"remove" + capitalizedKey + "Object:", _kvoMinusMethodForMethod,
"remove" + capitalizedKey + ":", _kvoMinusManyMethodForMethod,
"intersect" + capitalizedKey + ":", _kvoIntersectManyMethodForMethod
];
var i = 0,
@@ -342,7 +367,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
if (affectingKey.indexOf(@".") !== -1)
[_targetObject addObserver:self forKeyPath:affectingKey options:0 context:nil];
else
[self _replaceSetterForKey:affectingKey];
[self _replaceModifiersForKey:affectingKey];
}
}
@@ -369,7 +394,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
if (aPath.indexOf('.') != CPNotFound)
forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
else
[self _replaceSetterForKey:aPath];
[self _replaceModifiersForKey:aPath];
var observers = _observersForKey[aPath];
@@ -423,7 +448,6 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
- (void)_sendNotificationsForKey:(CPString)aKey changeOptions:(CPDictionary)changeOptions isBefore:(BOOL)isBefore
{
// CPLog.warn("_sendNotificationsForKey: " + aKey + " ...isBefore: " + isBefore);
var changes = _changesForKey[aKey];
if (isBefore)
@@ -431,12 +455,36 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
changes = changeOptions;
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
var setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
if (indexes)
if(setMutationKind)
{
var setMutationObjects = [changes[_CPKeyValueChangeSetMutationObjectsKey] copy];
var setExistingObjects = [[_targetObject valueForKey: aKey] copy];
if(setMutationKind == CPKeyValueMinusSetMutation)
{
[setExistingObjects intersectSet: setMutationObjects];
[changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
}
else if (setMutationKind === CPKeyValueIntersectSetMutation || setMutationKind === CPKeyValueSetSetMutation)
{
[setExistingObjects minusSet: setMutationObjects];
[changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
}
//for unordered to-many relationships (CPSet) even new values can only be calculated before!!!
if (setMutationKind === CPKeyValueUnionSetMutation || setMutationKind === CPKeyValueSetSetMutation)
{
[setMutationObjects minusSet: setExistingObjects];
//hide new value (for CPKeyValueObservingOptionPrior messages)
//as long as "didChangeValue..." is not yet called!
changes[_CPKeyValueChangeSetMutationNewValueKey] = setMutationObjects;
}
}
else if (indexes)
{
var type = [changes objectForKey:CPKeyValueChangeKindKey];
// for to-many relationships, oldvalue is only sensible for replace and remove
// for ordered to-many relationships, oldvalue is only sensible for replace and remove
if (type === CPKeyValueChangeReplacement || type === CPKeyValueChangeRemoval)
{
//FIXME: do we need to go through and replace "" with CPNull?
@@ -460,20 +508,30 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
}
else
{
// The isBefore path may not have been called as would happen if didChangeX
// was called alone.
if (!changes)
changes = [CPDictionary new];
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
[changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
var setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
if (indexes)
if(setMutationKind)
{
//old and new values for unordered to-many relationships can only be calculated before
//set precalculated hidden new value as soon as "didChangeValue..." is called!
var newValue = changes[_CPKeyValueChangeSetMutationNewValueKey];
[changes setValue:newValue forKey:CPKeyValueChangeNewKey];
//delete hidden values
delete changes[_CPKeyValueChangeSetMutationNewValueKey];
delete changes[_CPKeyValueChangeSetMutationObjectsKey];
delete changes[_CPKeyValueChangeSetMutationKindKey];
}
else if (indexes)
{
var type = [changes objectForKey:CPKeyValueChangeKindKey];
// for to-many relationships, oldvalue is only sensible for replace and remove
// for to-many relationships, newvalue is only sensible for replace and insert
if (type == CPKeyValueChangeReplacement || type == CPKeyValueChangeInsertion)
{
//FIXME: do we need to go through and replace "" with CPNull?
@@ -522,9 +580,6 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
{
var keyPath = dependentKeyPaths[index];
// CPLog.warn("firing dependepent key " + index + " for " + aKey + ": "+keyPath);
// objj_backtrace_print(CPLog.error);
[self _sendNotificationsForKey:keyPath
changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
isBefore:isBefore];
@@ -607,24 +662,12 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
if (!aKey)
return;
var changeKind = _changeKindForSetMutationKind(mutationKind);
var changeOptions = [CPDictionary dictionaryWithObject:changeKind forKey:CPKeyValueChangeKindKey];
//set hidden change-dict ivars to support unordered to-many relationships
changeOptions[_CPKeyValueChangeSetMutationObjectsKey] = objects;
changeOptions[_CPKeyValueChangeSetMutationKindKey] = mutationKind;
var changeOptions = [CPDictionary dictionary];
switch(mutationKind)
{
case CPKeyValueUnionSetMutation:
[changeOptions setObject:CPKeyValueChangeInsertion forKey:CPKeyValueChangeKindKey];
break;
case CPKeyValueMinusSetMutation:
[changeOptions setObject:CPKeyValueChangeRemoval forKey:CPKeyValueChangeKindKey];
break;
case CPKeyValueIntersectSetMutation:
[changeOptions setObject:CPKeyValueChangeRemoval forKey:CPKeyValueChangeKindKey];
break;
case CPKeyValueSetSetMutation:
[changeOptions setObject:CPKeyValueChangeReplacement forKey:CPKeyValueChangeKindKey];
break;
}
//TO DO: find a way to specify "objects"
[[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
}
@@ -813,7 +856,7 @@ var _kvoMethodForMethod = function _kvoMethodForMethod(theKey, theMethod)
{
return function(self, _cmd, object)
{
//FIXME: do we have to call the specific willChange methods for to-many relationships?
//FIXME: do we have to call the specific willChange methods for to-many relationships?
[self willChangeValueForKey:theKey];
theMethod.method_imp(self, _cmd, object);
[self didChangeValueForKey:theKey];
@@ -882,52 +925,52 @@ var _kvoRemoveManyMethodForMethod = function _kvoRemoveManyMethodForMethod(theKe
var _kvoUnionMethodForMethod = function _kvoUnionMethodForMethod(theKey, theMethod)
{
return function(self, _cmd, object)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: [CPSet setWithObject: object]];
theMethod.method_imp(self, _cmd, object);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: [CPSet setWithObject: object]];
}
return function(self, _cmd, object)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: [CPSet setWithObject: object]];
theMethod.method_imp(self, _cmd, object);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: [CPSet setWithObject: object]];
}
}
var _kvoUnionManyMethodForMethod = function _kvoUnionManyMethodForMethod(theKey, theMethod)
{
return function(self, _cmd, objects)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: objects];
theMethod.method_imp(self, _cmd, objects);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: objects];
}
return function(self, _cmd, objects)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: objects];
theMethod.method_imp(self, _cmd, objects);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: objects];
}
}
var _kvoMinusMethodForMethod = function _kvoMinusMethodForMethod(theKey, theMethod)
{
return function(self, _cmd, object)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: [CPSet setWithObject: object]];
theMethod.method_imp(self, _cmd, object);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: [CPSet setWithObject: object]];
}
return function(self, _cmd, object)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: [CPSet setWithObject: object]];
theMethod.method_imp(self, _cmd, object);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: [CPSet setWithObject: object]];
}
}
var _kvoMinusManyMethodForMethod = function _kvoMinusManyMethodForMethod(theKey, theMethod)
{
return function(self, _cmd, objects)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: objects];
theMethod.method_imp(self, _cmd, objects);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: objects];
}
return function(self, _cmd, objects)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: objects];
theMethod.method_imp(self, _cmd, objects);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: objects];
}
}
var _kvoIntersectManyMethodForMethod = function _kvoIntersectManyMethodForMethod(theKey, theMethod)
{
return function(self, _cmd, objects)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueIntersectSetMutation usingObjects: objects];
theMethod.method_imp(self, _cmd, objects);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueIntersectSetMutation usingObjects: objects];
}
return function(self, _cmd, objects)
{
[self willChangeValueForKey:theKey withSetMutation:CPKeyValueIntersectSetMutation usingObjects: objects];
theMethod.method_imp(self, _cmd, objects);
[self didChangeValueForKey:theKey withSetMutation:CPKeyValueIntersectSetMutation usingObjects: objects];
}
}