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
00028
00029 @implementation CPObject (KeyValueObserving)
00030
00031 - (void)willChangeValueForKey:(CPString)aKey
00032 {
00033
00034 }
00035
00036 - (void)didChangeValueForKey:(CPString)aKey
00037 {
00038
00039 }
00040
00041 - (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
00042 {
00043 if (!anObserver || !aPath)
00044 return;
00045
00046 [[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext];
00047 }
00048
00049 - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aPath
00050 {
00051 if (!anObserver || !aPath)
00052 return;
00053
00054 [[KVOProxyMap objectForKey:[self hash]] _removeObserver:anObserver forKeyPath:aPath];
00055 }
00056
00057 - (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
00058 {
00059 return YES;
00060 }
00061
00062 @end
00063
00064
00065 CPKeyValueObservingOptionNew = 1 << 0;
00066 CPKeyValueObservingOptionOld = 1 << 1;
00067 CPKeyValueObservingOptionInitial = 1 << 2;
00068 CPKeyValueObservingOptionPrior = 1 << 3;
00069
00070
00071 var kvoNewAndOld = CPKeyValueObservingOptionNew|CPKeyValueObservingOptionOld;
00072
00073
00074 CPKeyValueChangeKindKey = @"CPKeyValueChangeKindKey";
00075 CPKeyValueChangeNewKey = @"CPKeyValueChangeNewKey";
00076 CPKeyValueChangeOldKey = @"CPKeyValueChangeOldKey";
00077 CPKeyValueChangeIndexesKey = @"CPKeyValueChangeIndexesKey";
00078 CPKeyValueChangeNotificationIsPriorKey = @"CPKeyValueChangeNotificationIsPriorKey";
00079
00080
00081 var KVOProxyMap = [CPDictionary dictionary];
00082
00083
00084
00085
00086 @implementation _CPKVOProxy : CPObject
00087 {
00088 id _targetObject;
00089 Class _nativeClass;
00090 CPDictionary _changesForKey;
00091 CPDictionary _observersForKey;
00092 CPDictionary _replacementMethods;
00093 }
00094
00095 + (id)proxyForObject:(CPObject)anObject
00096 {
00097 var proxy = [KVOProxyMap objectForKey:[anObject hash]];
00098
00099 if (proxy)
00100 return proxy;
00101
00102 proxy = [[self alloc] initWithTarget:anObject];
00103
00104
00105
00106
00107
00108 [proxy _replaceClass];
00109
00110 [KVOProxyMap setObject:proxy forKey:[anObject hash]];
00111
00112 return proxy;
00113 }
00114
00115 - (id)initWithTarget:(id)aTarget
00116 {
00117 self = [super init];
00118
00119 _targetObject = aTarget;
00120 _nativeClass = [aTarget class];
00121 _replacementMethods = [CPDictionary dictionary];
00122 _observersForKey = [CPDictionary dictionary];
00123 _changesForKey = [CPDictionary dictionary];
00124
00125 return self;
00126 }
00127
00128 - (void)_replaceSetters
00129 {
00130 var currentClass = [_targetObject class];
00131
00132 while (currentClass && currentClass != currentClass.super_class)
00133 {
00134 var methodList = currentClass.method_list,
00135 count = methodList.length;
00136
00137 for (var i=0; i<count; i++)
00138 {
00139 var newMethod = _kvoMethodForMethod(_targetObject, methodList[i]);
00140
00141 if (newMethod)
00142 [_replacementMethods setObject:newMethod forKey:methodList[i].name];
00143 }
00144
00145 currentClass = currentClass.super_class;
00146 }
00147 }
00148
00149 - (void)_replaceSetters
00150 {
00151 var currentClass = [_targetObject class];
00152
00153 while (currentClass && currentClass != currentClass.super_class)
00154 {
00155 var methodList = currentClass.method_list,
00156 count = methodList.length;
00157
00158 for (var i=0; i<count; i++)
00159 {
00160 var newMethod = _kvoMethodForMethod(_targetObject, methodList[i]);
00161
00162 if (newMethod)
00163 [_replacementMethods setObject:newMethod forKey:methodList[i].name];
00164 }
00165
00166 currentClass = currentClass.super_class;
00167 }
00168 }
00169
00170 - (void)_replaceClass
00171 {
00172 var currentClass = _nativeClass,
00173 kvoClassName = "$KVO_"+class_getName(_nativeClass),
00174 existingKVOClass = objj_lookUpClass(kvoClassName);
00175
00176 if (existingKVOClass)
00177 {
00178 _targetObject.isa = existingKVOClass;
00179 return;
00180 }
00181
00182 var kvoClass = objj_allocateClassPair(currentClass, kvoClassName);
00183
00184 objj_registerClassPair(kvoClass);
00185 _class_initialize(kvoClass);
00186
00187 while (currentClass && currentClass != currentClass.super_class)
00188 {
00189 var methodList = currentClass.method_list,
00190 count = methodList.length;
00191
00192 for (var i=0; i<count; i++)
00193 {
00194 var newMethodImp = _kvoMethodForMethod(_targetObject, methodList[i]);
00195
00196 if (newMethodImp)
00197 class_addMethod(kvoClass, method_getName(methodList[i]), newMethodImp, "");
00198 }
00199
00200 currentClass = currentClass.super_class;
00201 }
00202
00203 var methodList = _CPKVOModelSubclass.method_list,
00204 count = methodList.length;
00205
00206 for (var i=0; i<count; i++)
00207 {
00208 var method = methodList[i];
00209 class_addMethod(kvoClass, method_getName(method), method_getImplementation(method), "");
00210 }
00211
00212 _targetObject.isa = kvoClass;
00213 }
00214
00215 - (void)_addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext
00216 {
00217 if (!anObserver)
00218 return;
00219
00220 var forwarder = nil;
00221
00222 if (aPath.indexOf('.') != CPNotFound)
00223 forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
00224
00225 var observers = [_observersForKey objectForKey:aPath];
00226
00227 if (!observers)
00228 {
00229 observers = [CPDictionary dictionary];
00230 [_observersForKey setObject:observers forKey:aPath];
00231 }
00232
00233 [observers setObject:_CPKVOInfoMake(anObserver, options, aContext, forwarder) forKey:[anObserver hash]];
00234
00235 if (options & CPKeyValueObservingOptionInitial)
00236 {
00237 var newValue = [_targetObject valueForKeyPath:aPath];
00238
00239 if (!newValue && newValue !== "")
00240 newValue = [CPNull null];
00241
00242 var changes = [CPDictionary dictionaryWithObject:newValue forKey:CPKeyValueChangeNewKey];
00243 [anObserver observeValueForKeyPath:aPath ofObject:self change:changes context:aContext];
00244 }
00245 }
00246
00247 - (void)_removeObserver:(id)anObserver forKeyPath:(CPString)aPath
00248 {
00249 var observers = [_observersForKey objectForKey:aPath];
00250
00251 if (aPath.indexOf('.') != CPNotFound)
00252 {
00253 var forwarder = [observers objectForKey:[anObserver hash]].forwarder;
00254 [forwarder finalize];
00255 }
00256
00257 [observers removeObjectForKey:[anObserver hash]];
00258
00259 if (![observers count])
00260 [_observersForKey removeObjectForKey:aPath];
00261
00262 if (![_observersForKey count])
00263 {
00264 _targetObject.isa = _nativeClass;
00265 [KVOProxyMap removeObjectForKey:[_targetObject hash]];
00266 }
00267 }
00268
00269 - (void)_sendNotificationsForKey:(CPString)aKey isBefore:(BOOL)isBefore
00270 {
00271 var changes = [_changesForKey objectForKey:aKey];
00272
00273 if (isBefore)
00274 {
00275 changes = [CPDictionary dictionary];
00276
00277 var oldValue = [_targetObject valueForKey:aKey];
00278
00279 if (!oldValue && oldValue !== "")
00280 oldValue = [CPNull null];
00281
00282 [changes setObject:1 forKey:CPKeyValueChangeNotificationIsPriorKey];
00283 [changes setObject:oldValue forKey:CPKeyValueChangeOldKey];
00284
00285 [_changesForKey setObject:changes forKey:aKey];
00286 }
00287 else
00288 {
00289 [changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
00290
00291 var newValue = [_targetObject valueForKey:aKey];
00292
00293 if (!newValue && newValue !== "")
00294 newValue = [CPNull null];
00295
00296 [changes setObject:newValue forKey:CPKeyValueChangeNewKey];
00297 }
00298
00299 var observers = [[_observersForKey objectForKey:aKey] allValues],
00300 count = [observers count];
00301
00302 while (count--)
00303 {
00304 var observerInfo = observers[count];
00305
00306 if (isBefore && (observerInfo.options & CPKeyValueObservingOptionPrior))
00307 [observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:changes context:observerInfo.context];
00308 else if (!isBefore)
00309 [observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:changes context:observerInfo.context];
00310 }
00311 }
00312
00313 @end
00314
00315 @implementation _CPKVOModelSubclass
00316 {
00317 }
00318
00319 - (void)willChangeValueForKey:(CPString)aKey
00320 {
00321 if (!aKey)
00322 return;
00323
00324 [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey isBefore:YES];
00325 }
00326
00327 - (void)didChangeValueForKey:(CPString)aKey
00328 {
00329 if (!aKey)
00330 return;
00331
00332 [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey isBefore:NO];
00333 }
00334
00335 - (Class)class
00336 {
00337 return [KVOProxyMap objectForKey:[self hash]]._nativeClass;
00338 }
00339
00340 - (Class)superclass
00341 {
00342 return [[self class] superclass];
00343 }
00344
00345 - (BOOL)isKindOfClass:(Class)aClass
00346 {
00347 return [[self class] isSubclassOfClass:aClass];
00348 }
00349
00350 - (BOOL)isMemberOfClass:(Class)aClass
00351 {
00352 return [self class] == aClass;
00353 }
00354
00355 - (CPString)className
00356 {
00357 return [self class].name;
00358 }
00359
00360 @end
00361
00362 @implementation _CPKVOForwardingObserver : CPObject
00363 {
00364 id _object;
00365 id _observer;
00366 id _context;
00367
00368 CPString _firstPart;
00369 CPString _secondPart;
00370
00371 id _value;
00372 }
00373
00374 - (id)initWithKeyPath:(CPString)aKeyPath object:(id)anObject observer:(id)anObserver options:(unsigned)options context:(id)aContext
00375 {
00376 self = [super init];
00377
00378 _context = aContext;
00379 _observer = anObserver;
00380 _object = anObject;
00381
00382
00383
00384 var dotIndex = aKeyPath.indexOf('.');
00385
00386 if (dotIndex == CPNotFound)
00387 [CPException raise:CPInvalidArgumentException reason:"Created _CPKVOForwardingObserver without compound key path: "+aKeyPath];
00388
00389 _firstPart = aKeyPath.substring(0, dotIndex);
00390 _secondPart = aKeyPath.substring(dotIndex+1);
00391
00392
00393 [_object addObserver:self forKeyPath:_firstPart options:kvoNewAndOld context:nil];
00394
00395
00396 _value = [_object valueForKey:_firstPart];
00397
00398 if (_value)
00399 [_value addObserver:self forKeyPath:_secondPart options:kvoNewAndOld context:nil];
00400
00401 return self;
00402 }
00403
00404 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
00405 {
00406 if (anObject == _object)
00407 {
00408 [_observer observeValueForKeyPath:_firstPart ofObject:_object change:changes context:_context];
00409
00410
00411 if (_value)
00412 [_value removeObserver:self forKeyPath:_secondPart];
00413
00414 _value = [_object valueForKey:_firstPart];
00415
00416 if (_value)
00417 [_value addObserver:self forKeyPath:_secondPart options:kvoNewAndOld context:nil];
00418 }
00419 else
00420 {
00421
00422 [_observer observeValueForKeyPath:_firstPart+"."+aKeyPath ofObject:_object change:changes context:_context];
00423 }
00424 }
00425
00426 - (void)finalize
00427 {
00428 if (_value)
00429 [_value removeObserver:self forKeyPath:_secondPart];
00430
00431 [_object removeObserver:self forKeyPath:_firstPart];
00432
00433 _object = nil;
00434 _observer = nil;
00435 _context = nil;
00436 _value = nil;
00437 }
00438
00439 @end
00440
00441 var _CPKVOInfoMake = function _CPKVOInfoMake(anObserver, theOptions, aContext, aForwarder)
00442 {
00443 return {
00444 observer: anObserver,
00445 options: theOptions,
00446 context: aContext,
00447 forwarder: aForwarder
00448 };
00449 }
00450
00451 var _kvoKeyForSetter = function _kvoKeyForSetter(selector)
00452 {
00453 if (selector.split(":").length > 2 || !([selector hasPrefix:@"set"] || [selector hasPrefix:@"_set"]))
00454 return nil;
00455
00456 var keyIndex = selector.indexOf("set") + "set".length,
00457 colonIndex = selector.indexOf(":");
00458
00459 return selector.charAt(keyIndex).toLowerCase() + selector.substring(keyIndex+1, colonIndex);
00460 }
00461
00462 var _kvoMethodForMethod = function _kvoMethodForMethod(theObject, theMethod)
00463 {
00464 var methodName = theMethod.name,
00465 methodImplementation = theMethod.method_imp,
00466 setterKey = _kvoKeyForSetter(methodName);
00467
00468 if (setterKey && objj_msgSend(theObject, @selector(automaticallyNotifiesObserversForKey:), setterKey))
00469 {
00470 var newMethodImp = function(self)
00471 {
00472 [self willChangeValueForKey:setterKey];
00473 methodImplementation.apply(self, arguments);
00474 [self didChangeValueForKey:setterKey];
00475 }
00476
00477 return newMethodImp;
00478 }
00479
00480 return nil;
00481 }
00482
00483 @implementation CPArray (KeyValueObserving)
00484
00485 - (void)addObserver:(id)anObserver toObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
00486 {
00487 var index = [indexes firstIndex];
00488
00489 while (index >= 0)
00490 {
00491 [self[index] addObserver:anObserver forKeyPath:aKeyPath options:options context:context];
00492
00493 index = [indexes indexGreaterThanIndex:index];
00494 }
00495 }
00496
00497 - (void)removeObserver:(id)anObserver fromObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath
00498 {
00499 var index = [indexes firstIndex];
00500
00501 while (index >= 0)
00502 {
00503 [self[index] removeObserver:anObserver forKeyPath:aKeyPath];
00504
00505 index = [indexes indexGreaterThanIndex:index];
00506 }
00507 }
00508
00509 -(void)addObserver:(id)observer forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
00510 {
00511 [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"];
00512 }
00513
00514 -(void)removeObserver:(id)observer forKeyPath:(CPString)aKeyPath
00515 {
00516 [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"];
00517 }
00518
00519 @end