// // GTMNSObject+KeyValueObserving.h // // 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 "GTMNSObject+KeyValueObserving.h" #import #import "GTMDefines.h" #import "GTMDebugSelectorValidation.h" #import "GTMObjC2Runtime.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_; 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; } - (void)dealloc { [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_]; } @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 = [NSString stringWithFormat:@"%p:%p:%@:%p", observer, target, keyPath, selector]; 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) { #if DEBUG GTMKeyValueObservingHelper *oldHelper = [observerHelpers_ objectForKey:key]; if (oldHelper) { _GTMDevLog(@"%@ already observing %@ forKeyPath %@", observer, target, keyPath); } #endif // DEBUG [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]; GTMKeyValueObservingHelper *helper = nil; @synchronized(self) { helper = [[observerHelpers_ objectForKey:key] retain]; #if DEBUG if (!helper) { _GTMDevLog(@"%@ was not observing %@ with keypath %@", observer, target, keyPath); } #endif // DEBUG [observerHelpers_ removeObjectForKey:key]; } [helper deregister]; [helper release]; } @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]; } @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]]); } - (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