From 1ddb352f1d7621deeb93848dedf034dc5d271a1f Mon Sep 17 00:00:00 2001 From: "gtm.daemon" Date: Tue, 11 Aug 2009 18:30:29 +0000 Subject: [Author: dmaclach] Fixed up GTMGoogleSearch so that it actually works on the iPhone Device. Fixed up some GTMNSObject+KVO issues on the iPhone Device. Fixed up GTMObjC2Runtime so that it actually compiles on the device. Added debugging capabilities to KVO with GTM. Removed unnecessary GTM_* system version macros from GTMDefines.h R=thomasvl DELTA=185 (153 added, 19 deleted, 13 changed) --- Foundation/GTMGoogleSearch.h | 4 + Foundation/GTMGoogleSearch.m | 11 ++- Foundation/GTMGoogleSearchTest.m | 15 ++- Foundation/GTMNSObject+KeyValueObserving.m | 124 ++++++++++++++++++++++++- Foundation/GTMNSObject+KeyValueObservingTest.m | 3 + Foundation/GTMObjC2Runtime.h | 17 ++-- GTMDefines.h | 23 +---- ReleaseNotes.txt | 5 + 8 files changed, 168 insertions(+), 34 deletions(-) diff --git a/Foundation/GTMGoogleSearch.h b/Foundation/GTMGoogleSearch.h index d18aadf..06b8d51 100644 --- a/Foundation/GTMGoogleSearch.h +++ b/Foundation/GTMGoogleSearch.h @@ -32,6 +32,10 @@ #define GTMGoogleSearchBooks @"books" #define GTMGoogleSearchWeb @"search" +// iPhone doesn't support distributed notifications, so this controls whether +// or not we enable them in this class. +#define GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS GTM_MACOS_SDK + // Composes URLs and searches for google properties in the correct language // and domain. @interface GTMGoogleSearch : NSObject { diff --git a/Foundation/GTMGoogleSearch.m b/Foundation/GTMGoogleSearch.m index 586efd3..56dcc82 100644 --- a/Foundation/GTMGoogleSearch.m +++ b/Foundation/GTMGoogleSearch.m @@ -131,6 +131,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMGoogleSearch, sharedInstance); - (id)init { self = [super init]; if (self != nil) { +#if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS // register for the notification NSDistributedNotificationCenter *distCenter = [NSDistributedNotificationCenter defaultCenter]; @@ -138,7 +139,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMGoogleSearch, sharedInstance); selector:@selector(reloadAllAppCachedValues:) name:kNotificationName object:nil]; - +#endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS // load the allApps value [self reloadAllAppCachedValues:nil]; @@ -175,13 +176,17 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMGoogleSearch, sharedInstance); return self; } +#if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS - (void)finalize { [[NSDistributedNotificationCenter defaultCenter] removeObject:self]; [super finalize]; } +#endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS - (void)dealloc { +#if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS [[NSDistributedNotificationCenter defaultCenter] removeObject:self]; +#endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS [allAppsCachedDomain_ release]; [allAppsCachedLanguage_ release]; [curAppCachedDomain_ release]; @@ -341,7 +346,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMGoogleSearch, sharedInstance); NSNull *nsNull = [NSNull null]; GTM_FOREACH_KEY(key, args) { NSString *object = [args objectForKey:key]; - if (![object isEqualTo:nsNull]) { + if (![object isEqual:nsNull]) { #if DEBUG // In debug we check key and object for things that should be escaped. // Note that percent is not in there because escaped strings will have @@ -515,6 +520,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMGoogleSearch, sharedInstance); allAppsCachedDomain_ = [domain copy]; allAppsCachedLanguage_ = [language copy]; +#if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS // NOTE: we'll go ahead and reload when this comes back to ourselves since // there is a race here if two folks wrote at about the same time. NSDistributedNotificationCenter *distCenter = @@ -522,6 +528,7 @@ GTMOBJECT_SINGLETON_BOILERPLATE(GTMGoogleSearch, sharedInstance); [distCenter postNotificationName:kNotificationName object:nil userInfo:nil]; +#endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS } @end diff --git a/Foundation/GTMGoogleSearchTest.m b/Foundation/GTMGoogleSearchTest.m index 8e737c9..45f7e66 100644 --- a/Foundation/GTMGoogleSearchTest.m +++ b/Foundation/GTMGoogleSearchTest.m @@ -166,15 +166,20 @@ STAssertEqualObjects(domain, @"domain", nil); STAssertEqualObjects(lang, @"lang", nil); STAssertFalse(areCurrentAppOnly, nil); +#if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS + // We don't test launching other tasks on the phone since this isn't a valid + // case until we can support real multiple tasks on the phone. // try changing the value directly in the plist file (as if another app had // done it) and sending our notification. [[NSTask launchedTaskWithLaunchPath:@"/usr/bin/defaults" arguments:[NSArray arrayWithObjects:@"write", @"com.google.GoogleSearchAllApps", - @"{ \"com.google.PreferredDomain\" = xxx; \"com.google.PreferredLanguage\" = yyy; }", + @"{ \"com.google.PreferredDomain\" = xxx;" + @"\"com.google.PreferredLanguage\" = yyy; }", nil]] waitUntilExit]; - // Sleep for a moment to let things flush (seen rarely as a problem on aharper's machine) + // Sleep for a moment to let things flush + // (seen rarely as a problem on aharper's machine). sleep(1); NSDistributedNotificationCenter *distCenter = [NSDistributedNotificationCenter defaultCenter]; @@ -184,7 +189,8 @@ options:NSNotificationDeliverImmediately]; // Spin the runloop so the notifications fire. - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + NSRunLoop *currentLoop = [NSRunLoop currentRunLoop]; + [currentLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; // did we get what we expected? [googleSearch preferredDomain:&domain language:&lang @@ -192,7 +198,8 @@ STAssertEqualObjects(domain, @"xxx", nil); STAssertEqualObjects(lang, @"yyy", nil); STAssertFalse(areCurrentAppOnly, nil); - +#endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS + // lastly, clean up what we set for all apps to leave the system at the // default. [googleSearch clearPreferredDomainAndLanguageForAllApps]; diff --git a/Foundation/GTMNSObject+KeyValueObserving.m b/Foundation/GTMNSObject+KeyValueObserving.m index dc6883d..e164e03 100644 --- a/Foundation/GTMNSObject+KeyValueObserving.m +++ b/Foundation/GTMNSObject+KeyValueObserving.m @@ -27,8 +27,6 @@ // See comment in header. #import "GTMNSObject+KeyValueObserving.h" - -#import #import "GTMDefines.h" #import "GTMDebugSelectorValidation.h" #import "GTMObjC2Runtime.h" @@ -115,6 +113,13 @@ static char* GTMKeyValueObservingHelperContext return self; } +- (NSString *)description { + return [NSString stringWithFormat: + @"%@ ", + [self class], observer_, keyPath_, target_, + NSStringFromSelector(selector_)]; +} + - (void)dealloc { [userInfo_ release]; [keyPath_ release]; @@ -327,6 +332,12 @@ static char* GTMKeyValueObservingHelperContext && [change_ isEqual:[object change]]); } +- (NSString *)description { + return [NSString stringWithFormat: + @"%@ ", + [self class], object_, keyPath_, userInfo_, change_]; +} + - (NSUInteger)hash { return [keyPath_ hash] + [object_ hash] + [userInfo_ hash] + [change_ hash]; } @@ -348,3 +359,112 @@ static char* GTMKeyValueObservingHelperContext } @end + +#ifdef DEBUG + +// This category exists to attempt to help debug tricky KVO issues. +// Set the GTMDebugKVO environment variable to 1 and you will get a whole +// pile of KVO notifications that may help you track down problems. +@interface NSObject (GTMDebugKeyValueObserving) +@end + +@implementation NSObject (GTMDebugKeyValueObserving) ++ (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]); + } + if (debug) { + Class cls = [NSObject class]; + SEL addSelector + = NSSelectorFromString(@"addObserver:forKeyPath:options:context:"); + SEL removeSelector = NSSelectorFromString(@"removeObserver:forKeyPath:"); + SEL debugAddSelector + = NSSelectorFromString(@"_gtmDebugAddObserver:forKeyPath:options:context:"); + SEL debugRemoveSelector + = NSSelectorFromString(@"_gtmDebugRemoveObserver:forKeyPath:"); + SEL willChangeValueSelector = NSSelectorFromString(@"willChangeValueForKey:"); + SEL didChangeValueSelector = NSSelectorFromString(@"didChangeValueForKey:"); + SEL debugWillChangeValueSelector + = NSSelectorFromString(@"_gtmDebugWillChangeValueForKey:"); + SEL debugDidChangeValueSelector + = NSSelectorFromString(@"_gtmDebugDidChangeValueForKey:"); + + Method m1 = class_getInstanceMethod(cls, addSelector); + Method m2 = class_getInstanceMethod(cls, debugAddSelector); + method_exchangeImplementations(m1, m2); + m1 = class_getInstanceMethod(cls, removeSelector); + m2 = class_getInstanceMethod(cls, debugRemoveSelector); + method_exchangeImplementations(m1, m2); + m1 = class_getInstanceMethod(cls, willChangeValueSelector); + m2 = class_getInstanceMethod(cls, debugWillChangeValueSelector); + method_exchangeImplementations(m1, m2); + m1 = class_getInstanceMethod(cls, didChangeValueSelector); + m2 = class_getInstanceMethod(cls, debugDidChangeValueSelector); + method_exchangeImplementations(m1, m2); + + debugAddSelector + = NSSelectorFromString(@"_gtmDebugArrayAddObserver:forKeyPath:options:context:"); + debugRemoveSelector + = NSSelectorFromString(@"_gtmDebugArrayRemoveObserver:forKeyPath:"); + + cls = [NSArray class]; + m1 = class_getInstanceMethod(cls, addSelector); + m2 = class_getInstanceMethod(cls, debugAddSelector); + method_exchangeImplementations(m1, m2); + m1 = class_getInstanceMethod(cls, removeSelector); + m2 = class_getInstanceMethod(cls, debugRemoveSelector); + method_exchangeImplementations(m1, m2); + } + [pool release]; +} + +- (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 + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { + _GTMDevLog(@"Array adding observer %@ to %@ keypath '%@'", observer, self, keyPath); + [self _gtmDebugArrayAddObserver:observer 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 + forKeyPath:(NSString *)keyPath { + _GTMDevLog(@"Array removing observer %@ from %@ keypath '%@'", observer, self, keyPath); + [self _gtmDebugArrayRemoveObserver:observer 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]; +} + +#endif // DEBUG + +@end diff --git a/Foundation/GTMNSObject+KeyValueObservingTest.m b/Foundation/GTMNSObject+KeyValueObservingTest.m index b97f7ff..ec35bc3 100644 --- a/Foundation/GTMNSObject+KeyValueObservingTest.m +++ b/Foundation/GTMNSObject+KeyValueObservingTest.m @@ -87,6 +87,9 @@ selector:@selector(observeValueChange:) userInfo:@"userInfo" options:NSKeyValueObservingOptionNew]; + [dict_ gtm_removeObserver:self + forKeyPath:@"key" + selector:@selector(observeValueChange:)]; } - (void)observeValueChange:(GTMKeyValueChangeNotification *)notification { diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h index 2c25841..4f569a5 100644 --- a/Foundation/GTMObjC2Runtime.h +++ b/Foundation/GTMObjC2Runtime.h @@ -75,7 +75,7 @@ OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protoco // barrier versions. GTM_INLINE bool OSAtomicCompareAndSwapPtrBarrier(void *predicate, void *replacement, - volatile void *theValue) { + void * volatile *theValue) { #if defined(__LP64__) && __LP64__ return OSAtomicCompareAndSwap64Barrier((int64_t)predicate, (int64_t)replacement, @@ -86,21 +86,24 @@ GTM_INLINE bool OSAtomicCompareAndSwapPtrBarrier(void *predicate, (int32_t *)theValue); #endif // defined(__LP64__) && __LP64__ } - + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + +#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) || (GTM_IPHONE_DEVICE) + GTM_INLINE BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtrBarrier(predicate, replacement, - objectLocation); + (void * volatile *)objectLocation); } GTM_INLINE BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtrBarrier(predicate, replacement, - objectLocation); + (void * volatile *)objectLocation); } -#endif - -#endif // OBJC2_UNAVAILABLE +#endif // (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) || (GTM_IPHONE_DEVICE) diff --git a/GTMDefines.h b/GTMDefines.h index 2bb4486..3c69547 100644 --- a/GTMDefines.h +++ b/GTMDefines.h @@ -29,21 +29,6 @@ #define MAC_OS_X_VERSION_10_6 1060 #endif -// These definitions exist to allow headerdoc to parse this file. -// Headerdoc 8.6 gives warnings about misuses of MAC_OS_X_VERSION_MIN_REQUIRED -// and MAC_OS_X_VERSION_MAX_ALLOWED if you use them directly. -// By defining GTM versions with slightly different names (MIN vs MINIMUM) -// we get around headerdoc's issues. Hopefully we can work around this in the -// future and get rid of the GTM versions, so please use the default ones -// wherever you can. -#ifndef GTM_MAC_OS_X_VERSION_MINIMUM_REQUIRED - #define GTM_MAC_OS_X_VERSION_MINIMUM_REQUIRED MAC_OS_X_VERSION_MIN_REQUIRED -#endif - -#ifndef GTM_MAC_OS_X_VERSION_MAXIMUM_ALLOWED - #define GTM_MAC_OS_X_VERSION_MAXIMUM_ALLOWED MAC_OS_X_VERSION_MAX_ALLOWED -#endif - // ---------------------------------------------------------------------------- // CPP symbols that can be overridden in a prefix to control how the toolbox // is compiled. @@ -165,7 +150,7 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); // does keys, so pick the right thing, nothing is done on the FastEnumeration // side to be sure you're getting what you wanted. #ifndef GTM_FOREACH_OBJECT - #if TARGET_OS_IPHONE || (GTM_MAC_OS_X_VERSION_MINIMUM_REQUIRED >= MAC_OS_X_VERSION_10_5) + #if TARGET_OS_IPHONE || !(MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) #define GTM_FOREACH_ENUMEREE(element, enumeration) \ for (element in enumeration) #define GTM_FOREACH_OBJECT(element, collection) \ @@ -224,7 +209,7 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); #else // We can't find a symbol to tell if GC is supported/required, so best we // do on Mac targets is include it if we're on 10.5 or later. - #if GTM_MAC_OS_X_VERSION_MAXIMUM_ALLOWED <= MAC_OS_X_VERSION_10_4 + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 #define GTM_SUPPORT_GC 0 #else #define GTM_SUPPORT_GC 1 @@ -234,7 +219,7 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); // To simplify support for 64bit (and Leopard in general), we provide the type // defines for non Leopard SDKs -#if GTM_MAC_OS_X_VERSION_MAXIMUM_ALLOWED <= MAC_OS_X_VERSION_10_4 +#if !(MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) // NSInteger/NSUInteger and Max/Mins #ifndef NSINTEGER_DEFINED #if __LP64__ || NS_BUILD_32_LIKE_64 @@ -265,4 +250,4 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); #endif /* !defined(__LP64__) || !__LP64__ */ #define CGFLOAT_DEFINED 1 #endif // CGFLOAT_DEFINED -#endif // GTM_MAC_OS_X_VERSION_MAXIMUM_ALLOWED <= MAC_OS_X_VERSION_10_4 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index aa7083a..d2a30b1 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -316,6 +316,11 @@ Changes since 1.5.1 - Added gtm_dictionaryWithHttpArgumentsString to NSDictionary+URLArguments. +- Added GTMDebugKeyValueObserving category to NSObject. This makes debugging + KVO a little easier in some cases. To turn it on, set the "GTMDebugKVO" + environment variable to "1". It will output a lot of data about adding and + removing KVO observers, and when the values are actually changed. + Release 1.5.1 Changes since 1.5.0 -- cgit v1.2.3