Files
cappuccino/Foundation/CPKeyValueObserving.j
T

1112 lines
37 KiB
Plaintext

/*
* CPKeyValueCoding.j
* Foundation
*
* Created by Ross Boucher.
* Copyright 2008, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPArray.j"
@import "CPDictionary.j"
@import "CPException.j"
@import "CPNull.j"
@import "CPObject.j"
@import "CPSet.j"
@implementation CPObject (KeyValueObserving)
- (void)willChangeValueForKey:(CPString)aKey
{
}
- (void)didChangeValueForKey:(CPString)aKey
{
}
- (void)willChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
{
}
- (void)didChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
{
}
- (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
{
}
- (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
{
}
- (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
{
if (!anObserver || !aPath)
return;
[[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext];
}
- (void)removeObserver:(id)anObserver forKeyPath:(CPString)aPath
{
if (!anObserver || !aPath)
return;
[self[KVOProxyKey] _removeObserver:anObserver forKeyPath:aPath];
}
+ (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
{
return YES;
}
+ (CPSet)keyPathsForValuesAffectingValueForKey:(CPString)aKey
{
var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1),
selector = "keyPathsForValuesAffecting" + capitalizedKey;
if ([[self class] respondsToSelector:selector])
return objj_msgSend([self class], selector);
return [CPSet set];
}
- (void)applyChange:(CPDictionary)aChange toKeyPath:(CPString)aKeyPath
{
var changeKind = [aChange objectForKey:CPKeyValueChangeKindKey],
oldValue = [aChange objectForKey:CPKeyValueChangeOldKey],
newValue = [aChange objectForKey:CPKeyValueChangeNewKey];
if (newValue === [CPNull null])
newValue = nil;
if (changeKind === CPKeyValueChangeSetting)
return [self setValue:newValue forKeyPath:aKeyPath];
var indexes = [aChange objectForKey:CPKeyValueChangeIndexesKey];
// If we have an indexes entry, then we have an ordered to-many relationship
if (indexes)
{
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 mutableSetValueForKeyPath:aKeyPath] unionSet:newValue];
else if (changeKind === CPKeyValueChangeRemoval)
[[self mutableSetValueForKeyPath:aKeyPath] minusSet:oldValue];
else if (changeKind === CPKeyValueChangeReplacement)
[[self mutableSetValueForKeyPath:aKeyPath] setSet:newValue];
}
}
@end
@implementation CPDictionary (KeyValueObserving)
- (CPDictionary)inverseChangeDictionary
{
var inverseChangeDictionary = [self mutableCopy],
changeKind = [self objectForKey:CPKeyValueChangeKindKey];
if (changeKind === CPKeyValueChangeSetting || changeKind === CPKeyValueChangeReplacement)
{
[inverseChangeDictionary
setObject:[self objectForKey:CPKeyValueChangeOldKey]
forKey:CPKeyValueChangeNewKey];
[inverseChangeDictionary
setObject:[self objectForKey:CPKeyValueChangeNewKey]
forKey:CPKeyValueChangeOldKey];
}
else if (changeKind === CPKeyValueChangeInsertion)
{
[inverseChangeDictionary
setObject:CPKeyValueChangeRemoval
forKey:CPKeyValueChangeKindKey];
[inverseChangeDictionary
setObject:[self objectForKey:CPKeyValueChangeNewKey]
forKey:CPKeyValueChangeOldKey];
[inverseChangeDictionary removeObjectForKey:CPKeyValueChangeNewKey];
}
else if (changeKind === CPKeyValueChangeRemoval)
{
[inverseChangeDictionary
setObject:CPKeyValueChangeInsertion
forKey:CPKeyValueChangeKindKey];
[inverseChangeDictionary
setObject:[self objectForKey:CPKeyValueChangeOldKey]
forKey:CPKeyValueChangeNewKey];
[inverseChangeDictionary removeObjectForKey:CPKeyValueChangeOldKey];
}
return inverseChangeDictionary;
}
@end
// KVO Options
CPKeyValueObservingOptionNew = 1 << 0;
CPKeyValueObservingOptionOld = 1 << 1;
CPKeyValueObservingOptionInitial = 1 << 2;
CPKeyValueObservingOptionPrior = 1 << 3;
// KVO Change Dictionary Keys
CPKeyValueChangeKindKey = @"CPKeyValueChangeKindKey";
CPKeyValueChangeNewKey = @"CPKeyValueChangeNewKey";
CPKeyValueChangeOldKey = @"CPKeyValueChangeOldKey";
CPKeyValueChangeIndexesKey = @"CPKeyValueChangeIndexesKey";
CPKeyValueChangeNotificationIsPriorKey = @"CPKeyValueChangeNotificationIsPriorKey";
// KVO Change Types
CPKeyValueChangeSetting = 1;
CPKeyValueChangeInsertion = 2;
CPKeyValueChangeRemoval = 3;
CPKeyValueChangeReplacement = 4;
// CPKeyValueSetMutationKind
CPKeyValueUnionSetMutation = 1;
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";
//rule of thumb: _ methods are called on the real proxy object, others are called on the "fake" proxy object (aka the real object)
/* @ignore */
@implementation _CPKVOProxy : CPObject
{
id _targetObject;
Class _nativeClass;
CPDictionary _changesForKey;
Object _observersForKey;
int _observersForKeyLength;
CPSet _replacedKeys;
}
+ (id)proxyForObject:(CPObject)anObject
{
var proxy = anObject[KVOProxyKey];
if (proxy)
return proxy;
return [[self alloc] initWithTarget:anObject];
}
- (id)initWithTarget:(id)aTarget
{
if (self = [super init])
{
_targetObject = aTarget;
_nativeClass = [aTarget class];
_observersForKey = {};
_changesForKey = {};
_observersForKeyLength = 0;
[self _replaceClass];
aTarget[KVOProxyKey] = self;
}
return self;
}
- (void)_replaceClass
{
var currentClass = _nativeClass,
kvoClassName = "$KVO_" + class_getName(_nativeClass),
existingKVOClass = objj_lookUpClass(kvoClassName);
if (existingKVOClass)
{
_targetObject.isa = existingKVOClass;
_replacedKeys = existingKVOClass._replacedKeys;
return;
}
var kvoClass = objj_allocateClassPair(currentClass, kvoClassName);
objj_registerClassPair(kvoClass);
_replacedKeys = [CPSet set];
kvoClass._replacedKeys = _replacedKeys;
//copy in the methods from our model subclass
var methods = class_copyMethodList(_CPKVOModelSubclass);
if ([_targetObject isKindOfClass:[CPDictionary class]])
methods = methods.concat(class_copyMethodList(_CPKVOModelDictionarySubclass));
class_addMethods(kvoClass, methods);
_targetObject.isa = kvoClass;
}
- (void)_replaceModifiersForKey:(CPString)aKey
{
if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey])
return;
[_replacedKeys addObject:aKey];
var theClass = _nativeClass,
KVOClass = _targetObject.isa,
capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1);
// Attribute and To-One Relationships
var setKey_selector = sel_getUid("set" + capitalizedKey + ":"),
setKey_method = class_getInstanceMethod(theClass, setKey_selector);
if (setKey_method)
{
var setKey_method_imp = setKey_method.method_imp;
class_addMethod(KVOClass, setKey_selector, function(self, _cmd, anObject)
{
[self willChangeValueForKey:aKey];
setKey_method_imp(self, _cmd, anObject);
[self didChangeValueForKey:aKey];
}, "");
}
// FIXME: Deprecated.
var _setKey_selector = sel_getUid("_set" + capitalizedKey + ":"),
_setKey_method = class_getInstanceMethod(theClass, _setKey_selector);
if (_setKey_method)
{
var _setKey_method_imp = _setKey_method.method_imp;
class_addMethod(KVOClass, _setKey_selector, function(self, _cmd, anObject)
{
[self willChangeValueForKey:aKey];
_setKey_method_imp(self, _cmd, anObject);
[self didChangeValueForKey:aKey];
}, "");
}
// Ordered To-Many Relationships
var insertObject_inKeyAtIndex_selector = sel_getUid("insertObject:in" + capitalizedKey + "AtIndex:"),
insertObject_inKeyAtIndex_method =
class_getInstanceMethod(theClass, insertObject_inKeyAtIndex_selector),
insertKey_atIndexes_selector = sel_getUid("insert" + capitalizedKey + ":atIndexes:"),
insertKey_atIndexes_method =
class_getInstanceMethod(theClass, insertKey_atIndexes_selector),
removeObjectFromKeyAtIndex_selector = sel_getUid("removeObjectFrom" + capitalizedKey + "AtIndex:"),
removeObjectFromKeyAtIndex_method =
class_getInstanceMethod(theClass, removeObjectFromKeyAtIndex_selector),
removeKeyAtIndexes_selector = sel_getUid("remove" + capitalizedKey + "AtIndexes:"),
removeKeyAtIndexes_method = class_getInstanceMethod(theClass, removeKeyAtIndexes_selector);
if ((insertObject_inKeyAtIndex_method || insertKey_atIndexes_method) &&
(removeObjectFromKeyAtIndex_method || removeKeyAtIndexes_method))
{
if (insertObject_inKeyAtIndex_method)
{
var insertObject_inKeyAtIndex_method_imp = insertObject_inKeyAtIndex_method.method_imp;
class_addMethod(KVOClass, insertObject_inKeyAtIndex_selector, function(self, _cmd, anObject, anIndex)
{
[self willChange:CPKeyValueChangeInsertion
valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
forKey:aKey];
insertObject_inKeyAtIndex_method_imp(self, _cmd, anObject, anIndex);
[self didChange:CPKeyValueChangeInsertion
valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
forKey:aKey];
}, "");
}
if (insertKey_atIndexes_method)
{
var insertKey_atIndexes_method_imp = insertKey_atIndexes_method.method_imp;
class_addMethod(KVOClass, insertKey_atIndexes_selector, function(self, _cmd, objects, indexes)
{
[self willChange:CPKeyValueChangeInsertion
valuesAtIndexes:[indexes copy]
forKey:aKey];
insertKey_atIndexes_method_imp(self, _cmd, objects, indexes);
[self didChange:CPKeyValueChangeInsertion
valuesAtIndexes:[indexes copy]
forKey:aKey];
}, "");
}
if (removeObjectFromKeyAtIndex_method)
{
var removeObjectFromKeyAtIndex_method_imp = removeObjectFromKeyAtIndex_method.method_imp;
class_addMethod(KVOClass, removeObjectFromKeyAtIndex_selector, function(self, _cmd, anIndex)
{
[self willChange:CPKeyValueChangeRemoval
valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
forKey:aKey];
removeObjectFromKeyAtIndex_method_imp(self, _cmd, anIndex);
[self didChange:CPKeyValueChangeRemoval
valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
forKey:aKey];
}, "");
}
if (removeKeyAtIndexes_method)
{
var removeKeyAtIndexes_method_imp = removeKeyAtIndexes_method.method_imp;
class_addMethod(KVOClass, removeKeyAtIndexes_selector, function(self, _cmd, indexes)
{
[self willChange:CPKeyValueChangeRemoval
valuesAtIndexes:[indexes copy]
forKey:aKey];
removeKeyAtIndexes_method_imp(self, _cmd, indexes);
[self didChange:CPKeyValueChangeRemoval
valuesAtIndexes:[indexes copy]
forKey:aKey];
}, "");
}
// These are optional.
var replaceObjectInKeyAtIndex_withObject_selector =
sel_getUid("replaceObjectIn" + capitalizedKey + "AtIndex:withObject:"),
replaceObjectInKeyAtIndex_withObject_method =
class_getInstanceMethod(theClass, replaceObjectInKeyAtIndex_withObject_selector);
if (replaceObjectInKeyAtIndex_withObject_method)
{
var replaceObjectInKeyAtIndex_withObject_method_imp =
replaceObjectInKeyAtIndex_withObject_method.method_imp;
class_addMethod(KVOClass, replaceObjectInKeyAtIndex_withObject_selector,
function(self, _cmd, anIndex, anObject)
{
[self willChange:CPKeyValueChangeReplacement
valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
forKey:aKey];
replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, anIndex, anObject);
[self didChange:CPKeyValueChangeReplacement
valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
forKey:aKey];
}, "");
}
var replaceKeyAtIndexes_withKey_selector =
sel_getUid("replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":"),
replaceKeyAtIndexes_withKey_method =
class_getInstanceMethod(theClass, replaceKeyAtIndexes_withKey_selector);
if (replaceKeyAtIndexes_withKey_method)
{
var replaceKeyAtIndexes_withKey_method_imp = replaceKeyAtIndexes_withKey_method.method_imp;
class_addMethod(KVOClass, replaceKeyAtIndexes_withKey_selector, function(self, _cmd, indexes, objects)
{
[self willChange:CPKeyValueChangeReplacement
valuesAtIndexes:[indexes copy]
forKey:aKey];
replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, indexes, objects);
[self didChange:CPKeyValueChangeReplacement
valuesAtIndexes:[indexes copy]
forKey:aKey];
}, "");
}
}
// Unordered To-Many Relationships
var addKeyObject_selector = sel_getUid("add" + capitalizedKey + "Object:"),
addKeyObject_method = class_getInstanceMethod(theClass, addKeyObject_selector),
addKey_selector = sel_getUid("add" + capitalizedKey + ":"),
addKey_method = class_getInstanceMethod(theClass, addKey_selector),
removeKeyObject_selector = sel_getUid("remove" + capitalizedKey + "Object:"),
removeKeyObject_method = class_getInstanceMethod(theClass, removeKeyObject_selector),
removeKey_selector = sel_getUid("remove" + capitalizedKey + ":"),
removeKey_method = class_getInstanceMethod(theClass, removeKey_selector);
if ((addKeyObject_method || addKey_method) && (removeKeyObject_method || removeKey_method))
{
if (addKeyObject_method)
{
var addKeyObject_method_imp = addKeyObject_method.method_imp;
class_addMethod(KVOClass, addKeyObject_selector, function(self, _cmd, anObject)
{
[self willChangeValueForKey:aKey
withSetMutation:CPKeyValueUnionSetMutation
usingObjects:[CPSet setWithObject:anObject]];
addKeyObject_method_imp(self, _cmd, anObject);
[self didChangeValueForKey:aKey
withSetMutation:CPKeyValueUnionSetMutation
usingObjects:[CPSet setWithObject:anObject]];
}, "");
}
if (addKey_method)
{
var addKey_method_imp = addKey_method.method_imp;
class_addMethod(KVOClass, addKey_selector, function(self, _cmd, objects)
{
[self willChangeValueForKey:aKey
withSetMutation:CPKeyValueUnionSetMutation
usingObjects:[objects copy]];
addKey_method_imp(self, _cmd, objects);
[self didChangeValueForKey:aKey
withSetMutation:CPKeyValueUnionSetMutation
usingObjects:[objects copy]];
}, "");
}
if (removeKeyObject_method)
{
var removeKeyObject_method_imp = removeKeyObject_method.method_imp;
class_addMethod(KVOClass, removeKeyObject_selector, function(self, _cmd, anObject)
{
[self willChangeValueForKey:aKey
withSetMutation:CPKeyValueMinusSetMutation
usingObjects:[CPSet setWithObject:anObject]];
removeKeyObject_method_imp(self, _cmd, anObject);
[self didChangeValueForKey:aKey
withSetMutation:CPKeyValueMinusSetMutation
usingObjects:[CPSet setWithObject:anObject]];
}, "");
}
if (removeKey_method)
{
var removeKey_method_imp = removeKey_method.method_imp;
class_addMethod(KVOClass, removeKey_selector, function(self, _cmd, objects)
{
[self willChangeValueForKey:aKey
withSetMutation:CPKeyValueMinusSetMutation
usingObjects:[objects copy]];
removeKey_method_imp(self, _cmd, objects);
[self didChangeValueForKey:aKey
withSetMutation:CPKeyValueMinusSetMutation
usingObjects:[objects copy]];
}, "");
}
// intersect<Key>: is optional.
var intersectKey_selector = sel_getUid("intersect" + capitalizedKey + ":"),
intersectKey_method = class_getInstanceMethod(theClass, intersectKey_selector);
if (intersectKey_method)
{
var intersectKey_method_imp = intersectKey_method.method_imp;
class_addMethod(KVOClass, intersectKey_selector, function(self, _cmd, aSet)
{
[self willChangeValueForKey:aKey
withSetMutation:CPKeyValueIntersectSetMutation
usingObjects:[aSet copy]];
intersectKey_method_imp(self, _cmd, aSet);
[self didChangeValueForKey:aKey
withSetMutation:CPKeyValueIntersectSetMutation
usingObjects:[aSet copy]];
}, "");
}
}
var affectingKeys = [[_nativeClass keyPathsForValuesAffectingValueForKey:aKey] allObjects],
affectingKeysCount = affectingKeys ? affectingKeys.length : 0;
if (!affectingKeysCount)
return;
var dependentKeysForClass = _nativeClass[DependentKeysKey];
if (!dependentKeysForClass)
{
dependentKeysForClass = {};
_nativeClass[DependentKeysKey] = dependentKeysForClass;
}
while (affectingKeysCount--)
{
var affectingKey = affectingKeys[affectingKeysCount],
affectedKeys = dependentKeysForClass[affectingKey];
if (!affectedKeys)
{
affectedKeys = [CPSet new];
dependentKeysForClass[affectingKey] = affectedKeys;
}
[affectedKeys addObject:aKey];
//observe key paths of objects other then ourselves, so we are notified of the changes
//use CPKeyValueObservingOptionPrior to ensure proper wrapping around changes
//so CPKeyValueObservingOptionPrior and CPKeyValueObservingOptionOld can be fulfilled even for dependent keys
if (affectingKey.indexOf(@".") !== -1)
[_targetObject addObserver:self forKeyPath:affectingKey options:CPKeyValueObservingOptionPrior | kvoNewAndOld context:nil];
else
[self _replaceModifiersForKey:affectingKey];
}
}
- (void)observeValueForKeyPath:(CPString)theKeyPath ofObject:(id)theObject change:(CPDictionary)theChanges context:(id)theContext
{
// Fire change events for the dependent keys
var dependentKeysForClass = _nativeClass[DependentKeysKey],
dependantKeys = [dependentKeysForClass[theKeyPath] allObjects];
var isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
for (var i = 0; i < [dependantKeys count]; i++)
{
var dependantKey = [dependantKeys objectAtIndex:i];
[self _sendNotificationsForKey:dependantKey changeOptions:theChanges isBefore:isBeforeFlag];
}
}
- (void)_addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
{
if (!anObserver)
return;
var forwarder = nil;
if (aPath.indexOf('.') != CPNotFound)
forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
else
[self _replaceModifiersForKey:aPath];
var observers = _observersForKey[aPath];
if (!observers)
{
observers = [CPDictionary dictionary];
_observersForKey[aPath] = observers;
_observersForKeyLength++;
}
[observers setObject:_CPKVOInfoMake(anObserver, options, aContext, forwarder) forKey:[anObserver UID]];
if (options & CPKeyValueObservingOptionInitial)
{
var newValue = [_targetObject valueForKeyPath:aPath];
if (newValue === nil || newValue === undefined)
newValue = [CPNull null];
var changes = [CPDictionary dictionaryWithObject:newValue forKey:CPKeyValueChangeNewKey];
[anObserver observeValueForKeyPath:aPath ofObject:_targetObject change:changes context:aContext];
}
}
- (void)_removeObserver:(id)anObserver forKeyPath:(CPString)aPath
{
var observers = _observersForKey[aPath];
if (aPath.indexOf('.') != CPNotFound)
{
var forwarder = [observers objectForKey:[anObserver UID]].forwarder;
[forwarder finalize];
}
[observers removeObjectForKey:[anObserver UID]];
if (![observers count])
{
_observersForKeyLength--;
delete _observersForKey[aPath];
}
if (!_observersForKeyLength)
{
_targetObject.isa = _nativeClass; //restore the original class
delete _targetObject[KVOProxyKey];
}
}
//FIXME: We do not compute and cache if CPKeyValueObservingOptionOld is needed, so we may do unnecessary work
- (void)_sendNotificationsForKey:(CPString)aKey changeOptions:(CPDictionary)changeOptions isBefore:(BOOL)isBefore
{
var changes = _changesForKey[aKey];
if (isBefore)
{
changes = changeOptions;
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
if (setMutationKind)
{
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 (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;
}
else
{
if (!changes)
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
[changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
if (setMutationKind)
{
//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];
}
}
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];
}
var dependentKeysMap = _nativeClass[DependentKeysKey];
if (!dependentKeysMap)
return;
var dependentKeyPaths = [dependentKeysMap[aKey] allObjects];
if (!dependentKeyPaths)
return;
var index = 0,
count = [dependentKeyPaths count];
for (; index < count; ++index)
{
var keyPath = dependentKeyPaths[index];
[self _sendNotificationsForKey:keyPath
changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
isBefore:isBefore];
}
}
@end
@implementation _CPKVOModelSubclass
{
}
- (void)willChangeValueForKey:(CPString)aKey
{
var superClass = [self class],
methodSelector = @selector(willChangeValueForKey:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, aKey);
if (!aKey)
return;
var changeOptions = [CPDictionary dictionaryWithObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey];
[[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
}
- (void)didChangeValueForKey:(CPString)aKey
{
var superClass = [self class],
methodSelector = @selector(didChangeValueForKey:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, aKey);
if (!aKey)
return;
[[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
}
- (void)willChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
{
var superClass = [self class],
methodSelector = @selector(willChange:valuesAtIndexes:forKey:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, change, indexes, aKey);
if (!aKey)
return;
var changeOptions = [CPDictionary dictionaryWithObjects:[change, indexes] forKeys:[CPKeyValueChangeKindKey, CPKeyValueChangeIndexesKey]];
[[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
}
- (void)didChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
{
var superClass = [self class],
methodSelector = @selector(didChange:valuesAtIndexes:forKey:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, change, indexes, aKey);
if (!aKey)
return;
[[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
}
- (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
{
var superClass = [self class],
methodSelector = @selector(willChangeValueForKey:withSetMutation:usingObjects:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, aKey, mutationKind, objects);
if (!aKey)
return;
var changeKind = _changeKindForSetMutationKind(mutationKind),
changeOptions = [CPDictionary dictionaryWithObject:changeKind forKey:CPKeyValueChangeKindKey];
//set hidden change-dict ivars to support unordered to-many relationships
changeOptions[_CPKeyValueChangeSetMutationObjectsKey] = objects;
changeOptions[_CPKeyValueChangeSetMutationKindKey] = mutationKind;
[[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
}
- (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
{
var superClass = [self class],
methodSelector = @selector(didChangeValueForKey:withSetMutation:usingObjects:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, aKey, mutationKind, objects);
if (!aKey)
return;
[[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
}
- (Class)class
{
return self[KVOProxyKey]._nativeClass;
}
- (Class)superclass
{
return [[self class] superclass];
}
- (BOOL)isKindOfClass:(Class)aClass
{
return [[self class] isSubclassOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass
{
return [self class] == aClass;
}
- (CPString)className
{
return [self class].name;
}
@end
@implementation _CPKVOModelDictionarySubclass
{
}
- (void)removeAllObjects
{
var keys = [self allKeys],
count = [keys count],
i = 0;
for (; i < count; i++)
[self willChangeValueForKey:keys[i]];
var superClass = [self class],
methodSelector = @selector(removeAllObjects),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector);
for (i = 0; i < count; i++)
[self didChangeValueForKey:keys[i]];
}
- (void)removeObjectForKey:(id)aKey
{
[self willChangeValueForKey:aKey];
var superClass = [self class],
methodSelector = @selector(removeObjectForKey:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, aKey);
[self didChangeValueForKey:aKey];
}
- (void)setObject:(id)anObject forKey:(id)aKey
{
[self willChangeValueForKey:aKey];
var superClass = [self class],
methodSelector = @selector(setObject:forKey:),
methodImp = class_getMethodImplementation(superClass, methodSelector);
methodImp(self, methodSelector, anObject, aKey);
[self didChangeValueForKey:aKey];
}
@end
@implementation _CPKVOForwardingObserver : CPObject
{
id _object;
id _observer;
id _context;
unsigned _options;
//a.b
CPString _firstPart; //a
CPString _secondPart; //b
id _value;
}
- (id)initWithKeyPath:(CPString)aKeyPath object:(id)anObject observer:(id)anObserver options:(unsigned)options context:(id)aContext
{
self = [super init];
_context = aContext;
_observer = anObserver;
_object = anObject;
_options = options;
var dotIndex = aKeyPath.indexOf('.');
if (dotIndex == CPNotFound)
[CPException raise:CPInvalidArgumentException reason:"Created _CPKVOForwardingObserver without compound key path: "+aKeyPath];
_firstPart = aKeyPath.substring(0, dotIndex);
_secondPart = aKeyPath.substring(dotIndex + 1);
//become an observer of the first part of our key (a)
[_object addObserver:self forKeyPath:_firstPart options:_options context:nil];
//the current value of a (not the value of a.b)
_value = [_object valueForKey:_firstPart];
if (_value)
[_value addObserver:self forKeyPath:_secondPart options:_options context:nil]; //we're observing b on current a
return self;
}
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
{
if (aKeyPath === _firstPart)
{
[_observer observeValueForKeyPath:_firstPart ofObject:_object change:changes context:_context];
//since a has changed, we should remove ourselves as an observer of the old a, and observe the new one
if (_value)
[_value removeObserver:self forKeyPath:_secondPart];
_value = [_object valueForKey:_firstPart];
if (_value)
[_value addObserver:self forKeyPath:_secondPart options:_options context:nil];
}
else
{
//a is the same, but a.b has changed -- nothing to do but forward this message along
[_observer observeValueForKeyPath:_firstPart+"."+aKeyPath ofObject:_object change:changes context:_context];
}
}
- (void)finalize
{
if (_value)
[_value removeObserver:self forKeyPath:_secondPart];
[_object removeObserver:self forKeyPath:_firstPart];
_object = nil;
_observer = nil;
_context = nil;
_value = nil;
}
@end
var _CPKVOInfoMake = function _CPKVOInfoMake(anObserver, theOptions, aContext, aForwarder)
{
return {
observer: anObserver,
options: theOptions,
context: aContext,
forwarder: aForwarder
};
}
@import "CPArray+KVO.j"
@import "CPSet+KVO.j"