API  0.9.8
 All Classes Files Functions Variables Typedefs 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  CPDictionary _minOptionsForKey;
324  Object _observersForKey;
325  int _observersForKeyLength;
326  CPSet _replacedKeys;
327 
328  // TODO: Remove this line when granular notifications are implemented
329  BOOL _adding;
330 }
331 
332 + (id)proxyForObject:(CPObject)anObject
333 {
334  var proxy = anObject[KVOProxyKey];
335 
336  if (proxy)
337  return proxy;
338 
339  return [[self alloc] initWithTarget:anObject];
340 }
341 
342 - (id)initWithTarget:(id)aTarget
343 {
344  if (self = [super init])
345  {
346  _targetObject = aTarget;
347  _nativeClass = [aTarget class];
348  _observersForKey = {};
349  _changesForKey = {};
350  _nestingForKey = {};
351  _minOptionsForKey = {};
352  _observersForKeyLength = 0;
353 
354  [self _replaceClass];
355  aTarget[KVOProxyKey] = self;
356  }
357  return self;
358 }
359 
360 - (void)_replaceClass
361 {
362  var currentClass = _nativeClass,
363  kvoClassName = "$KVO_" + class_getName(_nativeClass),
364  existingKVOClass = objj_lookUpClass(kvoClassName);
365 
366  if (existingKVOClass)
367  {
368  _targetObject.isa = existingKVOClass;
369  _replacedKeys = existingKVOClass._replacedKeys;
370  return;
371  }
372 
373  var kvoClass = objj_allocateClassPair(currentClass, kvoClassName);
374 
375  objj_registerClassPair(kvoClass);
376 
377  _replacedKeys = [CPSet set];
378  kvoClass._replacedKeys = _replacedKeys;
379 
380  //copy in the methods from our model subclass
381  var methods = class_copyMethodList(_CPKVOModelSubclass);
382 
383  if ([_targetObject isKindOfClass:[CPDictionary class]])
384  methods = methods.concat(class_copyMethodList(_CPKVOModelDictionarySubclass));
385 
386  class_addMethods(kvoClass, methods);
387 
388  _targetObject.isa = kvoClass;
389 }
390 
391 - (void)_replaceModifiersForKey:(CPString)aKey
392 {
393  if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey])
394  return;
395 
396  [_replacedKeys addObject:aKey];
397 
398  var theClass = _nativeClass,
399  KVOClass = _targetObject.isa,
400  capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1);
401 
402  // Attribute and To-One Relationships
403  var setKey_selector = sel_getUid("set" + capitalizedKey + ":"),
404  setKey_method = class_getInstanceMethod(theClass, setKey_selector);
405 
406  if (setKey_method)
407  {
408  var setKey_method_imp = setKey_method.method_imp;
409 
410  class_addMethod(KVOClass, setKey_selector, function(self, _cmd, anObject)
411  {
412  [self willChangeValueForKey:aKey];
413 
414  setKey_method_imp(self, _cmd, anObject);
415 
416  [self didChangeValueForKey:aKey];
417  }, "");
418  }
419 
420  // FIXME: Deprecated.
421  var _setKey_selector = sel_getUid("_set" + capitalizedKey + ":"),
422  _setKey_method = class_getInstanceMethod(theClass, _setKey_selector);
423 
424  if (_setKey_method)
425  {
426  var _setKey_method_imp = _setKey_method.method_imp;
427 
428  class_addMethod(KVOClass, _setKey_selector, function(self, _cmd, anObject)
429  {
430  [self willChangeValueForKey:aKey];
431 
432  _setKey_method_imp(self, _cmd, anObject);
433 
434  [self didChangeValueForKey:aKey];
435  }, "");
436  }
437 
438  // Ordered To-Many Relationships
439  var insertObject_inKeyAtIndex_selector = sel_getUid("insertObject:in" + capitalizedKey + "AtIndex:"),
440  insertObject_inKeyAtIndex_method =
441  class_getInstanceMethod(theClass, insertObject_inKeyAtIndex_selector),
442 
443  insertKey_atIndexes_selector = sel_getUid("insert" + capitalizedKey + ":atIndexes:"),
444  insertKey_atIndexes_method =
445  class_getInstanceMethod(theClass, insertKey_atIndexes_selector),
446 
447  removeObjectFromKeyAtIndex_selector = sel_getUid("removeObjectFrom" + capitalizedKey + "AtIndex:"),
448  removeObjectFromKeyAtIndex_method =
449  class_getInstanceMethod(theClass, removeObjectFromKeyAtIndex_selector),
450 
451  removeKeyAtIndexes_selector = sel_getUid("remove" + capitalizedKey + "AtIndexes:"),
452  removeKeyAtIndexes_method = class_getInstanceMethod(theClass, removeKeyAtIndexes_selector);
453 
454  if ((insertObject_inKeyAtIndex_method || insertKey_atIndexes_method) &&
455  (removeObjectFromKeyAtIndex_method || removeKeyAtIndexes_method))
456  {
457  if (insertObject_inKeyAtIndex_method)
458  {
459  var insertObject_inKeyAtIndex_method_imp = insertObject_inKeyAtIndex_method.method_imp;
460 
461  class_addMethod(KVOClass, insertObject_inKeyAtIndex_selector, function(self, _cmd, anObject, anIndex)
462  {
463  [self willChange:CPKeyValueChangeInsertion
464  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
465  forKey:aKey];
466 
467  insertObject_inKeyAtIndex_method_imp(self, _cmd, anObject, anIndex);
468 
469  [self didChange:CPKeyValueChangeInsertion
470  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
471  forKey:aKey];
472  }, "");
473  }
474 
475  if (insertKey_atIndexes_method)
476  {
477  var insertKey_atIndexes_method_imp = insertKey_atIndexes_method.method_imp;
478 
479  class_addMethod(KVOClass, insertKey_atIndexes_selector, function(self, _cmd, objects, indexes)
480  {
481  [self willChange:CPKeyValueChangeInsertion
482  valuesAtIndexes:[indexes copy]
483  forKey:aKey];
484 
485  insertKey_atIndexes_method_imp(self, _cmd, objects, indexes);
486 
487  [self didChange:CPKeyValueChangeInsertion
488  valuesAtIndexes:[indexes copy]
489  forKey:aKey];
490  }, "");
491  }
492 
493  if (removeObjectFromKeyAtIndex_method)
494  {
495  var removeObjectFromKeyAtIndex_method_imp = removeObjectFromKeyAtIndex_method.method_imp;
496 
497  class_addMethod(KVOClass, removeObjectFromKeyAtIndex_selector, function(self, _cmd, anIndex)
498  {
499  [self willChange:CPKeyValueChangeRemoval
500  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
501  forKey:aKey];
502 
503  removeObjectFromKeyAtIndex_method_imp(self, _cmd, anIndex);
504 
505  [self didChange:CPKeyValueChangeRemoval
506  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
507  forKey:aKey];
508  }, "");
509  }
510 
511  if (removeKeyAtIndexes_method)
512  {
513  var removeKeyAtIndexes_method_imp = removeKeyAtIndexes_method.method_imp;
514 
515  class_addMethod(KVOClass, removeKeyAtIndexes_selector, function(self, _cmd, indexes)
516  {
517  [self willChange:CPKeyValueChangeRemoval
518  valuesAtIndexes:[indexes copy]
519  forKey:aKey];
520 
521  removeKeyAtIndexes_method_imp(self, _cmd, indexes);
522 
523  [self didChange:CPKeyValueChangeRemoval
524  valuesAtIndexes:[indexes copy]
525  forKey:aKey];
526  }, "");
527  }
528 
529  // These are optional.
530  var replaceObjectInKeyAtIndex_withObject_selector =
531  sel_getUid("replaceObjectIn" + capitalizedKey + "AtIndex:withObject:"),
532  replaceObjectInKeyAtIndex_withObject_method =
533  class_getInstanceMethod(theClass, replaceObjectInKeyAtIndex_withObject_selector);
534 
535  if (replaceObjectInKeyAtIndex_withObject_method)
536  {
537  var replaceObjectInKeyAtIndex_withObject_method_imp =
538  replaceObjectInKeyAtIndex_withObject_method.method_imp;
539 
540  class_addMethod(KVOClass, replaceObjectInKeyAtIndex_withObject_selector,
541  function(self, _cmd, anIndex, anObject)
542  {
543  [self willChange:CPKeyValueChangeReplacement
544  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
545  forKey:aKey];
546 
547  replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, anIndex, anObject);
548 
549  [self didChange:CPKeyValueChangeReplacement
550  valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
551  forKey:aKey];
552  }, "");
553  }
554 
555  var replaceKeyAtIndexes_withKey_selector =
556  sel_getUid("replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":"),
557  replaceKeyAtIndexes_withKey_method =
558  class_getInstanceMethod(theClass, replaceKeyAtIndexes_withKey_selector);
559 
560  if (replaceKeyAtIndexes_withKey_method)
561  {
562  var replaceKeyAtIndexes_withKey_method_imp = replaceKeyAtIndexes_withKey_method.method_imp;
563 
564  class_addMethod(KVOClass, replaceKeyAtIndexes_withKey_selector, function(self, _cmd, indexes, objects)
565  {
566  [self willChange:CPKeyValueChangeReplacement
567  valuesAtIndexes:[indexes copy]
568  forKey:aKey];
569 
570  replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, indexes, objects);
571 
572  [self didChange:CPKeyValueChangeReplacement
573  valuesAtIndexes:[indexes copy]
574  forKey:aKey];
575  }, "");
576  }
577  }
578 
579  // Unordered To-Many Relationships
580  var addKeyObject_selector = sel_getUid("add" + capitalizedKey + "Object:"),
581  addKeyObject_method = class_getInstanceMethod(theClass, addKeyObject_selector),
582 
583  addKey_selector = sel_getUid("add" + capitalizedKey + ":"),
584  addKey_method = class_getInstanceMethod(theClass, addKey_selector),
585 
586  removeKeyObject_selector = sel_getUid("remove" + capitalizedKey + "Object:"),
587  removeKeyObject_method = class_getInstanceMethod(theClass, removeKeyObject_selector),
588 
589  removeKey_selector = sel_getUid("remove" + capitalizedKey + ":"),
590  removeKey_method = class_getInstanceMethod(theClass, removeKey_selector);
591 
592  if ((addKeyObject_method || addKey_method) && (removeKeyObject_method || removeKey_method))
593  {
594  if (addKeyObject_method)
595  {
596  var addKeyObject_method_imp = addKeyObject_method.method_imp;
597 
598  class_addMethod(KVOClass, addKeyObject_selector, function(self, _cmd, anObject)
599  {
600  [self willChangeValueForKey:aKey
601  withSetMutation:CPKeyValueUnionSetMutation
602  usingObjects:[CPSet setWithObject:anObject]];
603 
604  addKeyObject_method_imp(self, _cmd, anObject);
605 
606  [self didChangeValueForKey:aKey
607  withSetMutation:CPKeyValueUnionSetMutation
608  usingObjects:[CPSet setWithObject:anObject]];
609  }, "");
610  }
611 
612  if (addKey_method)
613  {
614  var addKey_method_imp = addKey_method.method_imp;
615 
616  class_addMethod(KVOClass, addKey_selector, function(self, _cmd, objects)
617  {
618  [self willChangeValueForKey:aKey
619  withSetMutation:CPKeyValueUnionSetMutation
620  usingObjects:[objects copy]];
621 
622  addKey_method_imp(self, _cmd, objects);
623 
624  [self didChangeValueForKey:aKey
625  withSetMutation:CPKeyValueUnionSetMutation
626  usingObjects:[objects copy]];
627  }, "");
628  }
629 
630  if (removeKeyObject_method)
631  {
632  var removeKeyObject_method_imp = removeKeyObject_method.method_imp;
633 
634  class_addMethod(KVOClass, removeKeyObject_selector, function(self, _cmd, anObject)
635  {
636  [self willChangeValueForKey:aKey
637  withSetMutation:CPKeyValueMinusSetMutation
638  usingObjects:[CPSet setWithObject:anObject]];
639 
640  removeKeyObject_method_imp(self, _cmd, anObject);
641 
642  [self didChangeValueForKey:aKey
643  withSetMutation:CPKeyValueMinusSetMutation
644  usingObjects:[CPSet setWithObject:anObject]];
645  }, "");
646  }
647 
648  if (removeKey_method)
649  {
650  var removeKey_method_imp = removeKey_method.method_imp;
651 
652  class_addMethod(KVOClass, removeKey_selector, function(self, _cmd, objects)
653  {
654  [self willChangeValueForKey:aKey
655  withSetMutation:CPKeyValueMinusSetMutation
656  usingObjects:[objects copy]];
657 
658  removeKey_method_imp(self, _cmd, objects);
659 
660  [self didChangeValueForKey:aKey
661  withSetMutation:CPKeyValueMinusSetMutation
662  usingObjects:[objects copy]];
663  }, "");
664  }
665 
666  // intersect<Key>: is optional.
667  var intersectKey_selector = sel_getUid("intersect" + capitalizedKey + ":"),
668  intersectKey_method = class_getInstanceMethod(theClass, intersectKey_selector);
669 
670  if (intersectKey_method)
671  {
672  var intersectKey_method_imp = intersectKey_method.method_imp;
673 
674  class_addMethod(KVOClass, intersectKey_selector, function(self, _cmd, aSet)
675  {
676  [self willChangeValueForKey:aKey
677  withSetMutation:CPKeyValueIntersectSetMutation
678  usingObjects:[aSet copy]];
679 
680  intersectKey_method_imp(self, _cmd, aSet);
681 
682  [self didChangeValueForKey:aKey
683  withSetMutation:CPKeyValueIntersectSetMutation
684  usingObjects:[aSet copy]];
685  }, "");
686  }
687  }
688 
689  var affectingKeys = [[_nativeClass keyPathsForValuesAffectingValueForKey:aKey] allObjects],
690  affectingKeysCount = affectingKeys ? affectingKeys.length : 0;
691 
692  if (!affectingKeysCount)
693  return;
694 
695  var dependentKeysForClass = _nativeClass[DependentKeysKey];
696 
697  if (!dependentKeysForClass)
698  {
699  dependentKeysForClass = {};
700  _nativeClass[DependentKeysKey] = dependentKeysForClass;
701  }
702 
703  while (affectingKeysCount--)
704  {
705  var affectingKey = affectingKeys[affectingKeysCount],
706  affectedKeys = dependentKeysForClass[affectingKey];
707 
708  if (!affectedKeys)
709  {
710  affectedKeys = [CPSet new];
711  dependentKeysForClass[affectingKey] = affectedKeys;
712  }
713 
714  [affectedKeys addObject:aKey];
715 
716  //observe key paths of objects other then ourselves, so we are notified of the changes
717  //use CPKeyValueObservingOptionPrior to ensure proper wrapping around changes
718  //so CPKeyValueObservingOptionPrior and CPKeyValueObservingOptionOld can be fulfilled even for dependent keys
719  if (affectingKey.indexOf(@".") !== -1)
720  [_targetObject addObserver:self forKeyPath:affectingKey options:CPKeyValueObservingOptionPrior | kvoNewAndOld context:nil];
721  else
722  [self _replaceModifiersForKey:affectingKey];
723  }
724 }
725 
726 - (void)observeValueForKeyPath:(CPString)theKeyPath ofObject:(id)theObject change:(CPDictionary)theChanges context:(id)theContext
727 {
728  // Fire change events for the dependent keys
729  var dependentKeysForClass = _nativeClass[DependentKeysKey],
730  dependantKeys = [dependentKeysForClass[theKeyPath] allObjects],
731  isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
732 
733  for (var i = 0; i < [dependantKeys count]; i++)
734  {
735  var dependantKey = [dependantKeys objectAtIndex:i];
736  [self _sendNotificationsForKey:dependantKey changeOptions:theChanges isBefore:isBeforeFlag];
737  }
738 }
739 
740 - (void)_addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(CPKeyValueObservingOptions)options context:(id)aContext
741 {
742  if (!anObserver)
743  return;
744 
745  var forwarder = nil;
746 
747  if (aPath.indexOf('.') !== CPNotFound && aPath.charAt(0) !== '@')
748  forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
749  else
750  [self _replaceModifiersForKey:aPath];
751 
752  var observers = _observersForKey[aPath];
753 
754  if (!observers)
755  {
756  observers = @{};
757  _observersForKey[aPath] = observers;
758  _observersForKeyLength++;
759  }
760 
761  [observers setObject:_CPKVOInfoMake(anObserver, options, aContext, forwarder) forKey:[anObserver UID]];
762 
763  if (options & CPKeyValueObservingOptionInitial)
764  {
765  var changes;
766 
767  if (options & CPKeyValueObservingOptionNew)
768  {
769  var newValue = [_targetObject valueForKeyPath:aPath];
770 
771  if (newValue == nil)
772  newValue = [CPNull null];
773 
775  } else {
777  }
778 
779  [anObserver observeValueForKeyPath:aPath ofObject:_targetObject change:changes context:aContext];
780  }
781 }
782 
783 - (void)_removeObserver:(id)anObserver forKeyPath:(CPString)aPath
784 {
785  var observers = _observersForKey[aPath];
786 
787  if (!observers)
788  {
789  // TODO: Remove this line when granular notifications are implemented
790  if (!_adding)
791  CPLog.warn(@"Cannot remove an observer %@ for the key path \"%@\" from %@ because it is not registered as an observer.", _targetObject, aPath, anObserver);
792 
793  return;
794  }
795 
796  if (aPath.indexOf('.') != CPNotFound)
797  {
798  // During cib instantiation, it is possible for the forwarder to not yet be available,
799  // so we have to check for nil.
800  var observer = [observers objectForKey:[anObserver UID]],
801  forwarder = observer ? observer.forwarder : nil;
802 
803  [forwarder finalize];
804  }
805 
806  [observers removeObjectForKey:[anObserver UID]];
807 
808  if (![observers count])
809  {
810  _observersForKeyLength--;
811  delete _observersForKey[aPath];
812  }
813 
814  if (!_observersForKeyLength)
815  {
816  _targetObject.isa = _nativeClass; //restore the original class
817  delete _targetObject[KVOProxyKey];
818  }
819 }
820 
821 //FIXME: We do not compute and cache if CPKeyValueObservingOptionOld is needed, so we may do unnecessary work
822 
823 - (void)_sendNotificationsForKey:(CPString)aKey changeOptions:(CPDictionary)changeOptions isBefore:(BOOL)isBefore
824 {
825  var changes = _changesForKey[aKey],
826  observers = [_observersForKey[aKey] allValues],
827  observersMinimumOptions = 0;
828 
829  if (isBefore)
830  {
831  if (changes)
832  {
833  // "willChange:X" nesting.
834  var level = _nestingForKey[aKey];
835 
836  if (!level)
837  [CPException raise:CPInternalInconsistencyException reason:@"_changesForKey without _nestingForKey"];
838 
839  _nestingForKey[aKey] = level + 1;
840  // Only notify on the first willChange..., silently note any following nested calls.
841  return;
842  }
843 
844  _nestingForKey[aKey] = 1;
845 
846  // Get the combined minimum of the ...Old and ...New options for all observers
847  var count = observers ? observers.length : 0;
848 
849  while (count--)
850  {
851  var observerInfo = observers[count];
852 
853  observersMinimumOptions |= observerInfo.options & kvoNewAndOld;
854  }
855 
856  _minOptionsForKey[aKey] = observersMinimumOptions;
857  changes = changeOptions;
858 
859  if (observersMinimumOptions & CPKeyValueObservingOptionOld)
860  {
861  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
862  setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
863 
864  if (setMutationKind)
865  {
866  var setMutationObjects = [changes[_CPKeyValueChangeSetMutationObjectsKey] copy],
867  setExistingObjects = [[_targetObject valueForKey: aKey] copy];
868 
869  if (setMutationKind == CPKeyValueMinusSetMutation)
870  {
871  [setExistingObjects intersectSet: setMutationObjects];
872  [changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
873  }
874  else if (setMutationKind === CPKeyValueIntersectSetMutation || setMutationKind === CPKeyValueSetSetMutation)
875  {
876  [setExistingObjects minusSet: setMutationObjects];
877  [changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
878  }
879 
880  //for unordered to-many relationships (CPSet) even new values can only be calculated before!!!
881  if (setMutationKind === CPKeyValueUnionSetMutation || setMutationKind === CPKeyValueSetSetMutation)
882  {
883  [setMutationObjects minusSet: setExistingObjects];
884  //hide new value (for CPKeyValueObservingOptionPrior messages)
885  //as long as "didChangeValue..." is not yet called!
886  changes[_CPKeyValueChangeSetMutationNewValueKey] = setMutationObjects;
887  }
888  }
889  else if (indexes)
890  {
891  var type = [changes objectForKey:CPKeyValueChangeKindKey];
892 
893  // for ordered to-many relationships, oldvalue is only sensible for replace and remove
894  if (type === CPKeyValueChangeReplacement || type === CPKeyValueChangeRemoval)
895  {
896  //FIXME: do we need to go through and replace "" with CPNull?
897  var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
898  [changes setValue:newValues forKey:CPKeyValueChangeOldKey];
899  }
900  }
901  else
902  {
903  var oldValue = [_targetObject valueForKey:aKey];
904 
905  if (oldValue === nil || oldValue === undefined)
906  oldValue = [CPNull null];
907 
908  [changes setObject:oldValue forKey:CPKeyValueChangeOldKey];
909  }
910 
911  }
912 
913  [changes setObject:1 forKey:CPKeyValueChangeNotificationIsPriorKey];
914  _changesForKey[aKey] = changes;
915 
916  // Clear ...New option as it should never be sent for a ...Prior option
917  observersMinimumOptions &= ~CPKeyValueObservingOptionNew;
918  }
919  else
920  {
921  var level = _nestingForKey[aKey];
922 
923  if (!changes || !level)
924  {
925  if (_targetObject._willChangeMessageCounter && _targetObject._willChangeMessageCounter[aKey])
926  {
927  // Close unobserved willChange for a given key.
928  _targetObject._willChangeMessageCounter[aKey] -= 1;
929 
930  if (!_targetObject._willChangeMessageCounter[aKey])
931  delete _targetObject._willChangeMessageCounter[aKey];
932 
933  return;
934  }
935  else
936  [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
937  }
938 
939  _nestingForKey[aKey] = level - 1;
940 
941  if (level - 1 > 0)
942  {
943  // willChange... was called multiple times. Only fire observation notifications when
944  // didChange... has been called an equal number of times.
945  return;
946  }
947 
948  delete _nestingForKey[aKey];
949 
950  [changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
951 
952  observersMinimumOptions = _minOptionsForKey[aKey];
953 
954  if (observersMinimumOptions & CPKeyValueObservingOptionNew)
955  {
956  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
957  setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
958 
959  if (setMutationKind)
960  {
961  //old and new values for unordered to-many relationships can only be calculated before
962  //set recalculated hidden new value as soon as "didChangeValue..." is called!
963  var newValue = changes[_CPKeyValueChangeSetMutationNewValueKey];
964  [changes setValue:newValue forKey:CPKeyValueChangeNewKey];
965 
966  //delete hidden values
967  delete changes[_CPKeyValueChangeSetMutationNewValueKey];
968  delete changes[_CPKeyValueChangeSetMutationObjectsKey];
969  delete changes[_CPKeyValueChangeSetMutationKindKey];
970  }
971  else if (indexes)
972  {
973  var type = [changes objectForKey:CPKeyValueChangeKindKey];
974 
975  // for ordered to-many relationships, newvalue is only sensible for replace and insert
977  {
978  //FIXME: do we need to go through and replace "" with CPNull?
979  var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
980  [changes setValue:newValues forKey:CPKeyValueChangeNewKey];
981  }
982  }
983  else
984  {
985  var newValue = [_targetObject valueForKey:aKey];
986 
987  if (newValue === nil || newValue === undefined)
988  newValue = [CPNull null];
989 
990  [changes setObject:newValue forKey:CPKeyValueChangeNewKey];
991  }
992  }
993 
994  delete _minOptionsForKey[aKey];
995  delete _changesForKey[aKey];
996  }
997 
998  var count = observers ? observers.length : 0,
999  changesCache = {};
1000 
1001  while (count--)
1002  {
1003  var observerInfo = observers[count],
1004  options = observerInfo.options,
1005  onlyNewAndOldOptions = options & kvoNewAndOld,
1006  observerChanges = nil;
1007 
1008  if (isBefore)
1009  {
1010  // Only send 'observeValueForKeyPath:' for '...Prior' option when handling 'willChangeValue...'
1011  if (options & CPKeyValueObservingOptionPrior)
1012  {
1013  observerChanges = changes;
1014  // The new values are not yet created in the change dictionary so remove ...New option to get a working cache below
1015  onlyNewAndOldOptions &= ~CPKeyValueObservingOptionNew;
1016  }
1017  }
1018  else
1019  {
1020  observerChanges = changes;
1021  }
1022 
1023  if (observerChanges)
1024  {
1025  // Don't change the 'change' dictionary when the observer wants the minimum options.
1026  // The ...New option is remved above for the ...Prior case
1027  if (onlyNewAndOldOptions !== observersMinimumOptions)
1028  {
1029  // Use a subset of the 'change' dictionary. First try to find it in the cache
1030  observerChanges = changesCache[onlyNewAndOldOptions];
1031  if (!observerChanges)
1032  {
1033  // Not in the cache. Build a new dictionary and store it in the cache
1034  changesCache[onlyNewAndOldOptions] = observerChanges = [changes mutableCopy];
1035  if (!(onlyNewAndOldOptions & CPKeyValueObservingOptionOld))
1036  [observerChanges removeObjectForKey:CPKeyValueChangeOldKey];
1037  if (!(onlyNewAndOldOptions & CPKeyValueObservingOptionNew))
1038  [observerChanges removeObjectForKey:CPKeyValueChangeNewKey];
1039  }
1040  }
1041  [observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:observerChanges context:observerInfo.context];
1042  }
1043  }
1044 
1045  var dependentKeysMap = _nativeClass[DependentKeysKey];
1046 
1047  if (!dependentKeysMap)
1048  return;
1049 
1050  var dependentKeyPaths = [dependentKeysMap[aKey] allObjects];
1051 
1052  if (!dependentKeyPaths)
1053  return;
1054 
1055  var index = 0,
1056  count = [dependentKeyPaths count];
1057 
1058  for (; index < count; ++index)
1059  {
1060  var keyPath = dependentKeyPaths[index];
1061 
1062  [self _sendNotificationsForKey:keyPath
1063  changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
1064  isBefore:isBefore];
1065  }
1066 }
1067 
1068 @end
1069 @implementation _CPKVOModelSubclass : CPObject
1070 {
1071  id __doxygen__;
1072 }
1073 
1074 - (void)willChangeValueForKey:(CPString)aKey
1075 {
1076  var superClass = [self class],
1077  methodSelector = @selector(willChangeValueForKey:),
1078  methodImp = class_getMethodImplementation(superClass, methodSelector);
1079 
1080  methodImp(self, methodSelector, aKey);
1081 
1082  if (!aKey)
1083  return;
1084 
1085  var changeOptions = @{ CPKeyValueChangeKindKey: CPKeyValueChangeSetting };
1086 
1087  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1088 }
1089 
1090 - (void)didChangeValueForKey:(CPString)aKey
1091 {
1092  var superClass = [self class],
1093  methodSelector = @selector(didChangeValueForKey:),
1094  methodImp = class_getMethodImplementation(superClass, methodSelector);
1095 
1096  methodImp(self, methodSelector, aKey);
1097 
1098  if (!aKey)
1099  return;
1100 
1101  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1102 }
1103 
1104 - (void)willChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
1105 {
1106  var superClass = [self class],
1107  methodSelector = @selector(willChange:valuesAtIndexes:forKey:),
1108  methodImp = class_getMethodImplementation(superClass, methodSelector);
1109 
1110  methodImp(self, methodSelector, change, indexes, aKey);
1111 
1112  if (!aKey)
1113  return;
1114 
1115  var changeOptions = @{ CPKeyValueChangeKindKey: change, CPKeyValueChangeIndexesKey: indexes };
1116 
1117  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1118 }
1119 
1120 - (void)didChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
1121 {
1122  var superClass = [self class],
1123  methodSelector = @selector(didChange:valuesAtIndexes:forKey:),
1124  methodImp = class_getMethodImplementation(superClass, methodSelector);
1125 
1126  methodImp(self, methodSelector, change, indexes, aKey);
1127 
1128  if (!aKey)
1129  return;
1130 
1131  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1132 }
1133 
1134 - (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
1135 {
1136  var superClass = [self class],
1137  methodSelector = @selector(willChangeValueForKey:withSetMutation:usingObjects:),
1138  methodImp = class_getMethodImplementation(superClass, methodSelector);
1139 
1140  methodImp(self, methodSelector, aKey, mutationKind, objects);
1141 
1142  if (!aKey)
1143  return;
1144 
1145  var changeKind = _changeKindForSetMutationKind(mutationKind),
1146  changeOptions = @{ CPKeyValueChangeKindKey: changeKind };
1147 
1148  //set hidden change-dict ivars to support unordered to-many relationships
1149  changeOptions[_CPKeyValueChangeSetMutationObjectsKey] = objects;
1150  changeOptions[_CPKeyValueChangeSetMutationKindKey] = mutationKind;
1151 
1152  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1153 }
1154 
1155 - (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
1156 {
1157  var superClass = [self class],
1158  methodSelector = @selector(didChangeValueForKey:withSetMutation:usingObjects:),
1159  methodImp = class_getMethodImplementation(superClass, methodSelector);
1160 
1161  methodImp(self, methodSelector, aKey, mutationKind, objects);
1162 
1163  if (!aKey)
1164  return;
1165 
1166  [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1167 }
1168 
1169 - (Class)class
1170 {
1171  return self[KVOProxyKey]._nativeClass;
1172 }
1173 
1174 - (Class)superclass
1175 {
1176  return [[self class] superclass];
1177 }
1178 
1179 - (BOOL)isKindOfClass:(Class)aClass
1180 {
1181  return [[self class] isSubclassOfClass:aClass];
1182 }
1183 
1184 - (BOOL)isMemberOfClass:(Class)aClass
1185 {
1186  return [self class] == aClass;
1187 }
1188 
1189 - (CPString)className
1190 {
1191  return [self class].name;
1192 }
1193 
1194 @end
1195 @implementation _CPKVOModelDictionarySubclass : CPObject
1196 {
1197  id __doxygen__;
1198 }
1199 
1200 - (void)removeAllObjects
1201 {
1202  var keys = [self allKeys],
1203  count = [keys count],
1204  i = 0;
1205 
1206  for (; i < count; i++)
1207  [self willChangeValueForKey:keys[i]];
1208 
1209  var superClass = [self class],
1210  methodSelector = @selector(removeAllObjects),
1211  methodImp = class_getMethodImplementation(superClass, methodSelector);
1212 
1213  methodImp(self, methodSelector);
1214 
1215  for (i = 0; i < count; i++)
1216  [self didChangeValueForKey:keys[i]];
1217 }
1218 
1219 - (void)removeObjectForKey:(id)aKey
1220 {
1221  [self willChangeValueForKey:aKey];
1222 
1223  var superClass = [self class],
1224  methodSelector = @selector(removeObjectForKey:),
1225  methodImp = class_getMethodImplementation(superClass, methodSelector);
1226 
1227  methodImp(self, methodSelector, aKey);
1228 
1229  [self didChangeValueForKey:aKey];
1230 }
1231 
1232 - (void)setObject:(id)anObject forKey:(id)aKey
1233 {
1234  [self willChangeValueForKey:aKey];
1235 
1236  var superClass = [self class],
1237  methodSelector = @selector(setObject:forKey:),
1238  methodImp = class_getMethodImplementation(superClass, methodSelector);
1239 
1240  methodImp(self, methodSelector, anObject, aKey);
1241 
1242  [self didChangeValueForKey:aKey];
1243 }
1244 
1245 @end
1246 
1247 @implementation _CPKVOForwardingObserver : CPObject
1248 {
1249  id _object;
1250  id _observer;
1251  id _context;
1252  unsigned _options;
1253  //a.b
1254  CPString _firstPart; //a
1255  CPString _secondPart; //b
1256 
1257  id _value;
1258 }
1259 
1260 - (id)initWithKeyPath:(CPString)aKeyPath object:(id)anObject observer:(id)anObserver options:(unsigned)options context:(id)aContext
1261 {
1262  self = [super init];
1263 
1264  _context = aContext;
1265  _observer = anObserver;
1266  _object = anObject;
1267  _options = options;
1268 
1269  var dotIndex = aKeyPath.indexOf('.');
1270 
1271  if (dotIndex === CPNotFound)
1272  [CPException raise:CPInvalidArgumentException reason:"Created _CPKVOForwardingObserver without compound key path: " + aKeyPath];
1273 
1274  _firstPart = aKeyPath.substring(0, dotIndex);
1275  _secondPart = aKeyPath.substring(dotIndex + 1);
1276 
1277  //become an observer of the first part of our key (a)
1278  [_object addObserver:self forKeyPath:_firstPart options:_options context:nil];
1279 
1280  //the current value of a (not the value of a.b)
1281  _value = [_object valueForKey:_firstPart];
1282 
1283  if (_value)
1284  [_value addObserver:self forKeyPath:_secondPart options:_options context:nil]; //we're observing b on current a
1285 
1286  return self;
1287 }
1288 
1289 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
1290 {
1291  if (aKeyPath === _firstPart)
1292  {
1293  var pathChanges = [CPMutableDictionary dictionaryWithObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey];
1294 
1295  if (_options & CPKeyValueObservingOptionOld)
1296  {
1297  var oldValue = [_value valueForKeyPath:_secondPart];
1298 
1299  [pathChanges setObject:oldValue != null ? oldValue : [CPNull null] forKey:CPKeyValueChangeOldKey];
1300  }
1301 
1302  if (_options & CPKeyValueObservingOptionNew)
1303  {
1304  var newValue = [_object valueForKeyPath:_firstPart + "." + _secondPart];
1305 
1306  [pathChanges setObject:newValue != null ? newValue : [CPNull null] forKey:CPKeyValueChangeNewKey];
1307  }
1308 
1309  [_observer observeValueForKeyPath:_firstPart + "." + _secondPart ofObject:_object change:pathChanges context:_context];
1310 
1311  //since a has changed, we should remove ourselves as an observer of the old a, and observe the new one
1312  if (_value)
1313  [_value removeObserver:self forKeyPath:_secondPart];
1314 
1315  _value = [_object valueForKey:_firstPart];
1316 
1317  if (_value)
1318  [_value addObserver:self forKeyPath:_secondPart options:_options context:nil];
1319  }
1320  else
1321  {
1322  //a is the same, but a.b has changed -- nothing to do but forward this message along
1323  [_observer observeValueForKeyPath:_firstPart + "." + aKeyPath ofObject:_object change:changes context:_context];
1324  }
1325 }
1326 
1327 - (void)finalize
1328 {
1329  if (_value)
1330  [_value removeObserver:self forKeyPath:_secondPart];
1331 
1332  [_object removeObserver:self forKeyPath:_firstPart];
1333 
1334  _object = nil;
1335  _observer = nil;
1336  _context = nil;
1337  _value = nil;
1338 }
1339 
1340 @end
1341 
1342 var _CPKVOInfoMake = function(anObserver, theOptions, aContext, aForwarder)
1343 {
1344  return {
1345  observer: anObserver,
1346  options: theOptions,
1347  context: aContext,
1348  forwarder: aForwarder
1349  };
1350 };
1351