mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user