Honor options when creating KVO change dictionary

This commit is contained in:
Martin Carlberg committed 2014-10-09 19:27:57 +02:00
1 parent 5e0e61b7e5
commit 643566bbfd
1 file changed
+172 -93
+172 -93
View File
@@ -327,6 +327,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
Class _nativeClass;
CPDictionary _changesForKey;
CPDictionary _nestingForKey;
CPDictionary _minOptionsForKey;
Object _observersForKey;
int _observersForKeyLength;
CPSet _replacedKeys;
@@ -354,6 +355,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
_observersForKey = {};
_changesForKey = {};
_nestingForKey = {};
_minOptionsForKey = {};
_observersForKeyLength = 0;
[self _replaceClass];
@@ -767,12 +769,20 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
if (options & CPKeyValueObservingOptionInitial)
{
var newValue = [_targetObject valueForKeyPath:aPath];
var changes;
if (newValue === nil || newValue === undefined)
newValue = [CPNull null];
if (options & CPKeyValueObservingOptionNew)
{
var newValue = [_targetObject valueForKeyPath:aPath];
if (newValue == nil)
newValue = [CPNull null];
changes = @{ CPKeyValueChangeKindKey: CPKeyValueChangeSetting, CPKeyValueChangeNewKey: newValue };
} else {
changes = @{ CPKeyValueChangeKindKey: CPKeyValueChangeSetting };
}
var changes = @{ CPKeyValueChangeNewKey: newValue };
[anObserver observeValueForKeyPath:aPath ofObject:_targetObject change:changes context:aContext];
}
}
@@ -819,7 +829,9 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
- (void)_sendNotificationsForKey:(CPString)aKey changeOptions:(CPDictionary)changeOptions isBefore:(BOOL)isBefore
{
var changes = _changesForKey[aKey];
var changes = _changesForKey[aKey],
observers = [_observersForKey[aKey] allValues],
observersMinimumOptions = 0;
if (isBefore)
{
@@ -838,61 +850,78 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
_nestingForKey[aKey] = 1;
// Get the combined minimum of the ...Old and ...New options for all observers
var count = observers ? observers.length : 0;
while (count--)
{
var observerInfo = observers[count];
observersMinimumOptions |= observerInfo.options & kvoNewAndOld;
}
_minOptionsForKey[aKey] = observersMinimumOptions;
changes = changeOptions;
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
if (setMutationKind)
if (observersMinimumOptions & CPKeyValueObservingOptionOld)
{
var setMutationObjects = [changes[_CPKeyValueChangeSetMutationObjectsKey] copy],
setExistingObjects = [[_targetObject valueForKey: aKey] copy];
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
if (setMutationKind == CPKeyValueMinusSetMutation)
if (setMutationKind)
{
[setExistingObjects intersectSet: setMutationObjects];
[changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
var setMutationObjects = [changes[_CPKeyValueChangeSetMutationObjectsKey] copy],
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 (setMutationKind === CPKeyValueIntersectSetMutation || setMutationKind === CPKeyValueSetSetMutation)
else if (indexes)
{
[setExistingObjects minusSet: setMutationObjects];
[changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
var type = [changes objectForKey:CPKeyValueChangeKindKey];
// 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?
var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
[changes setValue:newValues forKey:CPKeyValueChangeOldKey];
}
}
else
{
var oldValue = [_targetObject valueForKey:aKey];
if (oldValue === nil || oldValue === undefined)
oldValue = [CPNull null];
[changes setObject:oldValue 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 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?
var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
[changes setValue:newValues forKey:CPKeyValueChangeOldKey];
}
}
else
{
var oldValue = [_targetObject valueForKey:aKey];
if (oldValue === nil || oldValue === undefined)
oldValue = [CPNull null];
[changes setObject:oldValue forKey:CPKeyValueChangeOldKey];
}
[changes setObject:1 forKey:CPKeyValueChangeNotificationIsPriorKey];
_changesForKey[aKey] = changes;
// Clear ...New option as it should never be sent for a ...Prior option
observersMinimumOptions &= ~CPKeyValueObservingOptionNew;
}
else
{
@@ -927,55 +956,97 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
[changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
observersMinimumOptions = _minOptionsForKey[aKey];
if (setMutationKind)
if (observersMinimumOptions & CPKeyValueObservingOptionNew)
{
//old and new values for unordered to-many relationships can only be calculated before
//set recalculated hidden new value as soon as "didChangeValue..." is called!
var newValue = changes[_CPKeyValueChangeSetMutationNewValueKey];
[changes setValue:newValue forKey:CPKeyValueChangeNewKey];
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
//delete hidden values
delete changes[_CPKeyValueChangeSetMutationNewValueKey];
delete changes[_CPKeyValueChangeSetMutationObjectsKey];
delete changes[_CPKeyValueChangeSetMutationKindKey];
}
else if (indexes)
{
var type = [changes objectForKey:CPKeyValueChangeKindKey];
// for ordered to-many relationships, newvalue is only sensible for replace and insert
if (type == CPKeyValueChangeReplacement || type == CPKeyValueChangeInsertion)
if (setMutationKind)
{
//FIXME: do we need to go through and replace "" with CPNull?
var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
[changes setValue:newValues forKey:CPKeyValueChangeNewKey];
//old and new values for unordered to-many relationships can only be calculated before
//set recalculated 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 ordered 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?
var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
[changes setValue:newValues forKey:CPKeyValueChangeNewKey];
}
}
else
{
var newValue = [_targetObject valueForKey:aKey];
if (newValue === nil || newValue === undefined)
newValue = [CPNull null];
[changes setObject:newValue forKey:CPKeyValueChangeNewKey];
}
}
delete _minOptionsForKey[aKey];
delete _changesForKey[aKey];
}
var count = observers ? observers.length : 0,
changesCache = {};
while (count--)
{
var observerInfo = observers[count],
options = observerInfo.options,
onlyNewAndOldOptions = options & kvoNewAndOld,
observerChanges = nil;
if (isBefore)
{
// Only send 'observeValueForKeyPath:' for '...Prior' option when handling 'willChangeValue...'
if (options & CPKeyValueObservingOptionPrior)
{
observerChanges = changes;
// The new values are not yet created in the change dictionary so remove ...New option to get a working cache below
onlyNewAndOldOptions &= ~CPKeyValueObservingOptionNew;
}
}
else
{
var newValue = [_targetObject valueForKey:aKey];
if (newValue === nil || newValue === undefined)
newValue = [CPNull null];
[changes setObject:newValue forKey:CPKeyValueChangeNewKey];
observerChanges = changes;
}
delete _changesForKey[aKey];
}
var observers = [_observersForKey[aKey] allValues],
count = observers ? observers.length : 0;
while (count--)
{
var observerInfo = observers[count];
if (!isBefore || (observerInfo.options & CPKeyValueObservingOptionPrior))
[observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:changes context:observerInfo.context];
if (observerChanges)
{
// Don't change the 'change' dictionary when the observer wants the minimum options.
// The ...New option is remved above for the ...Prior case
if (onlyNewAndOldOptions !== observersMinimumOptions)
{
// Use a subset of the 'change' dictionary. First try to find it in the cache
observerChanges = changesCache[onlyNewAndOldOptions];
if (!observerChanges)
{
// Not in the cache. Build a new dictionary and store it in the cache
changesCache[onlyNewAndOldOptions] = observerChanges = [changes mutableCopy];
if (!(onlyNewAndOldOptions & CPKeyValueObservingOptionOld))
[observerChanges removeObjectForKey:CPKeyValueChangeOldKey];
if (!(onlyNewAndOldOptions & CPKeyValueObservingOptionNew))
[observerChanges removeObjectForKey:CPKeyValueChangeNewKey];
}
}
[observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:observerChanges context:observerInfo.context];
}
}
var dependentKeysMap = _nativeClass[DependentKeysKey];
@@ -1226,13 +1297,21 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
{
if (aKeyPath === _firstPart)
{
var oldValue = [_value valueForKeyPath:_secondPart],
newValue = [_object valueForKeyPath:_firstPart + "." + _secondPart],
pathChanges = @{
CPKeyValueChangeNewKey: newValue ? newValue : [CPNull null],
CPKeyValueChangeOldKey: oldValue ? oldValue : [CPNull null],
CPKeyValueChangeKindKey: CPKeyValueChangeSetting,
};
var pathChanges = [CPMutableDictionary dictionaryWithObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey];
if (_options & CPKeyValueObservingOptionOld)
{
var oldValue = [_value valueForKeyPath:_secondPart];
[pathChanges setObject:oldValue != null ? oldValue : [CPNull null] forKey:CPKeyValueChangeOldKey];
}
if (_options & CPKeyValueObservingOptionNew)
{
var newValue = [_object valueForKeyPath:_firstPart + "." + _secondPart];
[pathChanges setObject:newValue != null ? newValue : [CPNull null] forKey:CPKeyValueChangeNewKey];
}
[_observer observeValueForKeyPath:_firstPart + "." + _secondPart ofObject:_object change:pathChanges context:_context];