Merge pull request #1616 from slevenbits/kvo-willchange-fix-2

Fix crash if an observer is set between willChange…/didChange…
This commit is contained in:
Alexander Ljungberg
2012-07-14 13:58:18 +01:00
2 changed files with 140 additions and 1 deletions
+95 -1
View File
@@ -31,26 +31,107 @@
- (void)willChangeValueForKey:(CPString)aKey
{
if (!aKey)
return;
if (!self[KVOProxyKey])
{
if (!self._willChangeMessageCounter)
self._willChangeMessageCounter = new Object();
if (!self._willChangeMessageCounter[aKey])
self._willChangeMessageCounter[aKey] = 1;
else
self._willChangeMessageCounter[aKey] += 1;
}
}
- (void)didChangeValueForKey:(CPString)aKey
{
if (!aKey)
return;
if (!self[KVOProxyKey])
{
if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
{
self._willChangeMessageCounter[aKey] -= 1;
if (!self._willChangeMessageCounter[aKey])
delete self._willChangeMessageCounter[aKey];
}
else
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
}
}
- (void)willChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
{
if (!aKey)
return;
if (!self[KVOProxyKey])
{
if (!self._willChangeMessageCounter)
self._willChangeMessageCounter = new Object();
if (!self._willChangeMessageCounter[aKey])
self._willChangeMessageCounter[aKey] = 1;
else
self._willChangeMessageCounter[aKey] += 1;
}
}
- (void)didChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
{
if (!aKey)
return;
if (!self[KVOProxyKey])
{
if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
{
self._willChangeMessageCounter[aKey] -= 1;
if (!self._willChangeMessageCounter[aKey])
delete self._willChangeMessageCounter[aKey];
}
else
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
}
}
- (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
{
if (!aKey)
return;
if (!self[KVOProxyKey])
{
if (!self._willChangeMessageCounter)
self._willChangeMessageCounter = new Object();
if (!self._willChangeMessageCounter[aKey])
self._willChangeMessageCounter[aKey] = 1;
else
self._willChangeMessageCounter[aKey] += 1;
}
}
- (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
{
if (!self[KVOProxyKey])
{
if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
{
self._willChangeMessageCounter[aKey] -= 1;
if (!self._willChangeMessageCounter[aKey])
delete self._willChangeMessageCounter[aKey];
}
else
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
}
}
- (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
@@ -804,7 +885,20 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
{
var level = _nestingForKey[aKey];
if (!changes || !level)
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
{
if (_targetObject._willChangeMessageCounter && _targetObject._willChangeMessageCounter[aKey])
{
// Close unobserved willChange for a given key.
_targetObject._willChangeMessageCounter[aKey] -= 1;
if (!_targetObject._willChangeMessageCounter[aKey])
delete _targetObject._willChangeMessageCounter[aKey];
return;
}
else
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
}
_nestingForKey[aKey] = level - 1;
if (level - 1 > 0)
+45
View File
@@ -14,11 +14,14 @@
id focus;
CPInteger observationCount;
CPInteger testNestedNotificationsBobCount;
}
- (void)setUp
{
_sawObservation = NO;
testNestedNotificationsBobCount = 0;
}
- (void)testAddObserver
@@ -465,6 +468,44 @@
[self assertTrue:newImp === oldImp];
}
- (void)testNestedNotifications
{
var bob = [[PersonTester alloc] init];
[bob willChangeValueForKey:@"name"];
[self assertTrue:bob._willChangeMessageCounter[@"name"] === 1];
[bob didChangeValueForKey:@"name"];
[self assertTrue:!bob._willChangeMessageCounter[@"name"]];
[bob willChangeValueForKey:@"name"];
[self assertTrue:bob._willChangeMessageCounter[@"name"] === 1];
[bob willChangeValueForKey:@"phoneNumber"]
[self assertTrue:bob._willChangeMessageCounter[@"phoneNumber"] === 1];
[bob didChangeValueForKey:@"phoneNumber"];
[self assertTrue:!bob._willChangeMessageCounter[@"phoneNumber"]];
[bob didChangeValueForKey:@"name"];
[self assertTrue:!bob._willChangeMessageCounter[@"name"]];
[bob willChangeValueForKey:@"name"];
[self assertTrue:bob._willChangeMessageCounter[@"name"] === 1];
[bob addObserver:self forKeyPath:@"name" options:nil context:@"testNestedNotifications"];
[bob addObserver:self forKeyPath:@"phoneNumber" options:nil context:@"testNestedNotifications"];
[bob willChangeValueForKey:@"phoneNumber"];
[self assertTrue:!bob._willChangeMessageCounter[@"phoneNumber"]];
[bob didChangeValueForKey:@"phoneNumber"];
[self assertTrue:!bob._willChangeMessageCounter[@"phoneNumber"]];
[self assertTrue:testNestedNotificationsBobCount === 1];
[bob didChangeValueForKey:@"name"];
[self assertTrue:!bob._willChangeMessageCounter[@"name"]];
[bob willChangeValueForKey:@"name"];
[self assertTrue:!bob._willChangeMessageCounter[@"name"]];
[bob didChangeValueForKey:@"name"];
[self assertTrue:!bob._willChangeMessageCounter[@"name"]];
[self assertTrue:testNestedNotificationsBobCount === 2];
}
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
{
var oldValue = [changes objectForKey:CPKeyValueChangeOldKey],
@@ -669,6 +710,10 @@
[self assert:newValue equals:@"Jo Bob Ray"];
break;
case "testNestedNotifications":
testNestedNotificationsBobCount += 1;
break;
default:
[self assertFalse:YES message:@"unhandled observation, must be an error"];
return;