API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPKeyValueObserving.j
Go to the documentation of this file.
1 /*
2  * CPKeyValueObserving.j
3  * Foundation
4  *
5  * Created by Ross Boucher.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
25 
26 - (void)willChangeValueForKey:(CPString)aKey
27 {
28  if (!aKey)
29  return;
30 
31  if (!self[KVOProxyKey])
32  {
33  if (!self._willChangeMessageCounter)
34  self._willChangeMessageCounter = new Object();
35 
36  if (!self._willChangeMessageCounter[aKey])
37  self._willChangeMessageCounter[aKey] = 1;
38  else
39  self._willChangeMessageCounter[aKey] += 1;
40  }
41 }
42 
43 - (void)didChangeValueForKey:(CPString)aKey
44 {
45  if (!aKey)
46  return;
47 
48  if (!self[KVOProxyKey])
49  {
50  if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
51  {
52  self._willChangeMessageCounter[aKey] -= 1;
53 
54  if (!self._willChangeMessageCounter[aKey])
55  delete self._willChangeMessageCounter[aKey];
56  }
57  else
58  [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
59  }
60 }
61 
62 - (void)willChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
63 {
64  if (!aKey)
65  return;
66 
67  if (!self[KVOProxyKey])
68  {
69  if (!self._willChangeMessageCounter)
70  self._willChangeMessageCounter = new Object();
71 
72  if (!self._willChangeMessageCounter[aKey])
73  self._willChangeMessageCounter[aKey] = 1;
74  else
75  self._willChangeMessageCounter[aKey] += 1;
76  }
77 }
78 
79 - (void)didChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
80 {
81  if (!aKey)
82  return;
83 
84  if (!self[KVOProxyKey])
85  {
86  if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
87  {
88  self._willChangeMessageCounter[aKey] -= 1;
89 
90  if (!self._willChangeMessageCounter[aKey])
91  delete self._willChangeMessageCounter[aKey];
92  }
93  else
94  [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
95  }
96 }
97 
98 - (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
99 {
100  if (!aKey)
101  return;
102 
103  if (!self[KVOProxyKey])
104  {
105  if (!self._willChangeMessageCounter)
106  self._willChangeMessageCounter = new Object();
107 
108  if (!self._willChangeMessageCounter[aKey])
109  self._willChangeMessageCounter[aKey] = 1;
110  else
111  self._willChangeMessageCounter[aKey] += 1;
112  }
113 }
114 
115 - (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
116 {
117  if (!self[KVOProxyKey])
118  {
119  if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
120  {
121  self._willChangeMessageCounter[aKey] -= 1;
122 
123  if (!self._willChangeMessageCounter[aKey])
124  delete self._willChangeMessageCounter[aKey];
125  }
126  else
127  [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
128  }
129 }
130 
131 - (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(CPKeyValueObservingOptions)options context:(id)aContext
132 {
133  if (!anObserver || !aPath)
134  return;
135 
136  [[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext];
137 }
138 
139 - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aPath
140 {
141  if (!anObserver || !aPath)
142  return;
143 
144  [self[KVOProxyKey] _removeObserver:anObserver forKeyPath:aPath];
145 }
146 
157 + (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
158 {
159  var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1),
160  selector = "automaticallyNotifiesObserversOf" + capitalizedKey;
161 
162  if ([[self class] respondsToSelector:selector])
163  return objj_msgSend([self class], selector);
164 
165  return YES;
166 }
167 
168 + (CPSet)keyPathsForValuesAffectingValueForKey:(CPString)aKey
169 {
170  var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1),
171  selector = "keyPathsForValuesAffecting" + capitalizedKey;
172 
173  if ([[self class] respondsToSelector:selector])
174  return objj_msgSend([self class], selector);
175 
176  return [CPSet set];
177 }
178 
179 - (void)applyChange:(CPDictionary)aChange toKeyPath:(CPString)aKeyPath
180 {
181  var changeKind = [aChange objectForKey:CPKeyValueChangeKindKey],
182  oldValue = [aChange objectForKey:CPKeyValueChangeOldKey],
183  newValue = [aChange objectForKey:CPKeyValueChangeNewKey];
184 
185  if (newValue === [CPNull null])
186  newValue = nil;
187 
188  if (changeKind === CPKeyValueChangeSetting)
189  return [self setValue:newValue forKeyPath:aKeyPath];
190 
191  var indexes = [aChange objectForKey:CPKeyValueChangeIndexesKey];
192 
193  // If we have an indexes entry, then we have an ordered to-many relationship
194  if (indexes)
195  {
196  if (changeKind === CPKeyValueChangeInsertion)
197  [[self mutableArrayValueForKeyPath:aKeyPath] insertObjects:newValue atIndexes:indexes];
198 
199  else if (changeKind === CPKeyValueChangeRemoval)
200  [[self mutableArrayValueForKeyPath:aKeyPath] removeObjectsAtIndexes:indexes];
201 
202  else if (changeKind === CPKeyValueChangeReplacement)
203  [[self mutableArrayValueForKeyPath:aKeyPath] replaceObjectAtIndexes:indexes withObjects:newValue];
204  }
205  else
206  {
207  if (changeKind === CPKeyValueChangeInsertion)
208  [[self mutableSetValueForKeyPath:aKeyPath] unionSet:newValue];
209 
210  else if (changeKind === CPKeyValueChangeRemoval)
211  [[self mutableSetValueForKeyPath:aKeyPath] minusSet:oldValue];
212 
213  else if (changeKind === CPKeyValueChangeReplacement)
214  [[self mutableSetValueForKeyPath:aKeyPath] setSet:newValue];
215  }
216 }
217 
218 @end
219 
221 
222 - (CPDictionary)inverseChangeDictionary
223 {
224  var inverseChangeDictionary = [self mutableCopy],
225  changeKind = [self objectForKey:CPKeyValueChangeKindKey];
226 
227  if (changeKind === CPKeyValueChangeSetting || changeKind === CPKeyValueChangeReplacement)
228  {
230  setObject:[self objectForKey:CPKeyValueChangeOldKey]
231  forKey:CPKeyValueChangeNewKey];
232 
234  setObject:[self objectForKey:CPKeyValueChangeNewKey]
235  forKey:CPKeyValueChangeOldKey];
236  }
237 
238  else if (changeKind === CPKeyValueChangeInsertion)
239  {
241  setObject:CPKeyValueChangeRemoval
242  forKey:CPKeyValueChangeKindKey];
243 
245  setObject:[self objectForKey:CPKeyValueChangeNewKey]
246  forKey:CPKeyValueChangeOldKey];
247 
248  [inverseChangeDictionary removeObjectForKey:CPKeyValueChangeNewKey];
249  }
250 
251  else if (changeKind === CPKeyValueChangeRemoval)
252  {
254  setObject:CPKeyValueChangeInsertion
255  forKey:CPKeyValueChangeKindKey];
256 
258  setObject:[self objectForKey:CPKeyValueChangeOldKey]
259  forKey:CPKeyValueChangeNewKey];
260 
261  [inverseChangeDictionary removeObjectForKey:CPKeyValueChangeOldKey];
262  }
263 
264  return inverseChangeDictionary;
265 }
266 
267 @end
268 
269 // KVO Options
274 
275 // KVO Change Dictionary Keys
276 CPKeyValueChangeKindKey = @"CPKeyValueChangeKindKey";
277 CPKeyValueChangeNewKey = @"CPKeyValueChangeNewKey";
278 CPKeyValueChangeOldKey = @"CPKeyValueChangeOldKey";
279 CPKeyValueChangeIndexesKey = @"CPKeyValueChangeIndexesKey";
280 CPKeyValueChangeNotificationIsPriorKey = @"CPKeyValueChangeNotificationIsPriorKey";
281 
282 // KVO Change Types
287 
288 // CPKeyValueSetMutationKind
293 
294 //FIXME: "secret" dict ivar-keys are workaround to support unordered to-many relationships without too many modifications
295 _CPKeyValueChangeSetMutationObjectsKey = @"_CPKeyValueChangeSetMutationObjectsKey";
296 _CPKeyValueChangeSetMutationKindKey = @"_CPKeyValueChangeSetMutationKindKey";
297 _CPKeyValueChangeSetMutationNewValueKey = @"_CPKeyValueChangeSetMutationNewValueKey";
298 
299 var _changeKindForSetMutationKind = function(mutationKind)
300 {
301  switch (mutationKind)
302  {
307  }
308 };
309 
311  DependentKeysKey = "$KVODEPENDENT",
312  KVOProxyKey = "$KVOPROXY";
313 
314 //rule of thumb: _ methods are called on the real proxy object, others are called on the "fake" proxy object (aka the real object)
315 
316 /* @ignore */
317 @implementation _CPKVOProxy : CPObject
318 {
319  id _targetObject;
320  Class _nativeClass;
321  CPDictionary _changesForKey;
322  CPDictionary _nestingForKey;
323  Object _observersForKey;
324  int _observersForKeyLength;
325  CPSet _replacedKeys;
326 
327  // TODO: Remove this line when granular notifications are implemented
328  BOOL _adding;
329 }
330 
331 + (id)proxyForObject:(CPObject)anObject
332 {
333  var proxy = anObject[KVOProxyKey];
334 
335  if (proxy)
336  return proxy;
337 
338  return [[self alloc] initWithTarget:anObject];
339 }
340 
341 - (id)initWithTarget:(id)aTarget
342 {
343  if (self = [super init])
344  {
345  _targetObject = aTarget;
346  _nativeClass = [aTarget class];
347  _observersForKey = {};
348  _changesForKey = {};
349  _nestingForKey = {};
350  _observersForKeyLength = 0;
351 
352  [self _replaceClass];
353  aTarget[KVOProxyKey] = self;
354  }
355  return self;
356 }
357 
358 - (void)_replaceClass
359 {
360  var currentClass = _nativeClass,
361  kvoClassName = "$KVO_" + class_getName(_nativeClass),
362  existingKVOClass = objj_lookUpClass(kvoClassName);
363 
364  if (existingKVOClass)
365  {
366  _targetObject.isa = existingKVOClass;
367  _replacedKeys = existingKVOClass._replacedKeys;
368  return;
369  }
370 
371  var kvoClass = objj_allocateClassPair(currentClass, kvoClassName);
372 
373  objj_registerClassPair(kvoClass);
374 
375  _replacedKeys = [CPSet set];
376  kvoClass._replacedKeys = _replacedKeys;
377 
378  //copy in the methods from our model subclass
379  var methods = class_copyMethodList(_CPKVOModelSubclass);
380 
381  if ([_targetObject isKindOfClass:[CPDictionary class]])
382  methods = methods.concat(class_copyMethodList(_CPKVOModelDictionarySubclass));
383 
384  class_addMethods(kvoClass, methods);
385 
386  _targetObject.isa = kvoClass;
387 }
388 
389 - (void)_replaceModifiersForKey:(CPString)aKey
390 {
391  if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey])
392  return;
393 
394  [_replacedKeys addObject:aKey];
395 
396  var theClass = _nativeClass,
397  KVOClass = _targetObject.isa,
398  capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1);
399 
400  // Attribute and To-One Relationships
401  var setKey_selector = sel_getUid("set" + capitalizedKey + ":"),
402  setKey_method = class_getInstanceMethod(theClass, setKey_selector);
403 
404  if (setKey_method)
405  {
406  var setKey_method_imp = setKey_method.method_imp;
407 
408  class_addMethod(KVOClass, setKey_selector, function(self, _cmd, anObject)
409  {
410  [self willChangeValueForKey:aKey];
411 
412  setKey_method_imp(self, _cmd, anObject);
413 
414  [self didChangeValueForKey:aKey];
415  }, "");
416  }
417 
418  // FIXME: Deprecated.
419  var _setKey_selector = sel_getUid("_set" + capitalizedKey + ":"),
420  _setKey_method = class_getInstanceMethod(theClass, _setKey_selector);
421 
422  if (_setKey_method)
423  {
424  var _setKey_method_imp = _setKey_method.method_imp;
425 
426  class_addMethod(KVOClass, _setKey_selector, function(self, _cmd, anObject)
427  {
428  [self willChangeValueForKey:aKey];
429 
430  _setKey_method_imp(self, _cmd, anObject);
431 
432  [self didChangeValueForKey:aKey];
433  }, "");
434  }
435 
436  // Ordered To-Many Relationships
437  var insertObject_inKeyAtIndex_selector = sel_getUid("insertObject:in" + capitalizedKey + "AtIndex:"),
438  insertObject_inKeyAtIndex_method =
439  class_getInstanceMethod(theClass, insertObject_inKeyAtIndex_selector),
440 
441  insertKey_atIndexes_selector = sel_getUid("insert" + capitalizedKey + ":atIndexes:"),
442  insertKey_atIndexes_method =
443  class_getInstanceMethod(theClass, insertKey_atIndexes_selector),
444 
445  removeObjectFromKeyAtIndex_selector = sel_getUid("removeObjectFrom" + capitalizedKey + "AtIndex:"),
446  removeObjectFromKeyAtIndex_method =
447  class_getInstanceMethod(theClass, removeObjectFromKeyAtIndex_selector),
448 
449  removeKeyAtIndexes_selector = sel_getUid("remove" + capitalizedKey + "AtIndexes:"),
450  removeKeyAtIndexes_method = class_getInstanceMethod(theClass, removeKeyAtIndexes_selector);
451 
452  if ((insertObject_inKeyAtIndex_method || insertKey_atIndexes_method) &&
453  (removeObjectFromKeyAtIndex_method || removeKeyAtIndexes_method))
454  {
455  if (insertObject_inKeyAtIndex_method)
456  {
457  var insertObject_inKeyAtIndex_method_imp = insertObject_inKeyAtIndex_method.method_imp;
458 
459  class_addMethod(KVOClass, insertObject_inKeyAtIndex_selector, function(self, _cmd, anObject, anIndex)
460  {
461  [self willChange:CPKeyValueChangeInsertion
462  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
463  forKey:aKey];
464 
465  insertObject_inKeyAtIndex_method_imp(self, _cmd, anObject, anIndex);
466 
467  [self didChange:CPKeyValueChangeInsertion
468  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
469  forKey:aKey];
470  }, "");
471  }
472 
473  if (insertKey_atIndexes_method)
474  {
475  var insertKey_atIndexes_method_imp = insertKey_atIndexes_method.method_imp;
476 
477  class_addMethod(KVOClass, insertKey_atIndexes_selector, function(self, _cmd, objects, indexes)
478  {
479  [self willChange:CPKeyValueChangeInsertion
480  valuesAtIndexes:[indexes copy]
481  forKey:aKey];
482 
483  insertKey_atIndexes_method_imp(self, _cmd, objects, indexes);
484 
485  [self didChange:CPKeyValueChangeInsertion
486  valuesAtIndexes:[indexes copy]
487  forKey:aKey];
488  }, "");
489  }
490 
491  if (removeObjectFromKeyAtIndex_method)
492  {
493  var removeObjectFromKeyAtIndex_method_imp = removeObjectFromKeyAtIndex_method.method_imp;
494 
495  class_addMethod(KVOClass, removeObjectFromKeyAtIndex_selector, function(self, _cmd, anIndex)
496  {
497  [self willChange:CPKeyValueChangeRemoval
498  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
499  forKey:aKey];
500 
501  removeObjectFromKeyAtIndex_method_imp(self, _cmd, anIndex);
502 
503  [self didChange:CPKeyValueChangeRemoval
504  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
505  forKey:aKey];
506  }, "");
507  }
508 
509  if (removeKeyAtIndexes_method)
510  {
511  var removeKeyAtIndexes_method_imp = removeKeyAtIndexes_method.method_imp;
512 
513  class_addMethod(KVOClass, removeKeyAtIndexes_selector, function(self, _cmd, indexes)
514  {
515  [self willChange:CPKeyValueChangeRemoval
516  valuesAtIndexes:[indexes copy]
517  forKey:aKey];
518 
519  removeKeyAtIndexes_method_imp(self, _cmd, indexes);
520 
521  [self didChange:CPKeyValueChangeRemoval
522  valuesAtIndexes:[indexes copy]
523  forKey:aKey];
524  }, "");
525  }
526 
527  // These are optional.
528  var replaceObjectInKeyAtIndex_withObject_selector =
529  sel_getUid("replaceObjectIn" + capitalizedKey + "AtIndex:withObject:"),
530  replaceObjectInKeyAtIndex_withObject_method =
531  class_getInstanceMethod(theClass, replaceObjectInKeyAtIndex_withObject_selector);
532 
533  if (replaceObjectInKeyAtIndex_withObject_method)
534  {
535  var replaceObjectInKeyAtIndex_withObject_method_imp =
536  replaceObjectInKeyAtIndex_withObject_method.method_imp;
537 
538  class_addMethod(KVOClass, replaceObjectInKeyAtIndex_withObject_selector,
539  function(self, _cmd, anIndex, anObject)
540  {
541  [self willChange:CPKeyValueChangeReplacement
542  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
543  forKey:aKey];
544 
545  replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, anIndex, anObject);
546 
547  [self didChange:CPKeyValueChangeReplacement
548  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
549  forKey:aKey];
550  }, "");
551  }
552 
553  var replaceKeyAtIndexes_withKey_selector =
554  sel_getUid("replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":"),
555  replaceKeyAtIndexes_withKey_method =
556  class_getInstanceMethod(theClass, replaceKeyAtIndexes_withKey_selector);
557 
558  if (replaceKeyAtIndexes_withKey_method)
559  {
560  var replaceKeyAtIndexes_withKey_method_imp = replaceKeyAtIndexes_withKey_method.method_imp;
561 
562  class_addMethod(KVOClass, replaceKeyAtIndexes_withKey_selector, function(self, _cmd, indexes, objects)
563  {
564  [self willChange:CPKeyValueChangeReplacement
565  valuesAtIndexes:[indexes copy]
566  forKey:aKey];
567 
568  replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, indexes, objects);
569 
570  [self didChange:CPKeyValueChangeReplacement
571  valuesAtIndexes:[indexes copy]
572  forKey:aKey];
573  }, "");
574  }
575  }
576 
577  // Unordered To-Many Relationships
578  var addKeyObject_selector = sel_getUid("add" + capitalizedKey + "Object:"),
579  addKeyObject_method = class_getInstanceMethod(theClass, addKeyObject_selector),
580 
581  addKey_selector = sel_getUid("add" + capitalizedKey + ":"),
582  addKey_method = class_getInstanceMethod(theClass, addKey_selector),
583 
584  removeKeyObject_selector = sel_getUid("remove" + capitalizedKey + "Object:"),
585  removeKeyObject_method = class_getInstanceMethod(theClass, removeKeyObject_selector),
586 
587  removeKey_selector = sel_getUid("remove" + capitalizedKey + ":"),
588  removeKey_method = class_getInstanceMethod(theClass, removeKey_selector);
589 
590  if ((addKeyObject_method || addKey_method) && (removeKeyObject_method || removeKey_method))
591  {
592  if (addKeyObject_method)
593  {
594  var addKeyObject_method_imp = addKeyObject_method.method_imp;
595 
596  class_addMethod(KVOClass, addKeyObject_selector, function(self, _cmd, anObject)
597  {
598  [self willChangeValueForKey:aKey
599  withSetMutation:CPKeyValueUnionSetMutation
600  usingObjects:[CPSet setWithObject:anObject]];
601 
602  addKeyObject_method_imp(self, _cmd, anObject);
603 
604  [self didChangeValueForKey:aKey
605  withSetMutation:CPKeyValueUnionSetMutation
606  usingObjects:[CPSet setWithObject:anObject]];
607  }, "");
608  }
609 
610  if (addKey_method)
611  {
612  var addKey_method_imp = addKey_method.method_imp;
613 
614  class_addMethod(KVOClass, addKey_selector, function(self, _cmd, objects)
615  {
616  [self willChangeValueForKey:aKey
617  withSetMutation:CPKeyValueUnionSetMutation
618  usingObjects:[objects copy]];
619 
620  addKey_method_imp(self, _cmd, objects);
621 
622  [self didChangeValueForKey:aKey
623  withSetMutation:CPKeyValueUnionSetMutation
624  usingObjects:[objects copy]];
625  }, "");
626  }
627 
628  if (removeKeyObject_method)
629  {
630  var removeKeyObject_method_imp = removeKeyObject_method.method_imp;
631 
632  class_addMethod(KVOClass, removeKeyObject_selector, function(self, _cmd, anObject)
633  {
634  [self willChangeValueForKey:aKey
635  withSetMutation:CPKeyValueMinusSetMutation
636  usingObjects:[CPSet setWithObject:anObject]];
637 
638  removeKeyObject_method_imp(self, _cmd, anObject);
639 
640  [self didChangeValueForKey:aKey
641  withSetMutation:CPKeyValueMinusSetMutation
642  usingObjects:[CPSet setWithObject:anObject]];
643  }, "");
644  }
645 
646  if (removeKey_method)
647  {
648  var removeKey_method_imp = removeKey_method.method_imp;
649 
650  class_addMethod(KVOClass, removeKey_selector, function(self, _cmd, objects)
651  {
652  [self willChangeValueForKey:aKey
653  withSetMutation:CPKeyValueMinusSetMutation
654  usingObjects:[objects copy]];
655 
656  removeKey_method_imp(self, _cmd, objects);
657 
658  [self didChangeValueForKey:aKey
659  withSetMutation:CPKeyValueMinusSetMutation
660  usingObjects:[objects copy]];
661  }, "");
662  }
663 
664  // intersect<Key>: is optional.
665  var intersectKey_selector = sel_getUid("intersect" + capitalizedKey + ":"),
666  intersectKey_method = class_getInstanceMethod(theClass, intersectKey_selector);
667 
668  if (intersectKey_method)
669  {
670  var intersectKey_method_imp = intersectKey_method.method_imp;
671 
672  class_addMethod(KVOClass, intersectKey_selector, function(self, _cmd, aSet)
673  {
674  [self willChangeValueForKey:aKey
675  withSetMutation:CPKeyValueIntersectSetMutation
676  usingObjects:[aSet copy]];
677 
678  intersectKey_method_imp(self, _cmd, aSet);
679 
680  [self didChangeValueForKey:aKey
681  withSetMutation:CPKeyValueIntersectSetMutation
682  usingObjects:[aSet copy]];
683  }, "");
684  }
685  }
686 
687  var affectingKeys = [[_nativeClass keyPathsForValuesAffectingValueForKey:aKey] allObjects],
688  affectingKeysCount = affectingKeys ? affectingKeys.length : 0;
689 
690  if (!affectingKeysCount)
691  return;
692 
693  var dependentKeysForClass = _nativeClass[DependentKeysKey];
694 
695  if (!dependentKeysForClass)
696  {
697  dependentKeysForClass = {};
698  _nativeClass[DependentKeysKey] = dependentKeysForClass;
699  }
700 
701  while (affectingKeysCount--)
702  {
703  var affectingKey = affectingKeys[affectingKeysCount],
704  affectedKeys = dependentKeysForClass[affectingKey];
705 
706  if (!affectedKeys)
707  {
708  affectedKeys = [CPSet new];
709  dependentKeysForClass[affectingKey] = affectedKeys;
710  }
711 
712  [affectedKeys addObject:aKey];
713 
714  //observe key paths of objects other then ourselves, so we are notified of the changes
715  //use CPKeyValueObservingOptionPrior to ensure proper wrapping around changes
716  //so CPKeyValueObservingOptionPrior and CPKeyValueObservingOptionOld can be fulfilled even for dependent keys
717  if (affectingKey.indexOf(@".") !== -1)
718  [_targetObject addObserver:self forKeyPath:affectingKey options:CPKeyValueObservingOptionPrior | kvoNewAndOld context:nil];
719  else
720  [self _replaceModifiersForKey:affectingKey];
721  }
722 }
723 
724 - (void)observeValueForKeyPath:(CPString)theKeyPath ofObject:(id)theObject change:(CPDictionary)theChanges context:(id)theContext
725 {
726  // Fire change events for the dependent keys
727  var dependentKeysForClass = _nativeClass[DependentKeysKey],
728  dependantKeys = [dependentKeysForClass[theKeyPath] allObjects],
729  isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
730 
731  for (var i = 0; i < [dependantKeys count]; i++)
732  {
733  var dependantKey = [dependantKeys objectAtIndex:i];
734  [self _sendNotificationsForKey:dependantKey changeOptions:theChanges isBefore:isBeforeFlag];
735  }
736 }
737 
738 - (void)_addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(CPKeyValueObservingOptions)options context:(id)aContext
739 {
740  if (!anObserver)
741  return;
742 
743  var forwarder = nil;
744 
745  if (aPath.indexOf('.') !== CPNotFound && aPath.charAt(0) !== '@')
746  forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
747  else
748  [self _replaceModifiersForKey:aPath];
749 
750  var observers = _observersForKey[aPath];
751 
752  if (!observers)
753  {
754  observers = @{};
755  _observersForKey[aPath] = observers;
756  _observersForKeyLength++;
757  }
758 
759  [observers setObject:_CPKVOInfoMake(anObserver, options, aContext, forwarder) forKey:[anObserver UID]];
760 
761  if (options & CPKeyValueObservingOptionInitial)
762  {
763  var newValue = [_targetObject valueForKeyPath:aPath];
764 
765  if (newValue === nil || newValue === undefined)
766  newValue = [CPNull null];
767 
768  var changes = @{ CPKeyValueChangeNewKey: newValue };
769  [anObserver observeValueForKeyPath:aPath ofObject:_targetObject change:changes context:aContext];
770  }
771 }
772 
773 - (void)_removeObserver:(id)anObserver forKeyPath:(CPString)aPath
774 {
775  var observers = _observersForKey[aPath];
776 
777  if (!observers)
778  {
779  // TODO: Remove this line when granular notifications are implemented
780  if (!_adding)
781  CPLog.warn(@"Cannot remove an observer %@ for the key path \"%@\" from %@ because it is not registered as an observer.", _targetObject, aPath, anObserver);
782 
783  return;
784  }
785 
786  if (aPath.indexOf('.') != CPNotFound)
787  {
788  // During cib instantiation, it is possible for the forwarder to not yet be available,
789  // so we have to check for nil.
790  var observer = [observers objectForKey:[anObserver UID]],
791  forwarder = observer ? observer.forwarder : nil;
792 
793  [forwarder finalize];
794  }
795 
796  [observers removeObjectForKey:[anObserver UID]];
797 
798  if (![observers count])
799  {
800  _observersForKeyLength--;
801  delete _observersForKey[aPath];
802  }
803 
804  if (!_observersForKeyLength)
805  {
806  _targetObject.isa = _nativeClass; //restore the original class
807  delete _targetObject[KVOProxyKey];
808  }
809 }
810 
811 //FIXME: We do not compute and cache if CPKeyValueObservingOptionOld is needed, so we may do unnecessary work
812 
813 - (void)_sendNotificationsForKey:(CPString)aKey changeOptions:(CPDictionary)changeOptions isBefore:(BOOL)isBefore
814 {
815  var changes = _changesForKey[aKey];
816 
817  if (isBefore)
818  {
819  if (changes)
820  {
821  // "willChange:X" nesting.
822  var level = _nestingForKey[aKey];
823 
824  if (!level)
825  [CPException raise:CPInternalInconsistencyException reason:@"_changesForKey without _nestingForKey"];
826 
827  _nestingForKey[aKey] = level + 1;
828  // Only notify on the first willChange..., silently note any following nested calls.
829  return;
830  }
831 
832  _nestingForKey[aKey] = 1;
833 
834  changes = changeOptions;
835 
836  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
837  setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
838 
839  if (setMutationKind)
840  {
841  var setMutationObjects = [changes[_CPKeyValueChangeSetMutationObjectsKey] copy],
842  setExistingObjects = [[_targetObject valueForKey: aKey] copy];
843 
844  if (setMutationKind == CPKeyValueMinusSetMutation)
845  {
846  [setExistingObjects intersectSet: setMutationObjects];
847  [changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
848  }
849  else if (setMutationKind === CPKeyValueIntersectSetMutation || setMutationKind === CPKeyValueSetSetMutation)
850  {
851  [setExistingObjects minusSet: setMutationObjects];
852  [changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
853  }
854 
855  //for unordered to-many relationships (CPSet) even new values can only be calculated before!!!
856  if (setMutationKind === CPKeyValueUnionSetMutation || setMutationKind === CPKeyValueSetSetMutation)
857  {
858  [setMutationObjects minusSet: setExistingObjects];
859  //hide new value (for CPKeyValueObservingOptionPrior messages)
860  //as long as "didChangeValue..." is not yet called!
861  changes[_CPKeyValueChangeSetMutationNewValueKey] = setMutationObjects;
862  }
863  }
864  else if (indexes)
865  {
866  var type = [changes objectForKey:CPKeyValueChangeKindKey];
867 
868  // for ordered to-many relationships, oldvalue is only sensible for replace and remove
869  if (type === CPKeyValueChangeReplacement || type === CPKeyValueChangeRemoval)
870  {
871  //FIXME: do we need to go through and replace "" with CPNull?
872  var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
873  [changes setValue:newValues forKey:CPKeyValueChangeOldKey];
874  }
875  }
876  else
877  {
878  var oldValue = [_targetObject valueForKey:aKey];
879 
880  if (oldValue === nil || oldValue === undefined)
881  oldValue = [CPNull null];
882 
883  [changes setObject:oldValue forKey:CPKeyValueChangeOldKey];
884  }
885 
886  [changes setObject:1 forKey:CPKeyValueChangeNotificationIsPriorKey];
887 
888  _changesForKey[aKey] = changes;
889  }
890  else
891  {
892  var level = _nestingForKey[aKey];
893 
894  if (!changes || !level)
895  {
896  if (_targetObject._willChangeMessageCounter && _targetObject._willChangeMessageCounter[aKey])
897  {
898  // Close unobserved willChange for a given key.
899  _targetObject._willChangeMessageCounter[aKey] -= 1;
900 
901  if (!_targetObject._willChangeMessageCounter[aKey])
902  delete _targetObject._willChangeMessageCounter[aKey];
903 
904  return;
905  }
906  else
907  [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
908  }
909 
910  _nestingForKey[aKey] = level - 1;
911 
912  if (level - 1 > 0)
913  {
914  // willChange... was called multiple times. Only fire observation notifications when
915  // didChange... has been called an equal number of times.
916  return;
917  }
918 
919  delete _nestingForKey[aKey];
920 
921  [changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
922 
923  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
924  setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
925 
926  if (setMutationKind)
927  {
928  //old and new values for unordered to-many relationships can only be calculated before
929  //set recalculated hidden new value as soon as "didChangeValue..." is called!
930  var newValue = changes[_CPKeyValueChangeSetMutationNewValueKey];
931  [changes setValue:newValue forKey:CPKeyValueChangeNewKey];
932 
933  //delete hidden values
934  delete changes[_CPKeyValueChangeSetMutationNewValueKey];
935  delete changes[_CPKeyValueChangeSetMutationObjectsKey];
936  delete changes[_CPKeyValueChangeSetMutationKindKey];
937  }
938  else if (indexes)
939  {
940  var type = [changes objectForKey:CPKeyValueChangeKindKey];
941 
942  // for ordered to-many relationships, newvalue is only sensible for replace and insert
944  {
945  //FIXME: do we need to go through and replace "" with CPNull?
946  var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
947  [changes setValue:newValues forKey:CPKeyValueChangeNewKey];
948  }
949  }
950  else
951  {
952  var newValue = [_targetObject valueForKey:aKey];
953 
954  if (newValue === nil || newValue === undefined)
955  newValue = [CPNull null];
956 
957  [changes setObject:newValue forKey:CPKeyValueChangeNewKey];
958  }
959 
960  delete _changesForKey[aKey];
961  }
962 
963  var observers = [_observersForKey[aKey] allValues],
964  count = observers ? observers.length : 0;
965 
966  while (count--)
967  {
968  var observerInfo = observers[count];
969 
970  if (!isBefore || (observerInfo.options & CPKeyValueObservingOptionPrior))
971  [observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:changes context:observerInfo.context];
972  }
973 
974  var dependentKeysMap = _nativeClass[DependentKeysKey];
975 
976  if (!dependentKeysMap)
977  return;
978 
979  var dependentKeyPaths = [dependentKeysMap[aKey] allObjects];
980 
981  if (!dependentKeyPaths)
982  return;
983 
984  var index = 0,
985  count = [dependentKeyPaths count];
986 
987  for (; index < count; ++index)
988  {
989  var keyPath = dependentKeyPaths[index];
990 
991  [self _sendNotificationsForKey:keyPath
992  changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
993  isBefore:isBefore];
994  }
995 }
996 
997 @end
998 @implementation _CPKVOModelSubclass : CPObject
999 {
1000  id __doxygen__;
1001 }
1002 
1003 - (void)willChangeValueForKey:(CPString)aKey
1004 {
1005  var superClass = [self class],
1006  methodSelector = @selector(willChangeValueForKey:),
1007  methodImp = class_getMethodImplementation(superClass, methodSelector);
1008 
1009  methodImp(self, methodSelector, aKey);
1010 
1011  if (!aKey)
1012  return;
1013 
1014  var changeOptions = @{ CPKeyValueChangeKindKey: CPKeyValueChangeSetting };
1015 
1016  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1017 }
1018 
1019 - (void)didChangeValueForKey:(CPString)aKey
1020 {
1021  var superClass = [self class],
1022  methodSelector = @selector(didChangeValueForKey:),
1023  methodImp = class_getMethodImplementation(superClass, methodSelector);
1024 
1025  methodImp(self, methodSelector, aKey);
1026 
1027  if (!aKey)
1028  return;
1029 
1030  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1031 }
1032 
1033 - (void)willChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
1034 {
1035  var superClass = [self class],
1036  methodSelector = @selector(willChange:valuesAtIndexes:forKey:),
1037  methodImp = class_getMethodImplementation(superClass, methodSelector);
1038 
1039  methodImp(self, methodSelector, change, indexes, aKey);
1040 
1041  if (!aKey)
1042  return;
1043 
1044  var changeOptions = @{ CPKeyValueChangeKindKey: change, CPKeyValueChangeIndexesKey: indexes };
1045 
1046  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1047 }
1048 
1049 - (void)didChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
1050 {
1051  var superClass = [self class],
1052  methodSelector = @selector(didChange:valuesAtIndexes:forKey:),
1053  methodImp = class_getMethodImplementation(superClass, methodSelector);
1054 
1055  methodImp(self, methodSelector, change, indexes, aKey);
1056 
1057  if (!aKey)
1058  return;
1059 
1060  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1061 }
1062 
1063 - (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
1064 {
1065  var superClass = [self class],
1066  methodSelector = @selector(willChangeValueForKey:withSetMutation:usingObjects:),
1067  methodImp = class_getMethodImplementation(superClass, methodSelector);
1068 
1069  methodImp(self, methodSelector, aKey, mutationKind, objects);
1070 
1071  if (!aKey)
1072  return;
1073 
1074  var changeKind = _changeKindForSetMutationKind(mutationKind),
1075  changeOptions = @{ CPKeyValueChangeKindKey: changeKind };
1076 
1077  //set hidden change-dict ivars to support unordered to-many relationships
1078  changeOptions[_CPKeyValueChangeSetMutationObjectsKey] = objects;
1079  changeOptions[_CPKeyValueChangeSetMutationKindKey] = mutationKind;
1080 
1081  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1082 }
1083 
1084 - (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
1085 {
1086  var superClass = [self class],
1087  methodSelector = @selector(didChangeValueForKey:withSetMutation:usingObjects:),
1088  methodImp = class_getMethodImplementation(superClass, methodSelector);
1089 
1090  methodImp(self, methodSelector, aKey, mutationKind, objects);
1091 
1092  if (!aKey)
1093  return;
1094 
1095  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1096 }
1097 
1098 - (Class)class
1099 {
1100  return self[KVOProxyKey]._nativeClass;
1101 }
1102 
1103 - (Class)superclass
1104 {
1105  return [[self class] superclass];
1106 }
1107 
1108 - (BOOL)isKindOfClass:(Class)aClass
1109 {
1110  return [[self class] isSubclassOfClass:aClass];
1111 }
1112 
1113 - (BOOL)isMemberOfClass:(Class)aClass
1114 {
1115  return [self class] == aClass;
1116 }
1117 
1118 - (CPString)className
1119 {
1120  return [self class].name;
1121 }
1122 
1123 @end
1124 @implementation _CPKVOModelDictionarySubclass : CPObject
1125 {
1126  id __doxygen__;
1127 }
1128 
1129 - (void)removeAllObjects
1130 {
1131  var keys = [self allKeys],
1132  count = [keys count],
1133  i = 0;
1134 
1135  for (; i < count; i++)
1136  [self willChangeValueForKey:keys[i]];
1137 
1138  var superClass = [self class],
1139  methodSelector = @selector(removeAllObjects),
1140  methodImp = class_getMethodImplementation(superClass, methodSelector);
1141 
1142  methodImp(self, methodSelector);
1143 
1144  for (i = 0; i < count; i++)
1145  [self didChangeValueForKey:keys[i]];
1146 }
1147 
1148 - (void)removeObjectForKey:(id)aKey
1149 {
1150  [self willChangeValueForKey:aKey];
1151 
1152  var superClass = [self class],
1153  methodSelector = @selector(removeObjectForKey:),
1154  methodImp = class_getMethodImplementation(superClass, methodSelector);
1155 
1156  methodImp(self, methodSelector, aKey);
1157 
1158  [self didChangeValueForKey:aKey];
1159 }
1160 
1161 - (void)setObject:(id)anObject forKey:(id)aKey
1162 {
1163  [self willChangeValueForKey:aKey];
1164 
1165  var superClass = [self class],
1166  methodSelector = @selector(setObject:forKey:),
1167  methodImp = class_getMethodImplementation(superClass, methodSelector);
1168 
1169  methodImp(self, methodSelector, anObject, aKey);
1170 
1171  [self didChangeValueForKey:aKey];
1172 }
1173 
1174 @end
1175 
1176 @implementation _CPKVOForwardingObserver : CPObject
1177 {
1178  id _object;
1179  id _observer;
1180  id _context;
1181  unsigned _options;
1182  //a.b
1183  CPString _firstPart; //a
1184  CPString _secondPart; //b
1185 
1186  id _value;
1187 }
1188 
1189 - (id)initWithKeyPath:(CPString)aKeyPath object:(id)anObject observer:(id)anObserver options:(unsigned)options context:(id)aContext
1190 {
1191  self = [super init];
1192 
1193  _context = aContext;
1194  _observer = anObserver;
1195  _object = anObject;
1196  _options = options;
1197 
1198  var dotIndex = aKeyPath.indexOf('.');
1199 
1200  if (dotIndex === CPNotFound)
1201  [CPException raise:CPInvalidArgumentException reason:"Created _CPKVOForwardingObserver without compound key path: " + aKeyPath];
1202 
1203  _firstPart = aKeyPath.substring(0, dotIndex);
1204  _secondPart = aKeyPath.substring(dotIndex + 1);
1205 
1206  //become an observer of the first part of our key (a)
1207  [_object addObserver:self forKeyPath:_firstPart options:_options context:nil];
1208 
1209  //the current value of a (not the value of a.b)
1210  _value = [_object valueForKey:_firstPart];
1211 
1212  if (_value)
1213  [_value addObserver:self forKeyPath:_secondPart options:_options context:nil]; //we're observing b on current a
1214 
1215  return self;
1216 }
1217 
1218 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
1219 {
1220  if (aKeyPath === _firstPart)
1221  {
1222  var oldValue = [_value valueForKeyPath:_secondPart],
1223  newValue = [_object valueForKeyPath:_firstPart + "." + _secondPart],
1224  pathChanges = @{
1225  CPKeyValueChangeNewKey: newValue ? newValue : [CPNull null],
1226  CPKeyValueChangeOldKey: oldValue ? oldValue : [CPNull null],
1228  };
1229 
1230  [_observer observeValueForKeyPath:_firstPart + "." + _secondPart ofObject:_object change:pathChanges context:_context];
1231 
1232  //since a has changed, we should remove ourselves as an observer of the old a, and observe the new one
1233  if (_value)
1234  [_value removeObserver:self forKeyPath:_secondPart];
1235 
1236  _value = [_object valueForKey:_firstPart];
1237 
1238  if (_value)
1239  [_value addObserver:self forKeyPath:_secondPart options:_options context:nil];
1240  }
1241  else
1242  {
1243  //a is the same, but a.b has changed -- nothing to do but forward this message along
1244  [_observer observeValueForKeyPath:_firstPart + "." + aKeyPath ofObject:_object change:changes context:_context];
1245  }
1246 }
1247 
1248 - (void)finalize
1249 {
1250  if (_value)
1251  [_value removeObserver:self forKeyPath:_secondPart];
1252 
1253  [_object removeObserver:self forKeyPath:_firstPart];
1254 
1255  _object = nil;
1256  _observer = nil;
1257  _context = nil;
1258  _value = nil;
1259 }
1260 
1261 @end
1262 
1263 var _CPKVOInfoMake = function(anObserver, theOptions, aContext, aForwarder)
1264 {
1265  return {
1266  observer: anObserver,
1267  options: theOptions,
1268  context: aContext,
1269  forwarder: aForwarder
1270  };
1271 };
1272