/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
- //
- // GTMNSObject+KeyValueObserving.m
- //
- // Copyright 2009 Google Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License"); you may not
- // use this file except in compliance with the License. You may obtain a copy
- // of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- // License for the specific language governing permissions and limitations under
- // the License.
- //
- //
- // MAKVONotificationCenter.m
- // MAKVONotificationCenter
- //
- // Created by Michael Ash on 10/15/08.
- //
- // This code is based on code by Michael Ash.
- // See comment in header.
- #import "GTMDefines.h"
- #import "GTMNSObject+KeyValueObserving.h"
- #import "GTMDefines.h"
- #import "GTMDebugSelectorValidation.h"
- #import "GTMObjC2Runtime.h"
- #import "GTMMethodCheck.h"
- // A singleton that works as a dispatch center for KVO
- // -[NSObject observeValueForKeyPath:ofObject:change:context:] and turns them
- // into selector dispatches. It stores a collection of
- // GTMKeyValueObservingHelpers, and keys them via the key generated by
- // -dictionaryKeyForObserver:ofObject:forKeyPath:selector.
- @interface GTMKeyValueObservingCenter : NSObject {
- @private
- NSMutableDictionary *observerHelpers_;
- }
- + (id)defaultCenter;
- - (void)addObserver:(id)observer
- ofObject:(id)target
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector
- userInfo:(id)userInfo
- options:(NSKeyValueObservingOptions)options;
- - (void)removeObserver:(id)observer
- ofObject:(id)target
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector;
- - (id)dictionaryKeyForObserver:(id)observer
- ofObject:(id)target
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector;
- @end
- @interface GTMKeyValueObservingHelper : NSObject {
- @private
- __weak id observer_;
- SEL selector_;
- id userInfo_;
- __weak id target_;
- NSString* keyPath_;
- }
- - (id)initWithObserver:(id)observer
- object:(id)target
- keyPath:(NSString *)keyPath
- selector:(SEL)selector
- userInfo:(id)userInfo
- options:(NSKeyValueObservingOptions)options;
- - (void)deregister;
- @end
- @interface GTMKeyValueChangeNotification ()
- - (id)initWithKeyPath:(NSString *)keyPath ofObject:(id)object
- userInfo:(id)userInfo change:(NSDictionary *)change;
- @end
- @implementation GTMKeyValueObservingHelper
- // For info how and why we use these statics:
- // http://lists.apple.com/archives/cocoa-dev/2006/Jul/msg01038.html
- static char GTMKeyValueObservingHelperContextData;
- static char* GTMKeyValueObservingHelperContext
- = >MKeyValueObservingHelperContextData;
- - (id)initWithObserver:(id)observer
- object:(id)target
- keyPath:(NSString *)keyPath
- selector:(SEL)selector
- userInfo:(id)userInfo
- options:(NSKeyValueObservingOptions)options {
- if((self = [super init])) {
- observer_ = observer;
- selector_ = selector;
- userInfo_ = [userInfo retain];
- target_ = target;
- keyPath_ = [keyPath retain];
- [target addObserver:self
- forKeyPath:keyPath
- options:options
- context:GTMKeyValueObservingHelperContext];
- }
- return self;
- }
- - (NSString *)description {
- return [NSString stringWithFormat:
- @"%@ <observer = %@ keypath = %@ target = %@ selector = %@>",
- [self class], observer_, keyPath_, target_,
- NSStringFromSelector(selector_)];
- }
- - (void)dealloc {
- if (target_) {
- _GTMDevLog(@"Didn't deregister %@", self);
- [self deregister];
- }
- [userInfo_ release];
- [keyPath_ release];
- [super dealloc];
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary *)change
- context:(void *)context {
- if(context == GTMKeyValueObservingHelperContext) {
- GTMKeyValueChangeNotification *notification
- = [[GTMKeyValueChangeNotification alloc] initWithKeyPath:keyPath
- ofObject:object
- userInfo:userInfo_
- change:change];
- [observer_ performSelector:selector_ withObject:notification];
- [notification release];
- } else {
- // COV_NF_START
- // There's no way this should ever be called.
- // If it is, the call will go up to NSObject which will assert.
- [super observeValueForKeyPath:keyPath
- ofObject:object
- change:change
- context:context];
- // COV_NF_END
- }
- }
- - (void)deregister {
- [target_ removeObserver:self forKeyPath:keyPath_];
- target_ = nil;
- }
- @end
- @implementation GTMKeyValueObservingCenter
- + (id)defaultCenter {
- static GTMKeyValueObservingCenter *center = nil;
- if(!center) {
- // do a bit of clever atomic setting to make this thread safe
- // if two threads try to set simultaneously, one will fail
- // and the other will set things up so that the failing thread
- // gets the shared center
- GTMKeyValueObservingCenter *newCenter = [[self alloc] init];
- if(!objc_atomicCompareAndSwapGlobalBarrier(nil,
- newCenter,
- (void *)¢er)) {
- [newCenter release]; // COV_NF_LINE no guarantee we'll hit this line
- }
- }
- return center;
- }
- - (id)init {
- if((self = [super init])) {
- observerHelpers_ = [[NSMutableDictionary alloc] init];
- }
- return self;
- }
- // COV_NF_START
- // Singletons don't get deallocated
- - (void)dealloc {
- [observerHelpers_ release];
- [super dealloc];
- }
- // COV_NF_END
- - (id)dictionaryKeyForObserver:(id)observer
- ofObject:(id)target
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector {
- NSString *key = nil;
- if (!target && !keyPath && !selector) {
- key = [NSString stringWithFormat:@"%p:", observer];
- } else {
- key = [NSString stringWithFormat:@"%p:%@:%p:%p",
- observer, keyPath, selector, target];
- }
- return key;
- }
- - (void)addObserver:(id)observer
- ofObject:(id)target
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector
- userInfo:(id)userInfo
- options:(NSKeyValueObservingOptions)options {
- GTMKeyValueObservingHelper *helper
- = [[GTMKeyValueObservingHelper alloc] initWithObserver:observer
- object:target
- keyPath:keyPath
- selector:selector
- userInfo:userInfo
- options:options];
- id key = [self dictionaryKeyForObserver:observer
- ofObject:target
- forKeyPath:keyPath
- selector:selector];
- @synchronized(self) {
- GTMKeyValueObservingHelper *oldHelper = [observerHelpers_ objectForKey:key];
- if (oldHelper) {
- _GTMDevLog(@"%@ already observing %@ forKeyPath %@",
- observer, target, keyPath);
- [oldHelper deregister];
- }
- [observerHelpers_ setObject:helper forKey:key];
- }
- [helper release];
- }
- - (void)removeObserver:(id)observer
- ofObject:(id)target
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector {
- id key = [self dictionaryKeyForObserver:observer
- ofObject:target
- forKeyPath:keyPath
- selector:selector];
- NSMutableArray *allValidHelperKeys = [NSMutableArray array];
- NSArray *allValidHelpers = nil;
- @synchronized(self) {
- NSString *helperKey;
- GTM_FOREACH_OBJECT(helperKey, [observerHelpers_ allKeys]) {
- if ([helperKey hasPrefix:key]) {
- [allValidHelperKeys addObject:helperKey];
- }
- }
- #if DEBUG
- if ([allValidHelperKeys count] == 0) {
- _GTMDevLog(@"%@ was not observing %@ with keypath %@",
- observer, target, keyPath);
- }
- #endif // DEBUG
- allValidHelpers = [observerHelpers_ objectsForKeys:allValidHelperKeys
- notFoundMarker:[NSNull null]];
- [observerHelpers_ removeObjectsForKeys:allValidHelperKeys];
- }
- [allValidHelpers makeObjectsPerformSelector:@selector(deregister)];
- }
- @end
- @implementation NSObject (GTMKeyValueObservingAdditions)
- - (void)gtm_addObserver:(id)observer
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector
- userInfo:(id)userInfo
- options:(NSKeyValueObservingOptions)options {
- _GTMDevAssert(observer && [keyPath length] && selector,
- @"Missing observer, keyPath, or selector");
- GTMKeyValueObservingCenter *center
- = [GTMKeyValueObservingCenter defaultCenter];
- GTMAssertSelectorNilOrImplementedWithArguments(
- observer,
- selector,
- @encode(GTMKeyValueChangeNotification *),
- NULL);
- [center addObserver:observer
- ofObject:self
- forKeyPath:keyPath
- selector:selector
- userInfo:userInfo
- options:options];
- }
- - (void)gtm_removeObserver:(id)observer
- forKeyPath:(NSString *)keyPath
- selector:(SEL)selector {
- _GTMDevAssert(observer && [keyPath length] && selector,
- @"Missing observer, keyPath, or selector");
- GTMKeyValueObservingCenter *center
- = [GTMKeyValueObservingCenter defaultCenter];
- GTMAssertSelectorNilOrImplementedWithArguments(
- observer,
- selector,
- @encode(GTMKeyValueChangeNotification *),
- NULL);
- [center removeObserver:observer
- ofObject:self
- forKeyPath:keyPath
- selector:selector];
- }
- - (void)gtm_stopObservingAllKeyPaths {
- GTMKeyValueObservingCenter *center
- = [GTMKeyValueObservingCenter defaultCenter];
- [center removeObserver:self
- ofObject:nil
- forKeyPath:nil
- selector:Nil];
- }
- @end
- @implementation GTMKeyValueChangeNotification
- - (id)initWithKeyPath:(NSString *)keyPath ofObject:(id)object
- userInfo:(id)userInfo change:(NSDictionary *)change {
- if ((self = [super init])) {
- keyPath_ = [keyPath copy];
- object_ = [object retain];
- userInfo_ = [userInfo retain];
- change_ = [change retain];
- }
- return self;
- }
- - (void)dealloc {
- [keyPath_ release];
- [object_ release];
- [userInfo_ release];
- [change_ release];
- [super dealloc];
- }
- - (id)copyWithZone:(NSZone *)zone {
- return [[[self class] allocWithZone:zone] initWithKeyPath:keyPath_
- ofObject:object_
- userInfo:userInfo_
- change:change_];
- }
- - (BOOL)isEqual:(id)object {
- return ([keyPath_ isEqualToString:[object keyPath]]
- && [object_ isEqual:[object object]]
- && [userInfo_ isEqual:[object userInfo]]
- && [change_ isEqual:[object change]]);
- }
- - (NSString *)description {
- return [NSString stringWithFormat:
- @"%@ <object = %@ keypath = %@ userInfo = %@ change = %@>",
- [self class], object_, keyPath_, userInfo_, change_];
- }
- - (NSUInteger)hash {
- return [keyPath_ hash] + [object_ hash] + [userInfo_ hash] + [change_ hash];
- }
- - (NSString *)keyPath {
- return keyPath_;
- }
- - (id)object {
- return object_;
- }
- - (id)userInfo {
- return userInfo_;
- }
- - (NSDictionary *)change {
- return change_;
- }
- @end
- #ifdef DEBUG
- static void SwizzleMethodsInClass(Class cls, SEL sel1, SEL sel2) {
- Method m1 = class_getInstanceMethod(cls, sel1);
- Method m2 = class_getInstanceMethod(cls, sel2);
- method_exchangeImplementations(m1, m2);
- }
- #if GTM_PERFORM_KVO_CHECKS
- // This is only used when GTM_PERFORM_KVO_CHECKS is on.
- static void SwizzleClassMethodsInClass(Class cls, SEL sel1, SEL sel2) {
- Method m1 = class_getClassMethod(cls, sel1);
- Method m2 = class_getClassMethod(cls, sel2);
- method_exchangeImplementations(m1, m2);
- }
- #endif // GTM_PERFORM_KVO_CHECKS
- // This category exists to attempt to help deal with tricky KVO issues.
- // KVO is a wonderful technology in some ways, but is extremely fragile and
- // allows developers a lot of freedom to shoot themselves in the foot.
- // Refactoring an app that uses a lot of KVO can be really difficult, as can
- // debugging it.
- // These are some tools that we have found useful when working with KVO. Note
- // that these tools are only on in Debug builds.
- //
- // We have divided these tools up into two categories: Checks and Debugs.
- //
- // Debugs
- // Debugs are mainly for logging all the KVO/KVC that is occurring in your
- // application. To enable our KVO debugging, set the GTMDebugKVO environment
- // variable to 1 and you will get a whole pile of KVO logging that may help you
- // track down problems.
- // bash - export GTMDebugKVO=1
- // csh/tcsh - setenv GTMDebugKVO 1
- //
- // Checks
- // First we believe that instance variables should be private by default,
- // and that any KVO should be done via accessors. Apple by default allows KVO
- // to get at instance variables directly. Since our coding standards define
- // that instance variables should be @private, we feel that KVO shouldn't be
- // breaking this encapsulation. Unfortunately the @private, @protected
- // designators are a compile time convention only, and don't get carried over
- // into the runtime, so there's no way to check on an individual iVar what
- // it's visibility is. We therefore assume that an instance variable is private,
- // and disallow KVO access to instance variables. The problem with most KVO
- // issues is that they occur at runtime and unless you execute that case you
- // may never see the bug until it's too late. We try to force KVO issues to
- // rear their head at the time of the observing if at all possible.
- // Checks are on by default in debug builds. They can be turned off by defining
- // the compile flag GTM_PERFORM_KVO_CHECKS to 0
- // i.e. #define GTM_PERFORM_KVO_CHECKS 0, or set it
- // in GCC_PREPROCESSOR_DEFINITIONS.
- //
- // Checks work at a couple of different levels.
- // The most restrictive of the checks is that we set
- // |accessInstanceVariablesDirectly| to NO by default. This means that if you
- // attempt to perform KVO on an instance variable, you will get an exception
- // thrown.
- // Also, when adding an observer, we check to see if any member of the path
- // starts or ends with _ which by convention denotes an instance variable. If so
- // we warn you about attempting to access a ivar directly.
- @interface NSObject (GTMDebugKeyValueObserving)
- - (void)_gtmDebugAddObserver:(NSObject *)observer
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context;
- - (void)_gtmDebugArrayAddObserver:(NSObject *)observer
- toObjectsAtIndexes:(NSIndexSet *)indexes
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context;
- - (void)_gtmDebugRemoveObserver:(NSObject *)observer
- forKeyPath:(NSString *)keyPath;
- - (void)_gtmDebugArrayRemoveObserver:(NSObject *)observer
- fromObjectsAtIndexes:(NSIndexSet *)indexes
- forKeyPath:(NSString *)keyPath;
- - (void)_gtmDebugWillChangeValueForKey:(NSString*)key;
- - (void)_gtmDebugDidChangeValueForKey:(NSString*)key;
- #if GTM_PERFORM_KVO_CHECKS
- - (void)_gtmCheckAddObserver:(NSObject *)observer
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context;
- - (void)_gtmCheckAddObserver:(NSObject *)observer
- toObjectsAtIndexes:(NSIndexSet *)indexes
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context;
- + (BOOL)_gtmAccessInstanceVariablesDirectly;
- #endif // GTM_PERFORM_KVO_CHECKS
- @end
- @implementation NSObject (GTMDebugKeyValueObserving)
- GTM_METHOD_CHECK(NSObject, _gtmDebugAddObserver:forKeyPath:options:context:);
- GTM_METHOD_CHECK(NSObject, _gtmDebugRemoveObserver:forKeyPath:);
- GTM_METHOD_CHECK(NSObject, _gtmDebugWillChangeValueForKey:);
- GTM_METHOD_CHECK(NSObject, _gtmDebugDidChangeValueForKey:);
- GTM_METHOD_CHECK(NSArray,
- _gtmDebugArrayAddObserver:toObjectsAtIndexes:forKeyPath:options:context:);
- GTM_METHOD_CHECK(NSArray,
- _gtmDebugArrayRemoveObserver:fromObjectsAtIndexes:forKeyPath:);
- #if GTM_PERFORM_KVO_CHECKS
- GTM_METHOD_CHECK(NSObject,
- _gtmCheckAddObserver:forKeyPath:options:context:);
- GTM_METHOD_CHECK(NSArray,
- _gtmCheckAddObserver:toObjectsAtIndexes:forKeyPath:options:context:);
- GTM_METHOD_CHECK(NSObject,
- _gtmAccessInstanceVariablesDirectly);
- #endif // GTM_PERFORM_KVO_CHECKS
- + (void)load {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSDictionary *env = [[NSProcessInfo processInfo] environment];
- id debugKeyValue = [env valueForKey:@"GTMDebugKVO"];
- BOOL debug = NO;
- if ([debugKeyValue isKindOfClass:[NSNumber class]]) {
- debug = [debugKeyValue intValue] != 0 ? YES : NO;
- } else if ([debugKeyValue isKindOfClass:[NSString class]]) {
- debug = ([debugKeyValue hasPrefix:@"Y"] || [debugKeyValue hasPrefix:@"T"] ||
- [debugKeyValue intValue]);
- }
- Class cls = Nil;
- if (debug) {
- cls = [NSObject class];
- SwizzleMethodsInClass(cls,
- @selector(addObserver:forKeyPath:options:context:),
- @selector(_gtmDebugAddObserver:forKeyPath:options:context:));
- SwizzleMethodsInClass(cls,
- @selector(removeObserver:forKeyPath:),
- @selector(_gtmDebugRemoveObserver:forKeyPath:));
- SwizzleMethodsInClass(cls,
- @selector(willChangeValueForKey:),
- @selector(_gtmDebugWillChangeValueForKey:));
- SwizzleMethodsInClass(cls,
- @selector(didChangeValueForKey:),
- @selector(_gtmDebugDidChangeValueForKey:));
- cls = [NSArray class];
- SwizzleMethodsInClass(cls,
- @selector(addObserver:toObjectsAtIndexes:forKeyPath:options:context:),
- @selector(_gtmDebugArrayAddObserver:toObjectsAtIndexes:forKeyPath:options:context:));
- SwizzleMethodsInClass(cls,
- @selector(removeObserver:fromObjectsAtIndexes:forKeyPath:),
- @selector(_gtmDebugArrayRemoveObserver:fromObjectsAtIndexes:forKeyPath:));
- }
- #if GTM_PERFORM_KVO_CHECKS
- cls = [NSObject class];
- SwizzleMethodsInClass(cls,
- @selector(addObserver:forKeyPath:options:context:),
- @selector(_gtmCheckAddObserver:forKeyPath:options:context:));
- SwizzleClassMethodsInClass(cls,
- @selector(accessInstanceVariablesDirectly),
- @selector(_gtmAccessInstanceVariablesDirectly));
- cls = [NSArray class];
- SwizzleMethodsInClass(cls,
- @selector(addObserver:toObjectsAtIndexes:forKeyPath:options:context:),
- @selector(_gtmCheckAddObserver:toObjectsAtIndexes:forKeyPath:options:context:));
- #endif // GTM_PERFORM_KVO_CHECKS
- [pool drain];
- }
- - (void)_gtmDebugAddObserver:(NSObject *)observer
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context {
- _GTMDevLog(@"Adding observer %@ to %@ keypath '%@'", observer, self, keyPath);
- [self _gtmDebugAddObserver:observer forKeyPath:keyPath
- options:options context:context];
- }
- - (void)_gtmDebugArrayAddObserver:(NSObject *)observer
- toObjectsAtIndexes:(NSIndexSet *)indexes
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context {
- _GTMDevLog(@"Array adding observer %@ to indexes %@ of %@ keypath '%@'",
- observer, indexes, self, keyPath);
- [self _gtmDebugArrayAddObserver:observer
- toObjectsAtIndexes:indexes
- forKeyPath:keyPath
- options:options context:context];
- }
- - (void)_gtmDebugRemoveObserver:(NSObject *)observer
- forKeyPath:(NSString *)keyPath {
- _GTMDevLog(@"Removing observer %@ from %@ keypath '%@'",
- observer, self, keyPath);
- [self _gtmDebugRemoveObserver:observer forKeyPath:keyPath];
- }
- - (void)_gtmDebugArrayRemoveObserver:(NSObject *)observer
- fromObjectsAtIndexes:(NSIndexSet *)indexes
- forKeyPath:(NSString *)keyPath {
- _GTMDevLog(@"Array removing observer %@ from indexes %@ of %@ keypath '%@'",
- indexes, observer, self, keyPath);
- [self _gtmDebugArrayRemoveObserver:observer
- fromObjectsAtIndexes:indexes
- forKeyPath:keyPath];
- }
- - (void)_gtmDebugWillChangeValueForKey:(NSString*)key {
- _GTMDevLog(@"Will change '%@' of %@", key, self);
- [self _gtmDebugWillChangeValueForKey:key];
- }
- - (void)_gtmDebugDidChangeValueForKey:(NSString*)key {
- _GTMDevLog(@"Did change '%@' of %@", key, self);
- [self _gtmDebugDidChangeValueForKey:key];
- }
- #if GTM_PERFORM_KVO_CHECKS
- - (void)_gtmCheckAddObserver:(NSObject *)observer
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context {
- NSArray *keyPathElements = [keyPath componentsSeparatedByString:@"."];
- NSString *element;
- GTM_FOREACH_OBJECT(element, keyPathElements) {
- if ([element hasPrefix:@"_"] || [element hasSuffix:@"_"]) {
- _GTMDevLog(@"warning: %@ is registering an observation on what appears "
- @"to be a private ivar of %@ (or a sub keyed object) with "
- @"element %@ of keyPath %@.", observer, self, element,
- keyPath);
- }
- }
- [self _gtmCheckAddObserver:observer
- forKeyPath:keyPath
- options:options
- context:context];
- }
- - (void)_gtmCheckAddObserver:(NSObject *)observer
- toObjectsAtIndexes:(NSIndexSet *)indexes
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context {
- NSArray *keyPathElements = [keyPath componentsSeparatedByString:@"."];
- NSString *element;
- GTM_FOREACH_OBJECT(element, keyPathElements) {
- if ([element hasPrefix:@"_"] || [element hasSuffix:@"_"]) {
- _GTMDevLog(@"warning: %@ is registering an observation on what appears "
- @"to be a private ivar of %@ (or a sub keyed object) with "
- @"element %@ of keyPath %@.", observer, self, element,
- keyPath);
- }
- }
- [self _gtmCheckAddObserver:observer
- toObjectsAtIndexes:indexes
- forKeyPath:keyPath
- options:options
- context:context];
- }
- + (BOOL)_gtmAccessInstanceVariablesDirectly {
- // Apple has lots of "bad" direct instance variable accesses, so we
- // only want to check our code, as opposed to library code. iOS simulator
- // builds copy the app into the user's home directory. Xcode 4 also changes
- // the default location of the output directory. Don't count being within
- // the user's home and under "/Library/" as being a system library.
- // If this turns out to be slow, we may want to consider a cache to speed
- // things up.
- NSBundle *bundle = [NSBundle bundleForClass:self];
- NSString *path = [bundle bundlePath];
- BOOL hasLibrary = [path rangeOfString:@"/Library/"].location != NSNotFound;
- BOOL startsWithUser = [path hasPrefix:@"/Users/"];
- return !startsWithUser && hasLibrary;
- }
- #endif // GTM_PERFORM_KVO_CHECKS
- @end
- #endif // DEBUG