mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-09 10:20:59 +00:00
Merge pull request #2224 from mrcarlberg/kvo_honor_options_when_creating_change_dictionary
KVO now honor options when creating change dictionary
This commit is contained in:
commit
f2956670cd
5 files changed
+583
-129
No files matched your search
@@ -156,7 +156,7 @@ var CPBindingOperationAnd = 0,
|
||||
|
||||
[self _updatePlaceholdersWithOptions:options forBinding:aName];
|
||||
|
||||
[aDestination addObserver:self forKeyPath:aKeyPath options:CPKeyValueObservingOptionNew context:aBinding];
|
||||
[aDestination addObserver:self forKeyPath:aKeyPath options:0 context:aBinding];
|
||||
|
||||
var bindings = [bindingsMap objectForKey:[_source UID]];
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
CPArray observations;
|
||||
int aCount @accessors;
|
||||
|
||||
CPArray nameValues @accessors;
|
||||
CPArray depValues @accessors;
|
||||
}
|
||||
|
||||
- (CPArray)makeTestArray
|
||||
@@ -19,6 +22,22 @@
|
||||
[Employee employeeWithName:@"Tom" department:[Department departmentWithName:@"CommonJS"]]];
|
||||
}
|
||||
|
||||
- (CPArray)makeTestArrayMany
|
||||
{
|
||||
var a = [[Employee employeeWithName:@"Francisco" department:[Department departmentWithName:@"Cappuccino"]],
|
||||
[Employee employeeWithName:@"Ross" department:[Department departmentWithName:@"Cappuccino"]],
|
||||
[Employee employeeWithName:@"Tom" department:[Department departmentWithName:@"CommonJS"]]];
|
||||
|
||||
var i = 10000;
|
||||
|
||||
while (i--)
|
||||
{
|
||||
[a addObject:[Employee employeeWithName:@"Martin" + i department:[Department departmentWithName:@"Magic Lightning"]]];
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
- (void)initControllerWithSimpleArray
|
||||
{
|
||||
// Copy the array to allow the original to be reused.
|
||||
@@ -1054,6 +1073,54 @@
|
||||
[self assert:[1, 2, 3] equals:[arrayController1 arrangedObjects] message:"normal selection"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Test that bindings are not generation any change dictionary with Old or New values as a observer
|
||||
*/
|
||||
- (void)testCreationOfOldAndNeChangeDictionaryinObserverProtocol
|
||||
{
|
||||
[self initControllerWithContentBinding];
|
||||
|
||||
var arrayController = [self arrayController];
|
||||
|
||||
[self bind:@"nameValues" toObject:arrayController withKeyPath:@"arrangedObjects.name" options:nil];
|
||||
|
||||
countGetName = 0;
|
||||
|
||||
[self setItemsArray:[]];
|
||||
[self assert:0 equals:countGetName];
|
||||
|
||||
countGetName = 0;
|
||||
|
||||
[self setItemsArray:[self makeTestArray]];
|
||||
[self assert:3 equals:countGetName message:@"3 items in array and one '.name' bindings should make 3 accesses to the name property not " + countGetName + " times"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Test the speed of set an big array when the old was an empty.
|
||||
Also test the speed when an empty array is set and the old is an big
|
||||
*/
|
||||
- (void)testPerformance
|
||||
{
|
||||
[self initControllerWithContentBinding];
|
||||
|
||||
var arrayController = [self arrayController];
|
||||
[self bind:@"nameValues" toObject:arrayController withKeyPath:@"arrangedObjects.name" options:nil];
|
||||
[self bind:@"depValues" toObject:arrayController withKeyPath:@"arrangedObjects.department" options:nil];
|
||||
|
||||
[self setItemsArray:[]];
|
||||
var bigArray = [self makeTestArrayMany];
|
||||
|
||||
var d = new Date();
|
||||
[self setItemsArray:bigArray];
|
||||
var dd = new Date();
|
||||
[self setItemsArray:[]];
|
||||
var ddd = new Date();
|
||||
|
||||
CPLog.warn("\n" + [self className] + " Performance tests");
|
||||
CPLog.warn("EmptyArray -> BigArray (" + [bigArray count] + "): " + (dd - d) + "ms.");
|
||||
CPLog.warn("BigArray (" + [bigArray count] + ") -> EmptyArray: " + (ddd - dd) + "ms.");
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:keyPath
|
||||
ofObject:anActivity
|
||||
change:change
|
||||
@@ -1099,8 +1166,20 @@
|
||||
[_contentArray replaceObjectAtIndex:index withObject:anObject];
|
||||
}
|
||||
|
||||
- (void)setItemsArray:(CPArray)newValue
|
||||
{
|
||||
[self setValue:newValue forKey:@"contentArray"];
|
||||
}
|
||||
|
||||
- (CPArray)itemsArray
|
||||
{
|
||||
return [self valueForKey:@"contentArray"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var countGetName;
|
||||
|
||||
@implementation Employee : CPObject
|
||||
{
|
||||
CPString _name @accessors(property=name);
|
||||
@@ -1128,6 +1207,12 @@
|
||||
return [CPString stringWithFormat:@"<Employee %@>", [self name]];
|
||||
}
|
||||
|
||||
- (CPString)name
|
||||
{
|
||||
countGetName++;
|
||||
return _name;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation Department : CPObject
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
{
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:nil context:@"testAddObserver"];
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testAddObserver"];
|
||||
|
||||
[bob setValue:@"bob" forKey:@"name"];
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:nil context:@"testUnobservedKey"];
|
||||
[bob addObserver:self forKeyPath:@"name" options:0 context:@"testUnobservedKey"];
|
||||
|
||||
[bob setValue:@"555" forKey:@"phoneNumber"];
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
{
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:nil context:@"testAddTwoObservers"];
|
||||
[bob addObserver:[CPObject new] forKeyPath:@"name" options:nil context:@"testAddTwoObservers"];
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testAddTwoObservers"];
|
||||
[bob addObserver:[CPObject new] forKeyPath:@"name" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testAddTwoObservers"];
|
||||
|
||||
[bob setValue:@"bob" forKey:@"name"];
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
{
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"phoneNumber" options:nil context:@"testDirectIVarObservation"];
|
||||
[bob addObserver:self forKeyPath:@"phoneNumber" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testDirectIVarObservation"];
|
||||
|
||||
[bob setValue:@"555" forKey:@"phoneNumber"];
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
{
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:nil context:@"testRemoveObserver"];
|
||||
[bob addObserver:[CPString new] forKeyPath:@"name" options:nil context:@"testRemoveObserver"];
|
||||
[bob addObserver:self forKeyPath:@"name" options:0 context:@"testRemoveObserver"];
|
||||
[bob addObserver:[CPString new] forKeyPath:@"name" options:0 context:@"testRemoveObserver"];
|
||||
|
||||
[bob removeObserver:self forKeyPath:@"name"];
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
bob = [[PersonTester alloc] init];
|
||||
obj = [CPString new];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:nil context:@"testRemoveOtherObserver"];
|
||||
[bob addObserver:obj forKeyPath:@"name" options:nil context:@"testRemoveOtherObserver"];
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testRemoveOtherObserver"];
|
||||
[bob addObserver:obj forKeyPath:@"name" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testRemoveOtherObserver"];
|
||||
|
||||
[bob removeObserver:obj forKeyPath:@"name"];
|
||||
|
||||
@@ -113,8 +113,8 @@
|
||||
obj = [CPArray new];
|
||||
obj2 = [CPString new];
|
||||
|
||||
[bob addObserver:obj forKeyPath:@"name" options:nil context:@"testRemoveAllObservers"];
|
||||
[bob addObserver:obj2 forKeyPath:@"name" options:nil context:@"testRemoveAllObservers"];
|
||||
[bob addObserver:obj forKeyPath:@"name" options:0 context:@"testRemoveAllObservers"];
|
||||
[bob addObserver:obj2 forKeyPath:@"name" options:0 context:@"testRemoveAllObservers"];
|
||||
|
||||
[bob removeObserver:obj forKeyPath:@"name"];
|
||||
[bob removeObserver:obj2 forKeyPath:@"name"];
|
||||
@@ -133,7 +133,7 @@
|
||||
|
||||
[dict addObserver:self
|
||||
forKeyPath:@"dictionaryKey"
|
||||
options:nil
|
||||
options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld
|
||||
context:@"testDictionary"];
|
||||
|
||||
[dict setObject:@"Jo Bob Ray" forKey:@"dictionaryKey"];
|
||||
@@ -147,7 +147,20 @@
|
||||
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionPrior context:@"testPriorObservationOption"];
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionPrior | CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testPriorObservationOption"];
|
||||
|
||||
[bob setValue:@"bob" forKey:@"name"];
|
||||
|
||||
[self assertTrue:_sawPriorObservation message:@"asked for CPKeyValueObservingOptionPrior but did not recieve corresponding notification"];
|
||||
}
|
||||
|
||||
- (void)testPriorObservationOptionNoOld
|
||||
{
|
||||
_sawPriorObservation = NO;
|
||||
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionPrior | CPKeyValueObservingOptionNew context:@"testPriorObservationOptionNoOld"];
|
||||
|
||||
[bob setValue:@"bob" forKey:@"name"];
|
||||
|
||||
@@ -162,7 +175,23 @@
|
||||
|
||||
bob.name = "paul";
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionInitial context:@"testInitialObservationOption"];
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionInitial | CPKeyValueObservingOptionNew context:@"testInitialObservationOption"];
|
||||
[bob removeObserver:self forKeyPath:@"name"];
|
||||
|
||||
[bob setValue:@"bob" forKey:@"name"];
|
||||
|
||||
[self assertTrue:_sawInitialObservation message:@"asked for CPKeyValueObservingOptionInitial but did not recieve corresponding notification"];
|
||||
}
|
||||
|
||||
- (void)testInitialObservationOptionNoNew
|
||||
{
|
||||
_sawInitialObservation = NO;
|
||||
|
||||
bob = [[PersonTester alloc] init];
|
||||
|
||||
bob.name = "paul";
|
||||
|
||||
[bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionInitial context:@"testInitialObservationOptionNoNew"];
|
||||
[bob removeObserver:self forKeyPath:@"name"];
|
||||
|
||||
[bob setValue:@"bob" forKey:@"name"];
|
||||
@@ -178,8 +207,8 @@
|
||||
|
||||
bob.name = "paul";
|
||||
|
||||
[bob addObserver:self forKeyPath:@"bobName" options:0 context:@"testDependentKeyObservation"];
|
||||
[bob addObserver:self forKeyPath:@"twiceRemoved" options:0 context:@"testDependentKeyObservation2"];
|
||||
[bob addObserver:self forKeyPath:@"bobName" options:CPKeyValueObservingOptionOld context:@"testDependentKeyObservation"];
|
||||
[bob addObserver:self forKeyPath:@"twiceRemoved" options:CPKeyValueObservingOptionOld context:@"testDependentKeyObservation2"];
|
||||
|
||||
[bob setValue:@"bob" forKey:@"name"];
|
||||
|
||||
@@ -194,7 +223,7 @@
|
||||
|
||||
[cs101 setTeacher:bob];
|
||||
|
||||
[cs101 addObserver:self forKeyPath:@"teacher.name" options:0 context:@"testMultipartKey"];
|
||||
[cs101 addObserver:self forKeyPath:@"teacher.name" options:CPKeyValueObservingOptionNew context:@"testMultipartKey"];
|
||||
|
||||
[bob setName:@"bob"];
|
||||
|
||||
@@ -209,7 +238,7 @@
|
||||
|
||||
[bob setValue:focus forKey:@"car"];
|
||||
|
||||
[bob addObserver:self forKeyPath:@"self.car.thisCar.model" options:0 context:@"testMultiPartKeysWhereValuesEvaluateToSelf"];
|
||||
[bob addObserver:self forKeyPath:@"self.car.thisCar.model" options:CPKeyValueObservingOptionNew context:@"testMultiPartKeysWhereValuesEvaluateToSelf"];
|
||||
|
||||
[focus setValue:@"ford focus" forKey:@"model"];
|
||||
|
||||
@@ -225,7 +254,7 @@
|
||||
[cs101 setTeacher:bob];
|
||||
[bob setValue:focus forKey:@"car"];
|
||||
|
||||
[cs101 addObserver:self forKeyPath:@"teacher.car.model" options:0 context:@"testThreePartKey"];
|
||||
[cs101 addObserver:self forKeyPath:@"teacher.car.model" options:CPKeyValueObservingOptionNew context:@"testThreePartKey"];
|
||||
|
||||
[focus setValue:@"ford focus" forKey:@"model"];
|
||||
[self assertTrue: _sawObservation message:@"Never recieved an observation"];
|
||||
@@ -240,7 +269,7 @@
|
||||
[cs101 setTeacher:bob];
|
||||
[focus setValue:@"2000" forKey:@"year"];
|
||||
|
||||
[cs101 addObserver:self forKeyPath:@"teacher.car.year" options:0 context:@"testThreePartKeyPart2"];
|
||||
[cs101 addObserver:self forKeyPath:@"teacher.car.year" options:CPKeyValueObservingOptionNew context:@"testThreePartKeyPart2"];
|
||||
|
||||
[bob setValue:focus forKey:@"car"];
|
||||
|
||||
@@ -290,7 +319,7 @@
|
||||
[a setValue:[E new] forKeyPath:@"b.c.d.e"];
|
||||
[a setValue:[F new] forKeyPath:@"b.c.d.e.f"];
|
||||
|
||||
[a addObserver:self forKeyPath:@"b.c.d.e.f" options:0 context:@"testCrazyKeyPathChanges"];
|
||||
[a addObserver:self forKeyPath:@"b.c.d.e.f" options:CPKeyValueObservingOptionNew context:@"testCrazyKeyPathChanges"];
|
||||
|
||||
var newD = [D new];
|
||||
|
||||
@@ -311,7 +340,7 @@
|
||||
[a setValue:[E new] forKeyPath:@"b.c.d.e"];
|
||||
[a setValue:[F new] forKeyPath:@"b.c.d.e.f"];
|
||||
|
||||
[a addObserver:self forKeyPath:@"b.c.d.e.f" options:0 context:@"testCrazyKeyPathChanges2"];
|
||||
[a addObserver:self forKeyPath:@"b.c.d.e.f" options:CPKeyValueObservingOptionNew context:@"testCrazyKeyPathChanges2"];
|
||||
|
||||
[a setValue:nil forKeyPath:@"b.c"];
|
||||
|
||||
@@ -328,7 +357,7 @@
|
||||
[a setValue:[E new] forKeyPath:@"b.c.d.e"];
|
||||
[a setValue:[F new] forKeyPath:@"b.c.d.e.f"];
|
||||
|
||||
[a addObserver:self forKeyPath:@"b.c.d.e.f" options:0 context:@"testCrazyKeyPathChanges3"];
|
||||
[a addObserver:self forKeyPath:@"b.c.d.e.f" options:CPKeyValueObservingOptionNew context:@"testCrazyKeyPathChanges3"];
|
||||
|
||||
[a setValue:7 forKeyPath:@"b.c.d.e.f"];
|
||||
|
||||
@@ -341,7 +370,7 @@
|
||||
|
||||
[tester setValue:[1, 2, 3, 4] forKey:@"managedObjects"];
|
||||
|
||||
[tester addObserver:self forKeyPath:@"managedObjects" options:0 context:@"testInsertIntoToManyProperty"];
|
||||
[tester addObserver:self forKeyPath:@"managedObjects" options:CPKeyValueObservingOptionNew context:@"testInsertIntoToManyProperty"];
|
||||
|
||||
[tester insertObject:5 inManagedObjectsAtIndex:4];
|
||||
|
||||
@@ -354,7 +383,7 @@
|
||||
|
||||
[tester setValue:[1, 2, 3, 4] forKey:@"managedObjects"];
|
||||
|
||||
[tester addObserver:self forKeyPath:@"managedObjects" options:0 context:@"testRemoveFromToManyProperty"];
|
||||
[tester addObserver:self forKeyPath:@"managedObjects" options:CPKeyValueObservingOptionOld context:@"testRemoveFromToManyProperty"];
|
||||
|
||||
[tester removeObjectFromManagedObjectsAtIndex:0];
|
||||
|
||||
@@ -367,7 +396,7 @@
|
||||
|
||||
tester.tester = [ToManyTester new];
|
||||
[tester.tester setValue:[1, 2, 3, 4] forKey:@"managedObjects"];
|
||||
[tester addObserver:self forKeyPath:@"tester.managedObjects" options:0 context:@"testInsertIntoToManyPropertyIndirectly"];
|
||||
[tester addObserver:self forKeyPath:@"tester.managedObjects" options:CPKeyValueObservingOptionNew context:@"testInsertIntoToManyPropertyIndirectly"];
|
||||
|
||||
[tester.tester insertObject:5 inManagedObjectsAtIndex:4];
|
||||
|
||||
@@ -381,7 +410,7 @@
|
||||
tester.tester = [ToManyTester new];
|
||||
|
||||
[tester.tester setValue:[1, 2, 3, 4] forKey:@"subviews"];
|
||||
[tester addObserver:self forKeyPath:@"tester.subviews" options:0 context:@"testInsertIntoArrayPropertyIndirectly"];
|
||||
[tester addObserver:self forKeyPath:@"tester.subviews" options:CPKeyValueObservingOptionNew context:@"testInsertIntoArrayPropertyIndirectly"];
|
||||
|
||||
[tester.tester insertSubview:5 atIndex:4];
|
||||
|
||||
@@ -395,7 +424,7 @@
|
||||
tester.tester = [ToManyTester new];
|
||||
|
||||
[tester.tester setValue:[1, 2, 3, 4] forKey:@"subviews"];
|
||||
[tester addObserver:self forKeyPath:@"tester.subviews" options:0 context:@"testChangeTopLevelObject"];
|
||||
[tester addObserver:self forKeyPath:@"tester.subviews" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:@"testChangeTopLevelObject"];
|
||||
|
||||
var newTesterTester = [ToManyTester new];
|
||||
[newTesterTester setValue:[5, 6, 7, 8] forKey:@"subviews"];
|
||||
@@ -554,7 +583,24 @@
|
||||
if (!_sawPriorObservation)
|
||||
{
|
||||
[self assertTrue:prior message:@"Have not been sent the prior notification, but it should have been sent"];
|
||||
[self assertTrue:oldValue == [CPNull null] message:@"Shoudl be no initial value"];
|
||||
[self assertTrue:oldValue == [CPNull null] message:@"Should be no initial value"];
|
||||
[self assertFalse:newValue message:@"Should be no object for the new value key on the prior notification"];
|
||||
_sawPriorObservation = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self assertFalse:prior message:@"there should be no value for the notification is prior key, that notification was already sent"];
|
||||
[self assertTrue:newValue == "set_bob" message:@"newValue should be: set_bob was: " + newValue];
|
||||
}
|
||||
break;
|
||||
|
||||
case "testPriorObservationOptionNoOld":
|
||||
var prior = [changes objectForKey:CPKeyValueChangeNotificationIsPriorKey];
|
||||
|
||||
if (!_sawPriorObservation)
|
||||
{
|
||||
[self assertTrue:prior message:@"Have not been sent the prior notification, but it should have been sent"];
|
||||
[self assertTrue:oldValue == nil message:@"Should be no old value"];
|
||||
[self assertFalse:newValue message:@"Should be no object for the new value key on the prior notification"];
|
||||
_sawPriorObservation = YES;
|
||||
}
|
||||
@@ -568,8 +614,20 @@
|
||||
case "testInitialObservationOption":
|
||||
if (!_sawInitialObservation)
|
||||
{
|
||||
[self assertTrue:newValue == "paul" message:@"Expected old value to be: paul was: " + oldValue];
|
||||
[self assertFalse:oldValue message:@"Should be no value for new change key on initial observation"];
|
||||
[self assertTrue:newValue == "paul" message:@"Expected new value to be: paul was: " + newValue];
|
||||
[self assertTrue:oldValue == nil message:@"Should be no value for old change key on initial observation"];
|
||||
_sawInitialObservation = YES;
|
||||
}
|
||||
else
|
||||
[self assertFalse:YES message:@"Should never have received this notification"];
|
||||
|
||||
break;
|
||||
|
||||
case "testInitialObservationOptionNoNew":
|
||||
if (!_sawInitialObservation)
|
||||
{
|
||||
[self assertTrue:newValue == nil message:@"New value should not be included"];
|
||||
[self assertTrue:oldValue == nil message:@"Should be no value for old change key on initial observation"];
|
||||
_sawInitialObservation = YES;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@import <Foundation/CPKeyValueCoding.j>
|
||||
@import <Foundation/CPKeyValueObserving.j>
|
||||
|
||||
var _getCheeseCounter;
|
||||
|
||||
@implementation CPKeyValueObservingTest : OJTestCase
|
||||
{
|
||||
@@ -9,6 +10,12 @@
|
||||
id _lastObject;
|
||||
CPDictionary _lastChange;
|
||||
id _lastContext;
|
||||
|
||||
CPString _secondLastKeyPath;
|
||||
id _secondLastObject;
|
||||
CPDictionary _secondLastChange;
|
||||
id _secondLastContext;
|
||||
|
||||
}
|
||||
|
||||
- (Class)objectWithMethods:(CPString)aMethodName, ...
|
||||
@@ -26,9 +33,10 @@
|
||||
return [theClass new];
|
||||
}
|
||||
|
||||
- (void)setup
|
||||
- (void)setUp
|
||||
{
|
||||
_lastKeyPath = _lastObject = _lastChange = _lastContext = nil;
|
||||
_lastKeyPath = _lastObject = _lastChange = _lastContext = _secondLastKeyPath = _secondLastObject = _secondLastChange = _secondLastContext = nil;
|
||||
_getCheeseCounter = 0;
|
||||
}
|
||||
|
||||
- (void)testInitialObserving
|
||||
@@ -38,18 +46,210 @@
|
||||
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:[CPDictionary dictionaryWithObject:@"CHEESE!" forKey:CPKeyValueChangeNewKey] equals:_lastChange];
|
||||
[self assert:@{CPKeyValueChangeNewKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
}
|
||||
|
||||
- (void)testInitialObservingNoNew
|
||||
{
|
||||
var tester = [ObservingTester testerWithCheese:@"CHEESE!"];
|
||||
[tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionInitial context:nil];
|
||||
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
}
|
||||
|
||||
- (void)testSettingObservingPrior
|
||||
{
|
||||
var tester = [ObservingTester testerWithCheese:@"CHEESE!"];
|
||||
[tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionPrior context:nil];
|
||||
|
||||
[tester setValue:@"NEW CHEESE!" forKey:@"cheese"];
|
||||
|
||||
[self assert:@"cheese" equals:_secondLastKeyPath];
|
||||
[self assert:tester equals:_secondLastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting, CPKeyValueChangeNotificationIsPriorKey: 1} equals:_secondLastChange];
|
||||
[self assert:nil equals:_secondLastContext];
|
||||
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
[self assert:0 equals:_getCheeseCounter message:@"Get method should never be called"];
|
||||
}
|
||||
|
||||
- (void)testSettingObservingPriorPlusMoreObservers
|
||||
{
|
||||
var tester = [ObservingTester testerWithCheese:@"CHEESE!"];
|
||||
[tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionPrior context:nil];
|
||||
|
||||
var observerWithOld = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:observerWithOld forKeyPath:@"cheese" options:CPKeyValueObservingOptionPrior | CPKeyValueObservingOptionOld context:nil];
|
||||
|
||||
var observerWithNew = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:observerWithNew forKeyPath:@"cheese" options:CPKeyValueObservingOptionPrior | CPKeyValueObservingOptionNew context:nil];
|
||||
|
||||
var observerWithNewAndOld = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:observerWithNewAndOld forKeyPath:@"cheese" options:CPKeyValueObservingOptionPrior | CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:nil];
|
||||
|
||||
[tester setValue:@"NEW CHEESE!" forKey:@"cheese"];
|
||||
|
||||
[self assert:@"cheese" equals:_secondLastKeyPath];
|
||||
[self assert:tester equals:_secondLastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting, CPKeyValueChangeNotificationIsPriorKey: 1} equals:_secondLastChange];
|
||||
[self assert:nil equals:_secondLastContext];
|
||||
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:observerWithOld._secondLastKeyPath];
|
||||
[self assert:tester equals:observerWithOld._secondLastObject];
|
||||
[self assert:@{CPKeyValueChangeOldKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting, CPKeyValueChangeNotificationIsPriorKey: 1} equals:observerWithOld._secondLastChange];
|
||||
[self assert:nil equals:observerWithOld._secondLastContext];
|
||||
|
||||
[self assert:@"cheese" equals:observerWithOld._lastKeyPath];
|
||||
[self assert:tester equals:observerWithOld._lastObject];
|
||||
[self assert:@{CPKeyValueChangeOldKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:observerWithOld._lastChange];
|
||||
[self assert:nil equals:observerWithOld._lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:observerWithNew._secondLastKeyPath];
|
||||
[self assert:tester equals:observerWithNew._secondLastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting, CPKeyValueChangeNotificationIsPriorKey: 1} equals:observerWithNew._secondLastChange];
|
||||
[self assert:nil equals:observerWithNew._secondLastContext];
|
||||
|
||||
[self assert:@"cheese" equals:observerWithNew._lastKeyPath];
|
||||
[self assert:tester equals:observerWithNew._lastObject];
|
||||
[self assert:@{CPKeyValueChangeNewKey: @"NEW CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:observerWithNew._lastChange];
|
||||
[self assert:nil equals:observerWithNew._lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:observerWithNewAndOld._secondLastKeyPath];
|
||||
[self assert:tester equals:observerWithNewAndOld._secondLastObject];
|
||||
[self assert:@{CPKeyValueChangeOldKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting, CPKeyValueChangeNotificationIsPriorKey: 1} equals:observerWithNewAndOld._secondLastChange];
|
||||
[self assert:nil equals:observerWithNewAndOld._secondLastContext];
|
||||
|
||||
[self assert:@"cheese" equals:observerWithNewAndOld._lastKeyPath];
|
||||
[self assert:tester equals:observerWithNewAndOld._lastObject];
|
||||
[self assert:@{CPKeyValueChangeNewKey: @"NEW CHEESE!", CPKeyValueChangeOldKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:observerWithNewAndOld._lastChange];
|
||||
[self assert:nil equals:observerWithNewAndOld._lastContext];
|
||||
|
||||
[self assert:2 equals:_getCheeseCounter message:@"Get method should only be called twice, not " + _getCheeseCounter + " times"];
|
||||
}
|
||||
|
||||
- (void)testSettingObserving
|
||||
{
|
||||
var tester = [ObservingTester testerWithCheese:@"CHEESE!"];
|
||||
[tester addObserver:self forKeyPath:@"cheese" options:0 context:nil];
|
||||
|
||||
[tester setValue:@"NEW CHEESE!" forKey:@"cheese"];
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
[self assert:0 equals:_getCheeseCounter message:@"Get method should never be called"];
|
||||
}
|
||||
|
||||
- (void)testSettingObservingNewPlusAnotherObserverWithNoNew
|
||||
{
|
||||
var tester = [ObservingTester testerWithCheese:@"CHEESE!"];
|
||||
[tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionNew context:nil];
|
||||
|
||||
var anotherObserver = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:anotherObserver forKeyPath:@"cheese" options:0 context:nil];
|
||||
|
||||
[tester setValue:@"NEW CHEESE!" forKey:@"cheese"];
|
||||
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:@{CPKeyValueChangeNewKey: @"NEW CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:anotherObserver._lastKeyPath];
|
||||
[self assert:tester equals:anotherObserver._lastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:anotherObserver._lastChange];
|
||||
[self assert:nil equals:anotherObserver._lastContext];
|
||||
|
||||
[self assert:1 equals:_getCheeseCounter message:@"Get method should only be called once, not " + _getCheeseCounter + " times"];
|
||||
}
|
||||
|
||||
- (void)testSettingObservingOldPlusAnotherObserverWithNoOld
|
||||
{
|
||||
var tester = [ObservingTester testerWithCheese:@"CHEESE!"];
|
||||
[tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionOld context:nil];
|
||||
|
||||
var anotherObserver = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:anotherObserver forKeyPath:@"cheese" options:0 context:nil];
|
||||
|
||||
[tester setValue:@"NEW CHEESE!" forKey:@"cheese"];
|
||||
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:@{CPKeyValueChangeOldKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:anotherObserver._lastKeyPath];
|
||||
[self assert:tester equals:anotherObserver._lastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:anotherObserver._lastChange];
|
||||
[self assert:nil equals:anotherObserver._lastContext];
|
||||
|
||||
[self assert:1 equals:_getCheeseCounter message:@"Get method should only be called once, not " + _getCheeseCounter + " times"];
|
||||
}
|
||||
|
||||
- (void)testSettingObservingNewAndOldPlusMoreObserversWithOnlyNewOrOld
|
||||
{
|
||||
var tester = [ObservingTester testerWithCheese:@"CHEESE!"];
|
||||
[tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:nil];
|
||||
|
||||
var anotherObserver = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:anotherObserver forKeyPath:@"cheese" options:CPKeyValueObservingOptionOld context:nil];
|
||||
|
||||
var yetAnotherObserver = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:yetAnotherObserver forKeyPath:@"cheese" options:CPKeyValueObservingOptionNew context:nil];
|
||||
|
||||
var noNewOrOldObserver = [[AnotherObserver alloc] init];
|
||||
[tester addObserver:noNewOrOldObserver forKeyPath:@"cheese" options:0 context:nil];
|
||||
|
||||
[tester setValue:@"NEW CHEESE!" forKey:@"cheese"];
|
||||
|
||||
[self assert:@"cheese" equals:_lastKeyPath];
|
||||
[self assert:tester equals:_lastObject];
|
||||
[self assert:@{CPKeyValueChangeNewKey: @"NEW CHEESE!", CPKeyValueChangeOldKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:_lastChange];
|
||||
[self assert:nil equals:_lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:anotherObserver._lastKeyPath];
|
||||
[self assert:tester equals:anotherObserver._lastObject];
|
||||
[self assert:@{CPKeyValueChangeOldKey: @"CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:anotherObserver._lastChange];
|
||||
[self assert:nil equals:anotherObserver._lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:yetAnotherObserver._lastKeyPath];
|
||||
[self assert:tester equals:yetAnotherObserver._lastObject];
|
||||
[self assert:@{CPKeyValueChangeNewKey: @"NEW CHEESE!", CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:yetAnotherObserver._lastChange];
|
||||
[self assert:nil equals:yetAnotherObserver._lastContext];
|
||||
|
||||
[self assert:@"cheese" equals:noNewOrOldObserver._lastKeyPath];
|
||||
[self assert:tester equals:noNewOrOldObserver._lastObject];
|
||||
[self assert:@{CPKeyValueChangeKindKey: CPKeyValueChangeSetting} equals:noNewOrOldObserver._lastChange];
|
||||
[self assert:nil equals:noNewOrOldObserver._lastContext];
|
||||
|
||||
[self assert:2 equals:_getCheeseCounter message:@"Get method should only be called twice even with many observers, not " + _getCheeseCounter + " times"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath
|
||||
ofObject:(id)anObject
|
||||
change:(CPDictionary)aChange
|
||||
context:(id)aContext
|
||||
{
|
||||
_secondLastKeyPath = _lastKeyPath;
|
||||
_secondLastObject = _lastObject;
|
||||
_secondLastChange = _lastChange;
|
||||
_secondLastContext = _lastContext;
|
||||
|
||||
_lastKeyPath = aKeyPath;
|
||||
_lastObject = anObject;
|
||||
_lastChange = aChange;
|
||||
_lastChange = [aChange copy];
|
||||
_lastContext = aContext;
|
||||
}
|
||||
|
||||
@@ -244,6 +444,7 @@
|
||||
|
||||
- (id)cheese
|
||||
{
|
||||
_getCheeseCounter++;
|
||||
return cheese;
|
||||
}
|
||||
|
||||
@@ -280,3 +481,34 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AnotherObserver : CPObject
|
||||
{
|
||||
CPString _lastKeyPath;
|
||||
id _lastObject;
|
||||
CPDictionary _lastChange;
|
||||
id _lastContext;
|
||||
|
||||
CPString _secondLastKeyPath;
|
||||
id _secondLastObject;
|
||||
CPDictionary _secondLastChange;
|
||||
id _secondLastContext;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath
|
||||
ofObject:(id)anObject
|
||||
change:(CPDictionary)aChange
|
||||
context:(id)aContext
|
||||
{
|
||||
_secondLastKeyPath = _lastKeyPath;
|
||||
_secondLastObject = _lastObject;
|
||||
_secondLastChange = _lastChange;
|
||||
_secondLastContext = _lastContext;
|
||||
|
||||
_lastKeyPath = aKeyPath;
|
||||
_lastObject = anObject;
|
||||
_lastChange = [aChange copy];
|
||||
_lastContext = aContext;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user