00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPArray.j"
00024 @import "CPDictionary.j"
00025 @import "CPException.j"
00026 @import "CPObject.j"
00027 @import "CPSet.j"
00028
00029
00030 @implementation CPObject (KeyValueObserving)
00031
00032 - (void)willChangeValueForKey:(CPString)aKey
00033 {
00034 }
00035
00036 - (void)didChangeValueForKey:(CPString)aKey
00037 {
00038 }
00039
00040 - (void)willChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)key
00041 {
00042 }
00043
00044 - (void)didChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)key
00045 {
00046 }
00047
00048 - (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
00049 {
00050 if (!anObserver || !aPath)
00051 return;
00052
00053 [[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext];
00054 }
00055
00056 - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aPath
00057 {
00058 if (!anObserver || !aPath)
00059 return;
00060
00061 [[KVOProxyMap objectForKey:[self hash]] _removeObserver:anObserver forKeyPath:aPath];
00062 }
00063
00064 + (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
00065 {
00066 return YES;
00067 }
00068
00069 + (CPSet)keyPathsForValuesAffectingValueForKey:(CPString)aKey
00070 {
00071 var capitalizedKey = aKey.charAt(0).toUpperCase()+aKey.substring(1);
00072 selector = "keyPathsForValuesAffectingValueFor"+capitalizedKey;
00073
00074 if ([[self class] respondsToSelector:selector])
00075 return objj_msgSend([self class], selector);
00076
00077 return [CPSet set];
00078 }
00079
00080 @end
00081
00082
00083 CPKeyValueObservingOptionNew = 1 << 0;
00084 CPKeyValueObservingOptionOld = 1 << 1;
00085 CPKeyValueObservingOptionInitial = 1 << 2;
00086 CPKeyValueObservingOptionPrior = 1 << 3;
00087
00088
00089 CPKeyValueChangeKindKey = @"CPKeyValueChangeKindKey";
00090 CPKeyValueChangeNewKey = @"CPKeyValueChangeNewKey";
00091 CPKeyValueChangeOldKey = @"CPKeyValueChangeOldKey";
00092 CPKeyValueChangeIndexesKey = @"CPKeyValueChangeIndexesKey";
00093 CPKeyValueChangeNotificationIsPriorKey = @"CPKeyValueChangeNotificationIsPriorKey";
00094
00095
00096 CPKeyValueChangeSetting = 1;
00097 CPKeyValueChangeInsertion = 2;
00098 CPKeyValueChangeRemoval = 3;
00099 CPKeyValueChangeReplacement = 4;
00100
00101
00102 var kvoNewAndOld = CPKeyValueObservingOptionNew|CPKeyValueObservingOptionOld;
00103
00104
00105 var KVOProxyMap = [CPDictionary dictionary],
00106 DependentKeysMap = [CPDictionary dictionary];
00107
00108
00109
00110
00111 @implementation _CPKVOProxy : CPObject
00112 {
00113 id _targetObject;
00114 Class _nativeClass;
00115 CPDictionary _changesForKey;
00116 CPDictionary _observersForKey;
00117 CPSet _replacedKeys;
00118 }
00119
00120 + (id)proxyForObject:(CPObject)anObject
00121 {
00122 var proxy = [KVOProxyMap objectForKey:[anObject hash]];
00123
00124 if (proxy)
00125 return proxy;
00126
00127 proxy = [[self alloc] initWithTarget:anObject];
00128
00129 [proxy _replaceClass];
00130
00131 [KVOProxyMap setObject:proxy forKey:[anObject hash]];
00132
00133 return proxy;
00134 }
00135
00136 - (id)initWithTarget:(id)aTarget
00137 {
00138 self = [super init];
00139
00140 _targetObject = aTarget;
00141 _nativeClass = [aTarget class];
00142 _observersForKey = [CPDictionary dictionary];
00143 _changesForKey = [CPDictionary dictionary];
00144 _replacedKeys = [CPSet set];
00145
00146 return self;
00147 }
00148
00149 - (void)_replaceClass
00150 {
00151 var currentClass = _nativeClass,
00152 kvoClassName = "$KVO_"+class_getName(_nativeClass),
00153 existingKVOClass = objj_lookUpClass(kvoClassName);
00154
00155 if (existingKVOClass)
00156 {
00157 _targetObject.isa = existingKVOClass;
00158 return;
00159 }
00160
00161 var kvoClass = objj_allocateClassPair(currentClass, kvoClassName);
00162
00163 objj_registerClassPair(kvoClass);
00164 _class_initialize(kvoClass);
00165
00166
00167 var methodList = _CPKVOModelSubclass.method_list,
00168 count = methodList.length;
00169
00170 for (var i=0; i<count; i++)
00171 {
00172 var method = methodList[i];
00173 class_addMethod(kvoClass, method_getName(method), method_getImplementation(method), "");
00174 }
00175
00176 _targetObject.isa = kvoClass;
00177 }
00178
00179 - (void)_replaceSetterForKey:(CPString)aKey
00180 {
00181 if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey])
00182 return;
00183
00184 var currentClass = _nativeClass,
00185 capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1),
00186 found = false,
00187 replacementMethods = [
00188 "set"+capitalizedKey+":", _kvoMethodForMethod,
00189 "_set"+capitalizedKey+":", _kvoMethodForMethod,
00190 "insertObject:in"+capitalizedKey+"AtIndex:", _kvoInsertMethodForMethod,
00191 "replaceObjectIn"+capitalizedKey+"AtIndex:withObject:", _kvoReplaceMethodForMethod,
00192 "removeObjectFrom"+capitalizedKey+"AtIndex:", _kvoRemoveMethodForMethod
00193 ];
00194
00195 for (var i=0, count=replacementMethods.length; i<count; i+=2)
00196 {
00197 var theSelector = sel_getName(replacementMethods[i]),
00198 theReplacementMethod = replacementMethods[i+1];
00199
00200 if ([_nativeClass instancesRespondToSelector:theSelector])
00201 {
00202 var theMethod = class_getInstanceMethod(_nativeClass, theSelector);
00203
00204 class_addMethod(_targetObject.isa, theSelector, theReplacementMethod(aKey, theMethod), "");
00205
00206 found = true;
00207 }
00208 }
00209
00210 if (found)
00211 return;
00212
00213 var composedOfKeys = [[_nativeClass keyPathsForValuesAffectingValueForKey:aKey] allObjects];
00214
00215 if (!composedOfKeys)
00216 return;
00217
00218 var dependentKeysForClass = [DependentKeysMap objectForKey:[_nativeClass hash]];
00219
00220 if (!dependentKeysForClass)
00221 {
00222 dependentKeysForClass = [CPDictionary new];
00223 [DependentKeysMap setObject:dependentKeysForClass forKey:[_nativeClass hash]];
00224 }
00225
00226 for (var i=0, count=composedOfKeys.length; i<count; i++)
00227 {
00228 var componentKey = composedOfKeys[i],
00229 keysComposedOfKey = [dependentKeysForClass objectForKey:componentKey];
00230
00231 if (!keysComposedOfKey)
00232 {
00233 keysComposedOfKey = [CPSet new];
00234 [dependentKeysForClass setObject:keysComposedOfKey forKey:componentKey];
00235 }
00236
00237 [keysComposedOfKey addObject:aKey];
00238 [self _replaceSetterForKey:componentKey];
00239 }
00240 }
00241
00242 - (void)_addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
00243 {
00244 if (!anObserver)
00245 return;
00246
00247 var forwarder = nil;
00248
00249 if (aPath.indexOf('.') != CPNotFound)
00250 forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
00251 else
00252 [self _replaceSetterForKey:aPath];
00253
00254 var observers = [_observersForKey objectForKey:aPath];
00255
00256 if (!observers)
00257 {
00258 observers = [CPDictionary dictionary];
00259 [_observersForKey setObject:observers forKey:aPath];
00260 }
00261
00262 [observers setObject:_CPKVOInfoMake(anObserver, options, aContext, forwarder) forKey:[anObserver hash]];
00263
00264 if (options & CPKeyValueObservingOptionInitial)
00265 {
00266 var newValue = [_targetObject valueForKeyPath:aPath];
00267
00268 if (newValue === nil || newValue === undefined)
00269 newValue = [CPNull null];
00270
00271 var changes = [CPDictionary dictionaryWithObject:newValue forKey:CPKeyValueChangeNewKey];
00272 [anObserver observeValueForKeyPath:aPath ofObject:self change:changes context:aContext];
00273 }
00274 }
00275
00276 - (void)_removeObserver:(id)anObserver forKeyPath:(CPString)aPath
00277 {
00278 var observers = [_observersForKey objectForKey:aPath];
00279
00280 if (aPath.indexOf('.') != CPNotFound)
00281 {
00282 var forwarder = [observers objectForKey:[anObserver hash]].forwarder;
00283 [forwarder finalize];
00284 }
00285
00286 [observers removeObjectForKey:[anObserver hash]];
00287
00288 if (![observers count])
00289 [_observersForKey removeObjectForKey:aPath];
00290
00291 if (![_observersForKey count])
00292 {
00293 _targetObject.isa = _nativeClass;
00294 [KVOProxyMap removeObjectForKey:[_targetObject hash]];
00295 }
00296 }
00297
00298
00299
00300 - (void)_sendNotificationsForKey:(CPString)aKey changeOptions:(CPDictionary)changeOptions isBefore:(BOOL)isBefore
00301 {
00302 var changes = [_changesForKey objectForKey:aKey];
00303
00304 if (isBefore)
00305 {
00306 changes = changeOptions;
00307
00308 var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
00309
00310 if (indexes)
00311 {
00312 var type = [changes objectForKey:CPKeyValueChangeKindKey];
00313
00314
00315 if (type == CPKeyValueChangeReplacement || type == CPKeyValueChangeRemoval)
00316 {
00317
00318 var oldValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
00319 [changes setValue:oldValues forKey:CPKeyValueChangeOldKey];
00320 }
00321 }
00322 else
00323 {
00324 var oldValue = [_targetObject valueForKey:aKey];
00325
00326 if (oldValue === nil || oldValue === undefined)
00327 oldValue = [CPNull null];
00328
00329 [changes setObject:oldValue forKey:CPKeyValueChangeOldKey];
00330 }
00331
00332 [changes setObject:1 forKey:CPKeyValueChangeNotificationIsPriorKey];
00333
00334 [_changesForKey setObject:changes forKey:aKey];
00335 }
00336 else
00337 {
00338 [changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
00339
00340 var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
00341
00342 if (indexes)
00343 {
00344 var type = [changes objectForKey:CPKeyValueChangeKindKey];
00345
00346
00347 if (type == CPKeyValueChangeReplacement || type == CPKeyValueChangeInsertion)
00348 {
00349
00350 var oldValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
00351 [changes setValue:oldValues forKey:CPKeyValueChangeNewKey];
00352 }
00353 }
00354 else
00355 {
00356 var newValue = [_targetObject valueForKey:aKey];
00357
00358 if (newValue === nil || newValue === undefined)
00359 newValue = [CPNull null];
00360
00361 [changes setObject:newValue forKey:CPKeyValueChangeNewKey];
00362 }
00363 }
00364
00365 var observers = [[_observersForKey objectForKey:aKey] allValues],
00366 count = [observers count];
00367
00368 while (count--)
00369 {
00370 var observerInfo = observers[count];
00371
00372 if (isBefore && (observerInfo.options & CPKeyValueObservingOptionPrior))
00373 [observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:changes context:observerInfo.context];
00374 else if (!isBefore)
00375 [observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:changes context:observerInfo.context];
00376 }
00377
00378 var keysComposedOfKey = [[[DependentKeysMap objectForKey:[_nativeClass hash]] objectForKey:aKey] allObjects];
00379
00380 if (!keysComposedOfKey)
00381 return;
00382
00383 for (var i=0, count=keysComposedOfKey.length; i<count; i++)
00384 [self _sendNotificationsForKey:keysComposedOfKey[i] changeOptions:changeOptions isBefore:isBefore];
00385 }
00386
00387 @end
00388
00389 @implementation _CPKVOModelSubclass
00390 {
00391 }
00392
00393 - (void)willChangeValueForKey:(CPString)aKey
00394 {
00395 if (!aKey)
00396 return;
00397
00398 var changeOptions = [CPDictionary dictionaryWithObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey];
00399
00400 [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
00401 }
00402
00403 - (void)didChangeValueForKey:(CPString)aKey
00404 {
00405 if (!aKey)
00406 return;
00407
00408 [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
00409 }
00410
00411 - (void)willChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
00412 {
00413 if (!aKey)
00414 return;
00415
00416 var changeOptions = [CPDictionary dictionaryWithObjects:[change, indexes] forKeys:[CPKeyValueChangeKindKey, CPKeyValueChangeIndexesKey]];
00417
00418 [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
00419 }
00420
00421 - (void)didChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
00422 {
00423 if (!aKey)
00424 return;
00425
00426 [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
00427 }
00428
00429 - (Class)class
00430 {
00431 return [KVOProxyMap objectForKey:[self hash]]._nativeClass;
00432 }
00433
00434 - (Class)superclass
00435 {
00436 return [[self class] superclass];
00437 }
00438
00439 - (BOOL)isKindOfClass:(Class)aClass
00440 {
00441 return [[self class] isSubclassOfClass:aClass];
00442 }
00443
00444 - (BOOL)isMemberOfClass:(Class)aClass
00445 {
00446 return [self class] == aClass;
00447 }
00448
00449 - (CPString)className
00450 {
00451 return [self class].name;
00452 }
00453
00454 @end
00455
00456 @implementation _CPKVOForwardingObserver : CPObject
00457 {
00458 id _object;
00459 id _observer;
00460 id _context;
00461
00462 CPString _firstPart;
00463 CPString _secondPart;
00464
00465 id _value;
00466 }
00467
00468 - (id)initWithKeyPath:(CPString)aKeyPath object:(id)anObject observer:(id)anObserver options:(unsigned)options context:(id)aContext
00469 {
00470 self = [super init];
00471
00472 _context = aContext;
00473 _observer = anObserver;
00474 _object = anObject;
00475
00476
00477
00478 var dotIndex = aKeyPath.indexOf('.');
00479
00480 if (dotIndex == CPNotFound)
00481 [CPException raise:CPInvalidArgumentException reason:"Created _CPKVOForwardingObserver without compound key path: "+aKeyPath];
00482
00483 _firstPart = aKeyPath.substring(0, dotIndex);
00484 _secondPart = aKeyPath.substring(dotIndex+1);
00485
00486
00487 [_object addObserver:self forKeyPath:_firstPart options:kvoNewAndOld context:nil];
00488
00489
00490 _value = [_object valueForKey:_firstPart];
00491
00492 if (_value)
00493 [_value addObserver:self forKeyPath:_secondPart options:kvoNewAndOld context:nil];
00494
00495 return self;
00496 }
00497
00498 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
00499 {
00500 if (anObject == _object)
00501 {
00502 [_observer observeValueForKeyPath:_firstPart ofObject:_object change:changes context:_context];
00503
00504
00505 if (_value)
00506 [_value removeObserver:self forKeyPath:_secondPart];
00507
00508 _value = [_object valueForKey:_firstPart];
00509
00510 if (_value)
00511 [_value addObserver:self forKeyPath:_secondPart options:kvoNewAndOld context:nil];
00512 }
00513 else
00514 {
00515
00516 [_observer observeValueForKeyPath:_firstPart+"."+aKeyPath ofObject:_object change:changes context:_context];
00517 }
00518 }
00519
00520 - (void)finalize
00521 {
00522 if (_value)
00523 [_value removeObserver:self forKeyPath:_secondPart];
00524
00525 [_object removeObserver:self forKeyPath:_firstPart];
00526
00527 _object = nil;
00528 _observer = nil;
00529 _context = nil;
00530 _value = nil;
00531 }
00532
00533 @end
00534
00535 var _CPKVOInfoMake = function _CPKVOInfoMake(anObserver, theOptions, aContext, aForwarder)
00536 {
00537 return {
00538 observer: anObserver,
00539 options: theOptions,
00540 context: aContext,
00541 forwarder: aForwarder
00542 };
00543 }
00544
00545 var _kvoMethodForMethod = function _kvoMethodForMethod(theKey, theMethod)
00546 {
00547 return function(self, _cmd, object)
00548 {
00549 [self willChangeValueForKey:theKey];
00550 theMethod.method_imp(self, _cmd, object);
00551 [self didChangeValueForKey:theKey];
00552 }
00553 }
00554
00555 var _kvoInsertMethodForMethod = function _kvoInsertMethodForMethod(theKey, theMethod)
00556 {
00557 return function(self, _cmd, object, index)
00558 {
00559 [self willChange:CPKeyValueChangeInsertion valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey];
00560 theMethod.method_imp(self, _cmd, object, index);
00561 [self didChange:CPKeyValueChangeInsertion valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]
00562 }
00563 }
00564
00565 var _kvoReplaceMethodForMethod = function _kvoReplaceMethodForMethod(theKey, theMethod)
00566 {
00567 return function(self, _cmd, index, object)
00568 {
00569 [self willChange:CPKeyValueChangeReplacement valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey];
00570 theMethod.method_imp(self, _cmd, index, object);
00571 [self didChange:CPKeyValueChangeReplacement valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]
00572 }
00573 }
00574
00575 var _kvoRemoveMethodForMethod = function _kvoRemoveMethodForMethod(theKey, theMethod)
00576 {
00577 return function(self, _cmd, index)
00578 {
00579 [self willChange:CPKeyValueChangeRemoval valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey];
00580 theMethod.method_imp(self, _cmd, index);
00581 [self didChange:CPKeyValueChangeRemoval valuesAtIndexes:[CPIndexSet indexSetWithIndex:index] forKey:theKey]
00582 }
00583 }
00584
00585 @import "CPArray+KVO.j"