diff --git a/Foundation/CPKeyValueObserving.j b/Foundation/CPKeyValueObserving.j index fd8b5e0b6..547ffcb47 100644 --- a/Foundation/CPKeyValueObserving.j +++ b/Foundation/CPKeyValueObserving.j @@ -299,39 +299,301 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld, if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey]) return; - var currentClass = _nativeClass, - capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1), - found = false, - replacementMethods = [ - "set" + capitalizedKey + ":", _kvoMethodForMethod, - "_set" + capitalizedKey + ":", _kvoMethodForMethod, - "insertObject:in" + capitalizedKey + "AtIndex:", _kvoInsertMethodForMethod, - "insert" + capitalizedKey + ":atIndexes:", _kvoInsertManyMethodForMethod, - "replaceObjectIn" + capitalizedKey + "AtIndex:withObject:", _kvoReplaceMethodForMethod, - "replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":", _kvoReplaceManyMethodForMethod, - "removeObjectFrom" + capitalizedKey + "AtIndex:", _kvoRemoveMethodForMethod, - "remove" + capitalizedKey + "AtIndexes:", _kvoRemoveManyMethodForMethod, - "add" + capitalizedKey + "Object:", _kvoUnionMethodForMethod, - "add" + capitalizedKey + ":", _kvoUnionManyMethodForMethod, - "remove" + capitalizedKey + "Object:", _kvoMinusMethodForMethod, - "remove" + capitalizedKey + ":", _kvoMinusManyMethodForMethod, - "intersect" + capitalizedKey + ":", _kvoIntersectManyMethodForMethod - ]; + var theClass = _nativeClass, + KVOClass = _targetObject.isa, + capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1); - var i = 0, - count = replacementMethods.length; + // Attribute and To-One Relationships + var setKey_selector = sel_getUid("set" + capitalizedKey + ":"), + setKey_method = class_getInstanceMethod(theClass, setKey_selector); - for (; i < count; i += 2) + if (setKey_method) { - var theSelector = sel_getName(replacementMethods[i]), - theReplacementMethod = replacementMethods[i + 1]; + var setKey_method_imp = setKey_method.method_imp; - if ([_nativeClass instancesRespondToSelector:theSelector]) + class_addMethod(KVOClass, setKey_selector, function(self, _cmd, anObject) { - var theMethod = class_getInstanceMethod(_nativeClass, theSelector); + [self willChangeValueForKey:aKey]; - class_addMethod(_targetObject.isa, theSelector, theReplacementMethod(aKey, theMethod), ""); - [_replacedKeys addObject: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 + ":atIndex:"), + 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) + { + var isKeyObserved = self._KVOObservedKeys.hasOwnProperty(aKey); + + [self willChange:CPKeyValueChangeRemoval + valuesAtIndexes:[indexes copy] + forKey:aKey]; + + removeKeyAtIndexes_method_imp(self, _cmd, indexes); + + if (isKeyObserved) + [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) + { + var isKeyObserved = self._KVOObservedKeys.hasOwnProperty(aKey); + + [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) + { + var isKeyObserved = self._KVOObservedKeys.hasOwnProperty(aKey); + + [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: 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]]; + }, ""); } } @@ -856,127 +1118,5 @@ var _CPKVOInfoMake = function _CPKVOInfoMake(anObserver, theOptions, aContext, a }; } -var _kvoMethodForMethod = function _kvoMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, object) - { - //FIXME: do we have to call the specific willChange methods for to-many relationships? - [self willChangeValueForKey:theKey]; - theMethod.method_imp(self, _cmd, object); - [self didChangeValueForKey:theKey]; - } -} - -var _kvoInsertMethodForMethod = function _kvoInsertMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, object, index) - { - [self willChange:CPKeyValueChangeInsertion valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]; - theMethod.method_imp(self, _cmd, object, index); - [self didChange:CPKeyValueChangeInsertion valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]; - } -} - -var _kvoInsertManyMethodForMethod = function _kvoInsertManyMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, objects, indexes) - { - [self willChange:CPKeyValueChangeInsertion valuesAtIndexes:indexes forKey:theKey]; - theMethod.method_imp(self, _cmd, objects, indexes); - [self didChange:CPKeyValueChangeInsertion valuesAtIndexes:indexes forKey:theKey]; - } -} - -var _kvoReplaceMethodForMethod = function _kvoReplaceMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, index, object) - { - [self willChange:CPKeyValueChangeReplacement valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]; - theMethod.method_imp(self, _cmd, index, object); - [self didChange:CPKeyValueChangeReplacement valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]; - } -} - -var _kvoReplaceManyMethodForMethod = function _kvoReplaceManyMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, indexes, objects) - { - [self willChange:CPKeyValueChangeReplacement valuesAtIndexes:indexes forKey:theKey]; - theMethod.method_imp(self, _cmd, indexes, objects); - [self didChange:CPKeyValueChangeReplacement valuesAtIndexes:indexes forKey:theKey]; - } -} - -var _kvoRemoveMethodForMethod = function _kvoRemoveMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, index) - { - [self willChange:CPKeyValueChangeRemoval valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]; - theMethod.method_imp(self, _cmd, index); - [self didChange:CPKeyValueChangeRemoval valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]; - } -} - -var _kvoRemoveManyMethodForMethod = function _kvoRemoveManyMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, indexes) - { - [self willChange:CPKeyValueChangeRemoval valuesAtIndexes:indexes forKey:theKey]; - theMethod.method_imp(self, _cmd, indexes); - [self didChange:CPKeyValueChangeRemoval valuesAtIndexes:indexes forKey:theKey]; - } -} - -var _kvoUnionMethodForMethod = function _kvoUnionMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, object) - { - [self willChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: [CPSet setWithObject: object]]; - theMethod.method_imp(self, _cmd, object); - [self didChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: [CPSet setWithObject: object]]; - } -} - -var _kvoUnionManyMethodForMethod = function _kvoUnionManyMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, objects) - { - [self willChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: objects]; - theMethod.method_imp(self, _cmd, objects); - [self didChangeValueForKey:theKey withSetMutation:CPKeyValueUnionSetMutation usingObjects: objects]; - } -} - -var _kvoMinusMethodForMethod = function _kvoMinusMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, object) - { - [self willChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: [CPSet setWithObject: object]]; - theMethod.method_imp(self, _cmd, object); - [self didChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: [CPSet setWithObject: object]]; - } -} - -var _kvoMinusManyMethodForMethod = function _kvoMinusManyMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, objects) - { - [self willChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: objects]; - theMethod.method_imp(self, _cmd, objects); - [self didChangeValueForKey:theKey withSetMutation:CPKeyValueMinusSetMutation usingObjects: objects]; - } -} - -var _kvoIntersectManyMethodForMethod = function _kvoIntersectManyMethodForMethod(theKey, theMethod) -{ - return function(self, _cmd, objects) - { - [self willChangeValueForKey:theKey withSetMutation:CPKeyValueIntersectSetMutation usingObjects: objects]; - theMethod.method_imp(self, _cmd, objects); - [self didChangeValueForKey:theKey withSetMutation:CPKeyValueIntersectSetMutation usingObjects: objects]; - } -} - - @import "CPArray+KVO.j" @import "CPSet+KVO.j"