PageRenderTime 126ms CodeModel.GetById 13ms app.highlight 107ms RepoModel.GetById 1ms app.codeStats 1ms

/core/externals/update-engine/externals/google-toolbox-for-mac/Foundation/GTMNSObject+KeyValueObserving.m

http://macfuse.googlecode.com/
Objective C | 676 lines | 504 code | 77 blank | 95 comment | 36 complexity | 08992f313278a6ea9a3f390f26309bb4 MD5 | raw file
  1//
  2//  GTMNSObject+KeyValueObserving.m
  3//
  4//  Copyright 2009 Google Inc.
  5//
  6//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7//  use this file except in compliance with the License.  You may obtain a copy
  8//  of the License at
  9//
 10//  http://www.apache.org/licenses/LICENSE-2.0
 11//
 12//  Unless required by applicable law or agreed to in writing, software
 13//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 14//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 15//  License for the specific language governing permissions and limitations under
 16//  the License.
 17//
 18
 19//
 20//  MAKVONotificationCenter.m
 21//  MAKVONotificationCenter
 22//
 23//  Created by Michael Ash on 10/15/08.
 24//
 25
 26// This code is based on code by Michael Ash.
 27// See comment in header.
 28#import "GTMDefines.h"
 29#import "GTMNSObject+KeyValueObserving.h"
 30#import "GTMDefines.h"
 31#import "GTMDebugSelectorValidation.h"
 32#import "GTMObjC2Runtime.h"
 33#import "GTMMethodCheck.h"
 34
 35// A singleton that works as a dispatch center for KVO
 36// -[NSObject observeValueForKeyPath:ofObject:change:context:] and turns them
 37// into selector dispatches. It stores a collection of
 38// GTMKeyValueObservingHelpers, and keys them via the key generated by
 39// -dictionaryKeyForObserver:ofObject:forKeyPath:selector.
 40@interface GTMKeyValueObservingCenter : NSObject {
 41 @private
 42  NSMutableDictionary *observerHelpers_;
 43}
 44
 45+ (id)defaultCenter;
 46
 47- (void)addObserver:(id)observer
 48           ofObject:(id)target
 49         forKeyPath:(NSString *)keyPath
 50           selector:(SEL)selector
 51           userInfo:(id)userInfo
 52            options:(NSKeyValueObservingOptions)options;
 53- (void)removeObserver:(id)observer
 54              ofObject:(id)target
 55            forKeyPath:(NSString *)keyPath
 56              selector:(SEL)selector;
 57- (id)dictionaryKeyForObserver:(id)observer
 58                      ofObject:(id)target
 59                    forKeyPath:(NSString *)keyPath
 60                      selector:(SEL)selector;
 61@end
 62
 63@interface GTMKeyValueObservingHelper : NSObject {
 64 @private
 65  __weak id observer_;
 66  SEL selector_;
 67  id userInfo_;
 68  __weak id target_;
 69  NSString* keyPath_;
 70}
 71
 72- (id)initWithObserver:(id)observer
 73                object:(id)target
 74               keyPath:(NSString *)keyPath
 75              selector:(SEL)selector
 76              userInfo:(id)userInfo
 77               options:(NSKeyValueObservingOptions)options;
 78- (void)deregister;
 79
 80@end
 81
 82@interface GTMKeyValueChangeNotification ()
 83- (id)initWithKeyPath:(NSString *)keyPath ofObject:(id)object
 84             userInfo:(id)userInfo change:(NSDictionary *)change;
 85@end
 86
 87@implementation GTMKeyValueObservingHelper
 88
 89// For info how and why we use these statics:
 90// http://lists.apple.com/archives/cocoa-dev/2006/Jul/msg01038.html
 91static char GTMKeyValueObservingHelperContextData;
 92static char* GTMKeyValueObservingHelperContext
 93  = &GTMKeyValueObservingHelperContextData;
 94
 95- (id)initWithObserver:(id)observer
 96                object:(id)target
 97               keyPath:(NSString *)keyPath
 98              selector:(SEL)selector
 99              userInfo:(id)userInfo
100               options:(NSKeyValueObservingOptions)options {
101  if((self = [super init])) {
102    observer_ = observer;
103    selector_ = selector;
104    userInfo_ = [userInfo retain];
105
106    target_ = target;
107    keyPath_ = [keyPath retain];
108
109    [target addObserver:self
110             forKeyPath:keyPath
111                options:options
112                context:GTMKeyValueObservingHelperContext];
113  }
114  return self;
115}
116
117- (NSString *)description {
118  return [NSString stringWithFormat:
119          @"%@ <observer = %@ keypath = %@ target = %@ selector = %@>",
120          [self class], observer_, keyPath_, target_,
121          NSStringFromSelector(selector_)];
122}
123
124- (void)dealloc {
125  if (target_) {
126    _GTMDevLog(@"Didn't deregister %@", self);
127    [self deregister];
128  }
129  [userInfo_ release];
130  [keyPath_ release];
131  [super dealloc];
132}
133
134- (void)observeValueForKeyPath:(NSString *)keyPath
135                      ofObject:(id)object
136                        change:(NSDictionary *)change
137                       context:(void *)context {
138  if(context == GTMKeyValueObservingHelperContext) {
139    GTMKeyValueChangeNotification *notification
140      = [[GTMKeyValueChangeNotification alloc] initWithKeyPath:keyPath
141                                                      ofObject:object
142                                                      userInfo:userInfo_
143                                                        change:change];
144    [observer_ performSelector:selector_ withObject:notification];
145    [notification release];
146  } else {
147    // COV_NF_START
148    // There's no way this should ever be called.
149    // If it is, the call will go up to NSObject which will assert.
150    [super observeValueForKeyPath:keyPath
151                         ofObject:object
152                           change:change
153                          context:context];
154    // COV_NF_END
155  }
156}
157
158- (void)deregister {
159  [target_ removeObserver:self forKeyPath:keyPath_];
160  target_ = nil;
161}
162
163@end
164
165@implementation GTMKeyValueObservingCenter
166
167+ (id)defaultCenter {
168  static GTMKeyValueObservingCenter *center = nil;
169  if(!center) {
170    // do a bit of clever atomic setting to make this thread safe
171    // if two threads try to set simultaneously, one will fail
172    // and the other will set things up so that the failing thread
173    // gets the shared center
174    GTMKeyValueObservingCenter *newCenter = [[self alloc] init];
175    if(!objc_atomicCompareAndSwapGlobalBarrier(nil,
176                                               newCenter,
177                                               (void *)&center)) {
178      [newCenter release];  // COV_NF_LINE no guarantee we'll hit this line
179    }
180  }
181  return center;
182}
183
184- (id)init {
185  if((self = [super init])) {
186    observerHelpers_ = [[NSMutableDictionary alloc] init];
187  }
188  return self;
189}
190
191// COV_NF_START
192// Singletons don't get deallocated
193- (void)dealloc {
194  [observerHelpers_ release];
195  [super dealloc];
196}
197// COV_NF_END
198
199- (id)dictionaryKeyForObserver:(id)observer
200                      ofObject:(id)target
201                    forKeyPath:(NSString *)keyPath
202                      selector:(SEL)selector {
203  NSString *key = nil;
204  if (!target && !keyPath && !selector) {
205    key = [NSString stringWithFormat:@"%p:", observer];
206  } else {
207    key = [NSString stringWithFormat:@"%p:%@:%p:%p",
208           observer, keyPath, selector, target];
209  }
210  return key;
211}
212
213- (void)addObserver:(id)observer
214           ofObject:(id)target
215         forKeyPath:(NSString *)keyPath
216           selector:(SEL)selector
217           userInfo:(id)userInfo
218            options:(NSKeyValueObservingOptions)options {
219  GTMKeyValueObservingHelper *helper
220    = [[GTMKeyValueObservingHelper alloc] initWithObserver:observer
221                                                    object:target
222                                                   keyPath:keyPath
223                                                  selector:selector
224                                                  userInfo:userInfo
225                                                   options:options];
226  id key = [self dictionaryKeyForObserver:observer
227                                 ofObject:target
228                               forKeyPath:keyPath
229                                 selector:selector];
230  @synchronized(self) {
231    GTMKeyValueObservingHelper *oldHelper = [observerHelpers_ objectForKey:key];
232    if (oldHelper) {
233      _GTMDevLog(@"%@ already observing %@ forKeyPath %@",
234                 observer, target, keyPath);
235      [oldHelper deregister];
236    }
237    [observerHelpers_ setObject:helper forKey:key];
238  }
239  [helper release];
240}
241
242- (void)removeObserver:(id)observer
243              ofObject:(id)target
244            forKeyPath:(NSString *)keyPath
245              selector:(SEL)selector {
246  id key = [self dictionaryKeyForObserver:observer
247                                 ofObject:target
248                               forKeyPath:keyPath
249                                 selector:selector];
250  NSMutableArray *allValidHelperKeys = [NSMutableArray array];
251  NSArray *allValidHelpers = nil;
252  @synchronized(self) {
253
254    NSString *helperKey;
255    GTM_FOREACH_OBJECT(helperKey, [observerHelpers_ allKeys]) {
256      if ([helperKey hasPrefix:key]) {
257        [allValidHelperKeys addObject:helperKey];
258      }
259    }
260#if DEBUG
261    if ([allValidHelperKeys count] == 0) {
262      _GTMDevLog(@"%@ was not observing %@ with keypath %@",
263                 observer, target, keyPath);
264    }
265#endif // DEBUG
266    allValidHelpers = [observerHelpers_ objectsForKeys:allValidHelperKeys
267                                        notFoundMarker:[NSNull null]];
268    [observerHelpers_ removeObjectsForKeys:allValidHelperKeys];
269  }
270  [allValidHelpers makeObjectsPerformSelector:@selector(deregister)];
271}
272
273@end
274
275@implementation NSObject (GTMKeyValueObservingAdditions)
276
277- (void)gtm_addObserver:(id)observer
278             forKeyPath:(NSString *)keyPath
279               selector:(SEL)selector
280               userInfo:(id)userInfo
281                options:(NSKeyValueObservingOptions)options {
282  _GTMDevAssert(observer && [keyPath length] && selector,
283                @"Missing observer, keyPath, or selector");
284  GTMKeyValueObservingCenter *center
285    = [GTMKeyValueObservingCenter defaultCenter];
286  GTMAssertSelectorNilOrImplementedWithArguments(
287      observer,
288      selector,
289      @encode(GTMKeyValueChangeNotification *),
290      NULL);
291  [center addObserver:observer
292             ofObject:self
293           forKeyPath:keyPath
294             selector:selector
295             userInfo:userInfo
296              options:options];
297}
298
299- (void)gtm_removeObserver:(id)observer
300                forKeyPath:(NSString *)keyPath
301                  selector:(SEL)selector {
302  _GTMDevAssert(observer && [keyPath length] && selector,
303                @"Missing observer, keyPath, or selector");
304  GTMKeyValueObservingCenter *center
305    = [GTMKeyValueObservingCenter defaultCenter];
306  GTMAssertSelectorNilOrImplementedWithArguments(
307      observer,
308      selector,
309      @encode(GTMKeyValueChangeNotification *),
310      NULL);
311  [center removeObserver:observer
312                ofObject:self
313              forKeyPath:keyPath
314                selector:selector];
315}
316
317- (void)gtm_stopObservingAllKeyPaths {
318  GTMKeyValueObservingCenter *center
319    = [GTMKeyValueObservingCenter defaultCenter];
320  [center removeObserver:self
321                ofObject:nil
322              forKeyPath:nil
323                selector:Nil];
324}
325
326@end
327
328
329@implementation GTMKeyValueChangeNotification
330
331- (id)initWithKeyPath:(NSString *)keyPath ofObject:(id)object
332             userInfo:(id)userInfo change:(NSDictionary *)change {
333  if ((self = [super init])) {
334    keyPath_ = [keyPath copy];
335    object_ = [object retain];
336    userInfo_ = [userInfo retain];
337    change_ = [change retain];
338  }
339  return self;
340}
341
342- (void)dealloc {
343  [keyPath_ release];
344  [object_ release];
345  [userInfo_ release];
346  [change_ release];
347  [super dealloc];
348}
349
350- (id)copyWithZone:(NSZone *)zone {
351  return [[[self class] allocWithZone:zone] initWithKeyPath:keyPath_
352                                                   ofObject:object_
353                                                   userInfo:userInfo_
354                                                     change:change_];
355}
356
357- (BOOL)isEqual:(id)object {
358  return ([keyPath_ isEqualToString:[object keyPath]]
359          && [object_ isEqual:[object object]]
360          && [userInfo_ isEqual:[object userInfo]]
361          && [change_ isEqual:[object change]]);
362}
363
364- (NSString *)description {
365  return [NSString stringWithFormat:
366          @"%@ <object = %@ keypath = %@ userInfo = %@ change = %@>",
367          [self class], object_, keyPath_, userInfo_, change_];
368}
369
370- (NSUInteger)hash {
371  return [keyPath_ hash] + [object_ hash] + [userInfo_ hash] + [change_ hash];
372}
373
374- (NSString *)keyPath {
375  return keyPath_;
376}
377
378- (id)object {
379  return object_;
380}
381
382- (id)userInfo {
383  return userInfo_;
384}
385
386- (NSDictionary *)change {
387  return change_;
388}
389
390@end
391
392#ifdef DEBUG
393
394static void SwizzleMethodsInClass(Class cls, SEL sel1, SEL sel2) {
395  Method m1 = class_getInstanceMethod(cls, sel1);
396  Method m2 = class_getInstanceMethod(cls, sel2);
397  method_exchangeImplementations(m1, m2);
398}
399
400#if GTM_PERFORM_KVO_CHECKS
401
402// This is only used when GTM_PERFORM_KVO_CHECKS is on.
403static void SwizzleClassMethodsInClass(Class cls, SEL sel1, SEL sel2) {
404  Method m1 = class_getClassMethod(cls, sel1);
405  Method m2 = class_getClassMethod(cls, sel2);
406  method_exchangeImplementations(m1, m2);
407}
408
409#endif  // GTM_PERFORM_KVO_CHECKS
410
411// This category exists to attempt to help deal with tricky KVO issues.
412// KVO is a wonderful technology in some ways, but is extremely fragile and
413// allows developers a lot of freedom to shoot themselves in the foot.
414// Refactoring an app that uses a lot of KVO can be really difficult, as can
415// debugging it.
416// These are some tools that we have found useful when working with KVO. Note
417// that these tools are only on in Debug builds.
418//
419// We have divided these tools up into two categories: Checks and Debugs.
420//
421// Debugs
422// Debugs are mainly for logging all the KVO/KVC that is occurring in your
423// application. To enable our KVO debugging, set the GTMDebugKVO environment
424// variable to 1 and you will get a whole pile of KVO logging that may help you
425// track down problems.
426// bash - export GTMDebugKVO=1
427// csh/tcsh - setenv GTMDebugKVO 1
428//
429// Checks
430// First we believe that instance variables should be private by default,
431// and that any KVO should be done via accessors. Apple by default allows KVO
432// to get at instance variables directly. Since our coding standards define
433// that instance variables should be @private, we feel that KVO shouldn't be
434// breaking this encapsulation. Unfortunately the @private, @protected
435// designators are a compile time convention only, and don't get carried over
436// into the runtime, so there's no way to check on an individual iVar what
437// it's visibility is. We therefore assume that an instance variable is private,
438// and disallow KVO access to instance variables. The problem with most KVO
439// issues is that they occur at runtime and unless you execute that case you
440// may never see the bug until it's too late. We try to force KVO issues to
441// rear their head at the time of the observing if at all possible.
442// Checks are on by default in debug builds. They can be turned off by defining
443// the compile flag GTM_PERFORM_KVO_CHECKS to 0
444// i.e. #define GTM_PERFORM_KVO_CHECKS 0, or set it
445// in GCC_PREPROCESSOR_DEFINITIONS.
446//
447// Checks work at a couple of different levels.
448// The most restrictive of the checks is that we set
449// |accessInstanceVariablesDirectly| to NO by default. This means that if you
450// attempt to perform KVO on an instance variable, you will get an exception
451// thrown.
452// Also, when adding an observer, we check to see if any member of the path
453// starts or ends with _ which by convention denotes an instance variable. If so
454// we warn you about attempting to access a ivar directly.
455
456@interface NSObject (GTMDebugKeyValueObserving)
457- (void)_gtmDebugAddObserver:(NSObject *)observer
458                  forKeyPath:(NSString *)keyPath
459                     options:(NSKeyValueObservingOptions)options
460                     context:(void *)context;
461- (void)_gtmDebugArrayAddObserver:(NSObject *)observer
462               toObjectsAtIndexes:(NSIndexSet *)indexes
463                       forKeyPath:(NSString *)keyPath
464                          options:(NSKeyValueObservingOptions)options
465                          context:(void *)context;
466- (void)_gtmDebugRemoveObserver:(NSObject *)observer
467                     forKeyPath:(NSString *)keyPath;
468- (void)_gtmDebugArrayRemoveObserver:(NSObject *)observer
469                fromObjectsAtIndexes:(NSIndexSet *)indexes
470                          forKeyPath:(NSString *)keyPath;
471- (void)_gtmDebugWillChangeValueForKey:(NSString*)key;
472- (void)_gtmDebugDidChangeValueForKey:(NSString*)key;
473
474#if GTM_PERFORM_KVO_CHECKS
475
476- (void)_gtmCheckAddObserver:(NSObject *)observer
477                  forKeyPath:(NSString *)keyPath
478                     options:(NSKeyValueObservingOptions)options
479                     context:(void *)context;
480- (void)_gtmCheckAddObserver:(NSObject *)observer
481          toObjectsAtIndexes:(NSIndexSet *)indexes
482                  forKeyPath:(NSString *)keyPath
483                     options:(NSKeyValueObservingOptions)options
484                     context:(void *)context;
485+ (BOOL)_gtmAccessInstanceVariablesDirectly;
486
487#endif  // GTM_PERFORM_KVO_CHECKS
488@end
489
490@implementation NSObject (GTMDebugKeyValueObserving)
491GTM_METHOD_CHECK(NSObject, _gtmDebugAddObserver:forKeyPath:options:context:);
492GTM_METHOD_CHECK(NSObject, _gtmDebugRemoveObserver:forKeyPath:);
493GTM_METHOD_CHECK(NSObject, _gtmDebugWillChangeValueForKey:);
494GTM_METHOD_CHECK(NSObject, _gtmDebugDidChangeValueForKey:);
495GTM_METHOD_CHECK(NSArray,
496    _gtmDebugArrayAddObserver:toObjectsAtIndexes:forKeyPath:options:context:);
497GTM_METHOD_CHECK(NSArray,
498    _gtmDebugArrayRemoveObserver:fromObjectsAtIndexes:forKeyPath:);
499
500#if GTM_PERFORM_KVO_CHECKS
501
502GTM_METHOD_CHECK(NSObject,
503    _gtmCheckAddObserver:forKeyPath:options:context:);
504GTM_METHOD_CHECK(NSArray,
505    _gtmCheckAddObserver:toObjectsAtIndexes:forKeyPath:options:context:);
506GTM_METHOD_CHECK(NSObject,
507    _gtmAccessInstanceVariablesDirectly);
508
509#endif  // GTM_PERFORM_KVO_CHECKS
510
511+ (void)load {
512  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
513  NSDictionary *env = [[NSProcessInfo processInfo] environment];
514  id debugKeyValue = [env valueForKey:@"GTMDebugKVO"];
515  BOOL debug = NO;
516  if ([debugKeyValue isKindOfClass:[NSNumber class]]) {
517    debug = [debugKeyValue intValue] != 0 ? YES : NO;
518  } else if ([debugKeyValue isKindOfClass:[NSString class]]) {
519    debug = ([debugKeyValue hasPrefix:@"Y"] || [debugKeyValue hasPrefix:@"T"] ||
520             [debugKeyValue intValue]);
521  }
522  Class cls = Nil;
523  if (debug) {
524    cls = [NSObject class];
525    SwizzleMethodsInClass(cls,
526        @selector(addObserver:forKeyPath:options:context:),
527        @selector(_gtmDebugAddObserver:forKeyPath:options:context:));
528    SwizzleMethodsInClass(cls,
529        @selector(removeObserver:forKeyPath:),
530        @selector(_gtmDebugRemoveObserver:forKeyPath:));
531    SwizzleMethodsInClass(cls,
532        @selector(willChangeValueForKey:),
533        @selector(_gtmDebugWillChangeValueForKey:));
534    SwizzleMethodsInClass(cls,
535        @selector(didChangeValueForKey:),
536        @selector(_gtmDebugDidChangeValueForKey:));
537    cls = [NSArray class];
538    SwizzleMethodsInClass(cls,
539        @selector(addObserver:toObjectsAtIndexes:forKeyPath:options:context:),
540        @selector(_gtmDebugArrayAddObserver:toObjectsAtIndexes:forKeyPath:options:context:));
541    SwizzleMethodsInClass(cls,
542        @selector(removeObserver:fromObjectsAtIndexes:forKeyPath:),
543        @selector(_gtmDebugArrayRemoveObserver:fromObjectsAtIndexes:forKeyPath:));
544  }
545#if GTM_PERFORM_KVO_CHECKS
546  cls = [NSObject class];
547  SwizzleMethodsInClass(cls,
548      @selector(addObserver:forKeyPath:options:context:),
549      @selector(_gtmCheckAddObserver:forKeyPath:options:context:));
550  SwizzleClassMethodsInClass(cls,
551      @selector(accessInstanceVariablesDirectly),
552      @selector(_gtmAccessInstanceVariablesDirectly));
553  cls = [NSArray class];
554  SwizzleMethodsInClass(cls,
555      @selector(addObserver:toObjectsAtIndexes:forKeyPath:options:context:),
556      @selector(_gtmCheckAddObserver:toObjectsAtIndexes:forKeyPath:options:context:));
557
558#endif  // GTM_PERFORM_KVO_CHECKS
559  [pool drain];
560}
561
562- (void)_gtmDebugAddObserver:(NSObject *)observer
563                  forKeyPath:(NSString *)keyPath
564                     options:(NSKeyValueObservingOptions)options
565                     context:(void *)context {
566  _GTMDevLog(@"Adding observer %@ to %@ keypath '%@'", observer, self, keyPath);
567  [self _gtmDebugAddObserver:observer forKeyPath:keyPath
568                     options:options context:context];
569}
570
571- (void)_gtmDebugArrayAddObserver:(NSObject *)observer
572               toObjectsAtIndexes:(NSIndexSet *)indexes
573                       forKeyPath:(NSString *)keyPath
574                          options:(NSKeyValueObservingOptions)options
575                          context:(void *)context {
576  _GTMDevLog(@"Array adding observer %@ to indexes %@ of %@ keypath '%@'",
577             observer, indexes, self, keyPath);
578  [self _gtmDebugArrayAddObserver:observer
579               toObjectsAtIndexes:indexes
580                       forKeyPath:keyPath
581                          options:options context:context];
582}
583
584- (void)_gtmDebugRemoveObserver:(NSObject *)observer
585                     forKeyPath:(NSString *)keyPath {
586  _GTMDevLog(@"Removing observer %@ from %@ keypath '%@'",
587             observer, self, keyPath);
588  [self _gtmDebugRemoveObserver:observer forKeyPath:keyPath];
589}
590
591- (void)_gtmDebugArrayRemoveObserver:(NSObject *)observer
592                fromObjectsAtIndexes:(NSIndexSet *)indexes
593                          forKeyPath:(NSString *)keyPath {
594  _GTMDevLog(@"Array removing observer %@ from indexes %@ of %@ keypath '%@'",
595             indexes, observer, self, keyPath);
596  [self _gtmDebugArrayRemoveObserver:observer
597                fromObjectsAtIndexes:indexes
598                          forKeyPath:keyPath];
599}
600
601- (void)_gtmDebugWillChangeValueForKey:(NSString*)key {
602  _GTMDevLog(@"Will change '%@' of %@", key, self);
603  [self _gtmDebugWillChangeValueForKey:key];
604}
605
606- (void)_gtmDebugDidChangeValueForKey:(NSString*)key {
607  _GTMDevLog(@"Did change '%@' of %@", key, self);
608  [self _gtmDebugDidChangeValueForKey:key];
609}
610
611#if GTM_PERFORM_KVO_CHECKS
612
613- (void)_gtmCheckAddObserver:(NSObject *)observer
614                  forKeyPath:(NSString *)keyPath
615                     options:(NSKeyValueObservingOptions)options
616                     context:(void *)context {
617  NSArray *keyPathElements = [keyPath componentsSeparatedByString:@"."];
618  NSString *element;
619  GTM_FOREACH_OBJECT(element, keyPathElements) {
620    if ([element hasPrefix:@"_"] || [element hasSuffix:@"_"]) {
621      _GTMDevLog(@"warning: %@ is registering an observation on what appears "
622                 @"to be a private ivar of %@ (or a sub keyed object) with "
623                 @"element %@ of keyPath %@.", observer, self, element,
624                 keyPath);
625    }
626  }
627  [self _gtmCheckAddObserver:observer
628                  forKeyPath:keyPath
629                     options:options
630                     context:context];
631}
632
633- (void)_gtmCheckAddObserver:(NSObject *)observer
634          toObjectsAtIndexes:(NSIndexSet *)indexes
635                  forKeyPath:(NSString *)keyPath
636                     options:(NSKeyValueObservingOptions)options
637                     context:(void *)context {
638  NSArray *keyPathElements = [keyPath componentsSeparatedByString:@"."];
639  NSString *element;
640  GTM_FOREACH_OBJECT(element, keyPathElements) {
641    if ([element hasPrefix:@"_"] || [element hasSuffix:@"_"]) {
642      _GTMDevLog(@"warning: %@ is registering an observation on what appears "
643                 @"to be a private ivar of %@ (or a sub keyed object) with "
644                 @"element %@ of keyPath %@.", observer, self, element,
645                 keyPath);
646    }
647  }
648  [self _gtmCheckAddObserver:observer
649          toObjectsAtIndexes:indexes
650                  forKeyPath:keyPath
651                     options:options
652                     context:context];
653}
654
655
656+ (BOOL)_gtmAccessInstanceVariablesDirectly {
657  // Apple has lots of "bad" direct instance variable accesses, so we
658  // only want to check our code, as opposed to library code.  iOS simulator
659  // builds copy the app into the user's home directory.  Xcode 4 also changes
660  // the default location of the output directory.  Don't count being within
661  // the user's home and under "/Library/" as being a system library.
662
663  // If this turns out to be slow, we may want to consider a cache to speed
664  // things up.
665  NSBundle *bundle = [NSBundle bundleForClass:self];
666  NSString *path = [bundle bundlePath];
667  BOOL hasLibrary = [path rangeOfString:@"/Library/"].location != NSNotFound;
668  BOOL startsWithUser = [path hasPrefix:@"/Users/"];
669  return !startsWithUser && hasLibrary;
670}
671
672#endif  // GTM_PERFORM_KVO_CHECKS
673
674@end
675
676#endif  // DEBUG