From 74ad2857a75567b273951be9cbe998133fbca26a Mon Sep 17 00:00:00 2001 From: "thomasvl@gmail.com" Date: Tue, 3 Feb 2009 17:09:43 +0000 Subject: - Added GTMNSObject+KeyValueObserving to make it easier on folks to do KVO "correctly". Based on some excellent code by Michael Ash. http://www.mikeash.com/?page=pyblog/key-value-observing-done-right.html This has been added for iPhone and OS X. - Fixed up GTMSenTestCase on iPhone so that it has a description that matches that of OCUnit. --- Foundation/GTMNSObject+KeyValueObserving.m | 350 +++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 Foundation/GTMNSObject+KeyValueObserving.m (limited to 'Foundation/GTMNSObject+KeyValueObserving.m') diff --git a/Foundation/GTMNSObject+KeyValueObserving.m b/Foundation/GTMNSObject+KeyValueObserving.m new file mode 100644 index 0000000..dc6883d --- /dev/null +++ b/Foundation/GTMNSObject+KeyValueObserving.m @@ -0,0 +1,350 @@ +// +// 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 -- cgit v1.2.3