From e9fe951720e98ca768e2c51d05fa257ecfd6b894 Mon Sep 17 00:00:00 2001 From: "gtm.daemon" Date: Wed, 6 May 2009 17:30:40 +0000 Subject: [Author: dmaclach] Made GTMABAddressBook work on both the iPhone and the Desktop, giving you a single interface to both. DELTA=2539 (2173 added, 79 deleted, 287 changed) R=thomasvl --- AddressBook/GTMABAddressBook.h | 403 ++++++++++++ AddressBook/GTMABAddressBook.m | 1186 ++++++++++++++++++++++++++++++++++ AddressBook/GTMABAddressBook.strings | Bin 0 -> 1428 bytes AddressBook/GTMABAddressBookTest.m | 689 ++++++++++++++++++++ AddressBook/TestData/phone.png | Bin 0 -> 3242 bytes GTM.xcodeproj/project.pbxproj | 272 +++++++- GTMDefines.h | 22 +- GTMiPhone.xcodeproj/project.pbxproj | 56 +- ReleaseNotes.txt | 10 +- 9 files changed, 2608 insertions(+), 30 deletions(-) create mode 100644 AddressBook/GTMABAddressBook.h create mode 100644 AddressBook/GTMABAddressBook.m create mode 100644 AddressBook/GTMABAddressBook.strings create mode 100644 AddressBook/GTMABAddressBookTest.m create mode 100644 AddressBook/TestData/phone.png diff --git a/AddressBook/GTMABAddressBook.h b/AddressBook/GTMABAddressBook.h new file mode 100644 index 0000000..a3dfba3 --- /dev/null +++ b/AddressBook/GTMABAddressBook.h @@ -0,0 +1,403 @@ +// +// GTMABAddressBook.h +// +// Copyright 2008 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. +// + +// These classes wrap up the iPhone AddressBook 'C' API in a manner very +// similar to that found on Mac OS X. They differ only in that none of these +// routines throws, and some of the types are different as necessitated by +// the APIs that they wrap. These wrappers also protect you from a number +// of issues in the AddressBook API (as of iPhone SDK 2.0/2.1) +// +// Note that there is a strings file that you may want to localize +// (GTMABAddressBook.strings). +// +// If things seem strange, it may be due to one of the following radars: +// 6240394 AddressBook framework constants not initialized until +// ABCreateAddressBook called +// -- CLOSED as designed +// 6208390 Integer and real values don't work in ABMultiValueRefs +// (and this isn't part of the title, but dictionaries don't work +// either) +// 6207605 RecordIDs for people and groups are not unique in AddressBook +// -- CLOSED as designed +// 6204021 kABGroupNameProperty and kABPersonFirstNameProperty have the same +// value +// 6203982 ABPersonCopyLocalizedPropertyName returns name for +// kABGroupNameProperty +// 6203961 ABPersonGetTypeOfProperty returns a type for kABGroupNameProperty +// 6203854 ABMultiValues hash to their address +// 6203836 ABRecords hash to their address +// -- CLOSED behaves correctly +// 6203606 Need CFTypeIDs for AddressBook CFTypes +// 6202868 ABPersonSetImageData should validate image data +// 6202860 Passing nil person into ABGroupAddMember crashes +// -- CLOSED behaves correctly +// 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash +// -- CLOSED behaves correctly +// 6202807 ABMultiValueInsertValueAndLabelAtIndex allows you to insert values +// past end +// 6201276 Removing a NULL record using ABAddressBookRemoveRecord crashes +// -- CLOSED behaves correctly +// 6201258 Adding a NULL record using ABAddressBookAddRecord crashes +// -- CLOSED behaves correctly +// 6201046 ABRecordSetValue returns true even if you pass in a bad type for a +// value +// 6201005 ABRecordRemoveValue returns true for value that aren't in the record +// -- CLOSED behaves correctly +// 6200703 ABAddressBookAddRecord doesn't add an item to the people array until +// it's saved +// 6200638 ABAddressBookHasUnsavedChanges doesn't work + +#import "GTMDefines.h" +#import + +#if GTM_IPHONE_SDK +#import +#else // GTM_IPHONE_SDK +#import +#import +#endif // GTM_IPHONE_SDK + +@class GTMABPerson; +@class GTMABGroup; +@class GTMABRecord; + +GTM_EXTERN NSString *const kGTMABUnknownPropertyName; + +#if GTM_IPHONE_SDK + +@class UIImage; +typedef ABRecordID GTMABRecordID; +typedef ABPropertyID GTMABPropertyID; +typedef UIImage GTMABImage; +typedef ABPersonCompositeNameFormat GTMABPersonCompositeNameFormat; +typedef ABMultiValueIdentifier GTMABMultiValueIdentifier; + enum _GTMABPropertyType { + kGTMABInvalidPropertyType = kABInvalidPropertyType, + kGTMABStringPropertyType = kABStringPropertyType, + kGTMABIntegerPropertyType = kABIntegerPropertyType, + kGTMABRealPropertyType = kABRealPropertyType, + kGTMABDateTimePropertyType = kABDateTimePropertyType, + kGTMABDictionaryPropertyType = kABDictionaryPropertyType, + kGTMABMultiStringPropertyType = kABMultiStringPropertyType, + kGTMABMultiIntegerPropertyType = kABMultiIntegerPropertyType, + kGTMABMultiRealPropertyType = kABMultiRealPropertyType, + kGTMABMultiDateTimePropertyType = kABMultiDateTimePropertyType, + kGTMABMultiDictionaryPropertyType = kABMultiDictionaryPropertyType, +}; +typedef CFIndex GTMABPropertyType; +#define kGTMABPersonFirstNameProperty kABPersonFirstNameProperty +#define kGTMABPersonLastNameProperty kABPersonLastNameProperty +#define kGTMABPersonBirthdayProperty kABPersonBirthdayProperty +#define kGTMABPersonPhoneProperty kABPersonPhoneProperty +#define kGTMABPersonPhoneMainLabel kABPersonPhoneMainLabel +#define kGTMABGroupNameProperty kABGroupNameProperty + +#define kGTMABMultiValueInvalidIdentifier kABMultiValueInvalidIdentifier +#define kGTMABRecordInvalidID kABRecordInvalidID + +#else // GTM_IPHONE_SDK + +typedef NSString* GTMABRecordID; +typedef NSString* GTMABPropertyID; +typedef NSString* GTMABMultiValueIdentifier; +typedef NSImage GTMABImage; +typedef uint32_t GTMABPersonCompositeNameFormat; +enum { + kABPersonCompositeNameFormatFirstNameFirst = 0, + kABPersonCompositeNameFormatLastNameFirst = 1 +}; +enum _GTMABPropertyType { + kGTMABInvalidPropertyType = kABErrorInProperty, + kGTMABStringPropertyType = kABStringProperty, + kGTMABIntegerPropertyType = kABIntegerProperty, + kGTMABRealPropertyType = kABRealProperty, + kGTMABDateTimePropertyType = kABDateProperty, + kGTMABDictionaryPropertyType = kABDictionaryProperty, + kGTMABMultiStringPropertyType = kABMultiStringProperty, + kGTMABMultiIntegerPropertyType = kABMultiIntegerProperty, + kGTMABMultiRealPropertyType = kABMultiRealProperty, + kGTMABMultiDateTimePropertyType = kABMultiDateProperty, + kGTMABMultiDictionaryPropertyType = kABMultiDictionaryProperty, +}; +typedef CFIndex GTMABPropertyType; +#define kGTMABPersonFirstNameProperty kABFirstNameProperty +#define kGTMABPersonLastNameProperty kABLastNameProperty +#define kGTMABPersonBirthdayProperty kABBirthdayProperty +#define kGTMABPersonPhoneProperty kABPhoneProperty +#define kGTMABPersonPhoneMainLabel kABPhoneMainLabel +#define kGTMABGroupNameProperty kABGroupNameProperty + +#define kGTMABMultiValueInvalidIdentifier @"ABMultiValueInvalidIdentifier" +#define kGTMABRecordInvalidID @"ABRecordInvalidID" +extern NSString* const kABPersonRecordType; +extern NSString* const kABGroupRecordType; + +#endif // GTM_IPHONE_SDK + +// Wrapper for an AddressBook on iPhone +@interface GTMABAddressBook : NSObject { + @private + GTM_WEAK_ON_MACOS ABAddressBookRef addressBook_; +} + +// Returns a new instance of an address book. ++ (GTMABAddressBook *)addressBook; + +// Return the address book reference +- (ABAddressBookRef)addressBookRef; + +// Saves changes made since the last save +// Return YES if successful (or there was no change) +- (BOOL)save; + +// Returns YES if there are unsaved changes +// The unsaved changes flag is automatically set when changes are made +// As of iPhone 2.1, this does not work, and will always return NO. +// Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work +- (BOOL)hasUnsavedChanges; + +// Returns a GTMABPerson matching an ID +// Returns nil if the record could not be found +- (GTMABPerson *)personForId:(GTMABRecordID)uniqueId; + +// Returns a GTMABGroup matching an ID +// Returns nil if the record could not be found +- (GTMABGroup *)groupForId:(GTMABRecordID)uniqueId; + +// Adds a record (ABPerson or ABGroup) to the AddressBook database +// Be sure to read notes for -people and -group. +- (BOOL)addRecord:(GTMABRecord *)record; + +// Removes a record (ABPerson or ABGroup) from the AddressBook database +- (BOOL)removeRecord:(GTMABRecord *)record; + +// Returns an array (GTMABPerson) of all the people in the AddressBook database +// As of iPhone 2.1, this array will not contain new entries until you save +// the address book. +// Radar 6200703: ABAddressBookAddRecord doesn't add an item to the people array +// until it's saved +- (NSArray *)people; + +// Returns an array of all the groups (GTMABGroup) in the AddressBook database +// As of iPhone 2.1, this array will not contain new entries until you save +// the address book. +// Radar 6200703: ABAddressBookAddRecord doesn't add an item to the people array +// until it's saved +- (NSArray *)groups; + +// Performs a prefix search on the composite names of people in an address book +// and returns an array of persons that match the search criteria. +// Ignores case. +- (NSArray *)peopleWithCompositeNameWithPrefix:(NSString *)prefix; + +// Performs a prefix search on the composite names of groups in an address book +// and returns an array of groups that match the search criteria. +// Ignores case. +- (NSArray *)groupsWithCompositeNameWithPrefix:(NSString *)prefix; + +// Returns a localized name for a given label ++ (NSString *)localizedLabel:(NSString *)label; + +@end + +// Wrapper for a ABRecord on iPhone. +// A abstract class. Instantiate one of the concrete subclasses, GTMABPerson or +// GTMABGroup. +@interface GTMABRecord : NSObject { + @private + ABRecordRef record_; +} + +// Create a record with a recordRef. +// Since GTMABRecord is an abstract base class, attempting to create one +// of these directly will throw an exception. Use with one of the concrete +// subclasses. ++ (id)recordWithRecord:(ABRecordRef)record; + +// Designated initializer +// Since GTMABRecord is an abstract base class, attempting to create one +// of these directly will throw an exception. Use with one of the concrete +// subclasses. +- (id)initWithRecord:(ABRecordRef)record; + +// Return our recordRef +- (ABRecordRef)recordRef; + +// Return the recordID for the record +- (GTMABRecordID)recordID; + +// Returns the value of a given property. +// The type of the value depends on the property type. +- (id)valueForProperty:(GTMABPropertyID)property; + +// Set the value of a given property. +// The type of the value must match the property type. +// Returns YES if value set properly +- (BOOL)setValue:(id)value forProperty:(GTMABPropertyID)property; + +// Removes the value for the property +// Returns yes if value removed +- (BOOL)removeValueForProperty:(GTMABPropertyID)property; + +// returns a human friendly name for the record +- (NSString *)compositeName; + +// returns the type of a property ++ (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property; + +// returns a human friendly localized name for a property ++ (NSString *)localizedPropertyName:(GTMABPropertyID)property; +@end + +// Wrapper for an ABPerson on iPhone +@interface GTMABPerson : GTMABRecord + +// Creates a person with a first name and a last name. ++ (GTMABPerson *)personWithFirstName:(NSString *)first + lastName:(NSString *)last; + +// Sets image data for a person. Data must be to a block of data that +// will create a valid GTMABImage. +- (BOOL)setImageData:(NSData *)data; + +// Returns the image data. +- (NSData *)imageData; + +// Returns the image for a person +- (GTMABImage *)image; + +// Sets a the image for a person +- (BOOL)setImage:(GTMABImage *)image; + +// Returns the format in with names are composited ++ (GTMABPersonCompositeNameFormat)compositeNameFormat; +@end + +// Wrapper for a ABGroup on iPhone +@interface GTMABGroup : GTMABRecord +// Create a new group named |name| ++ (GTMABGroup *)groupNamed:(NSString *)name; + +// Return an array of members (GTMABPerson) +- (NSArray *)members; + +// Add a member to a group +- (BOOL)addMember:(GTMABPerson *)person; + +// Remove a member from a group +- (BOOL)removeMember:(GTMABPerson *)person; +@end + +// GTMABMultiValue does not support NSFastEnumeration because in +// the Apple frameworks it returns identifiers which are already NSStrings. +// In our case identifiers aren't NS types, and it doesn't make sense +// to convert them to NSNumbers just to convert them back so you can +// actually get at the values and labels. +// Instead we supply valueEnumerator and labelEnumerator which you can +// fast enumerate on to get values and labels directly. +@interface GTMABMultiValue : NSObject { + @protected + ABMultiValueRef multiValue_; +} + +// Create a multi value +- (id)initWithMultiValue:(ABMultiValueRef)multiValue; + +// return it's ref +- (ABMultiValueRef)multiValueRef; + +// Returns the number of value/label pairs +- (NSUInteger)count; + +// Returns a value at a given index +// Returns nil if index is out of bounds +- (id)valueAtIndex:(NSUInteger)idx; + +// Returns a label at a given index +// Returns nil if index is out of bounds +- (NSString *)labelAtIndex:(NSUInteger)idx; + +// Returns an identifier at a given index +// Returns kABMultiValueInvalidIdentifier if index is out of bounds +- (GTMABMultiValueIdentifier)identifierAtIndex:(NSUInteger)idx; + +// Returns the index of a given identifier +// Returns NSNotFound if not found +- (NSUInteger)indexForIdentifier:(GTMABMultiValueIdentifier)identifier; + +// Type of the contents of this multivalue +- (GTMABPropertyType)propertyType; + +// Returns the value for a given identifier +// Returns nil if the identifier is not found +- (id)valueForIdentifier:(GTMABMultiValueIdentifier)identifier; + +// Returns the value for a given identifier +// Returns nil if the identifier is not found +- (NSString *)labelForIdentifier:(GTMABMultiValueIdentifier)identifier; + +// Returns an enumerator for enumerating through values +- (NSEnumerator *)valueEnumerator; + +// Returns an enumerator for enumerating through labels +- (NSEnumerator *)labelEnumerator; + +@end + +@interface GTMABMutableMultiValue : GTMABMultiValue { + @private + // Use unsigned long here instead of NSUInteger because that's what + // NSFastEnumeration Protocol wants currently (iPhone 2.1) + unsigned long mutations_; +} + +// Create a new mutable multivalue with a given type ++ (id)valueWithPropertyType:(GTMABPropertyType)type; + +// Create a new mutable multivalue with a given type +- (id)initWithPropertyType:(GTMABPropertyType)type; + +// Create a new mutable multivalue based on |multiValue| +- (id)initWithMutableMultiValue:(ABMutableMultiValueRef)multiValue; + +// Adds a value with its label +// Returns the identifier if successful, kABMultiValueInvalidIdentifier +// otherwise. +- (GTMABMultiValueIdentifier)addValue:(id)value withLabel:(CFStringRef)label; + +// Insert a value/label pair at a given index +// Returns the identifier if successful. kABMultiValueInvalidIdentifier +// otherwise +// If index is out of bounds, returns kABMultiValueInvalidIdentifier. +- (GTMABMultiValueIdentifier)insertValue:(id)value + withLabel:(CFStringRef)label + atIndex:(NSUInteger)index; + +// Removes a value/label pair at a given index +// Returns NO if index out of bounds +- (BOOL)removeValueAndLabelAtIndex:(NSUInteger)index; + +// Replaces a value at a given index +// Returns NO if index out of bounds +- (BOOL)replaceValueAtIndex:(NSUInteger)index withValue:(id)value; + +// Replaces a label at a given index +// Returns NO if index out of bounds +- (BOOL)replaceLabelAtIndex:(NSUInteger)index withLabel:(CFStringRef)label; + +@end diff --git a/AddressBook/GTMABAddressBook.m b/AddressBook/GTMABAddressBook.m new file mode 100644 index 0000000..b7920fe --- /dev/null +++ b/AddressBook/GTMABAddressBook.m @@ -0,0 +1,1186 @@ +// +// GTMAddressBook.m +// +// Copyright 2008 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. +// + +#import "GTMABAddressBook.h" +#import "GTMGarbageCollection.h" + +NSString *const kGTMABUnknownPropertyName = @"UNKNOWN_PROPERTY"; + +typedef struct { + GTMABPropertyType pType; + Class class; +} TypeClassNameMap; + +@interface GTMABMultiValue () +- (unsigned long*)mutations; +@end + +@interface GTMABMutableMultiValue () +// Checks to see if a value is a valid type to be stored in this multivalue +- (BOOL)checkValueType:(id)value; +@end + +@interface GTMABMultiValueEnumerator : NSEnumerator { + @private + __weak ABMultiValueRef ref_; // ref_ cached from enumeree_ + GTMABMultiValue *enumeree_; + unsigned long mutations_; + NSUInteger count_; + NSUInteger index_; + BOOL useLabels_; +} ++ (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree; ++ (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree; +- (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(id *)stackbuf + count:(NSUInteger)len; +#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 +@end + +@implementation GTMABAddressBook ++ (GTMABAddressBook *)addressBook { + return [[[self alloc] init] autorelease]; +} + +- (id)init { + if ((self = [super init])) { +#if GTM_IPHONE_SDK + addressBook_ = ABAddressBookCreate(); +#else // GTM_IPHONE_SDK + addressBook_ = ABGetSharedAddressBook(); +#endif // GTM_IPHONE_SDK + if (!addressBook_) { + // COV_NF_START + [self release]; + self = nil; + // COV_NF_END + } + } + return self; +} + +- (void)dealloc { +#if GTM_IPHONE_SDK + if (addressBook_) { + CFRelease(addressBook_); + } +#endif // GTM_IPHONE_SDK + [super dealloc]; +} + +- (BOOL)save { +#if GTM_IPHONE_SDK + CFErrorRef cfError = NULL; + bool wasGood = ABAddressBookSave(addressBook_, &cfError); + if (!wasGood) { + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + } +#else // GTM_IPHONE_SDK + bool wasGood = ABSave(addressBook_); +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +- (BOOL)hasUnsavedChanges { + bool hasUnsavedChanges; +#if GTM_IPHONE_SDK + hasUnsavedChanges = ABAddressBookHasUnsavedChanges(addressBook_); +#else // GTM_IPHONE_SDK + hasUnsavedChanges = ABHasUnsavedChanges(addressBook_); +#endif // GTM_IPHONE_SDK + return hasUnsavedChanges ? YES : NO; +} + +- (BOOL)addRecord:(GTMABRecord *)record { + // Note: we check for bad data here because of radar + // 6201258 Adding a NULL record using ABAddressBookAddRecord crashes + if (!record) return NO; +#if GTM_IPHONE_SDK + CFErrorRef cfError = NULL; + bool wasGood = ABAddressBookAddRecord(addressBook_, + [record recordRef], &cfError); + if (cfError) { + // COV_NF_START + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + // COV_NF_END + } +#else // GTM_IPHONE_SDK + bool wasGood = ABAddRecord(addressBook_, [record recordRef]); +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +- (BOOL)removeRecord:(GTMABRecord *)record { + // Note: we check for bad data here because of radar + // 6201276 Removing a NULL record using ABAddressBookRemoveRecord crashes + if (!record) return NO; +#if GTM_IPHONE_SDK + CFErrorRef cfError = NULL; + bool wasGood = ABAddressBookRemoveRecord(addressBook_, + [record recordRef], &cfError); + if (cfError) { + // COV_NF_START + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + // COV_NF_END + } +#else // GTM_IPHONE_SDK + GTMABRecordID recID = [record recordID]; + ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_, (CFStringRef)recID); + bool wasGood = NO; + if (ref) { + wasGood = ABRemoveRecord(addressBook_, [record recordRef]); + CFRelease(ref); + } +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +- (NSArray *)people { +#if GTM_IPHONE_SDK + NSArray *people + = GTMCFAutorelease(ABAddressBookCopyArrayOfAllPeople(addressBook_)); +#else // GTM_IPHONE_SDK + NSArray *people + = GTMCFAutorelease(ABCopyArrayOfAllPeople(addressBook_)); +#endif // GTM_IPHONE_SDK + NSMutableArray *result = [NSMutableArray arrayWithCapacity:[people count]]; + id person; + GTM_FOREACH_OBJECT(person, people) { + [result addObject:[GTMABPerson recordWithRecord:person]]; + } + return result; +} + +- (NSArray *)groups { +#if GTM_IPHONE_SDK + NSArray *groups + = GTMCFAutorelease(ABAddressBookCopyArrayOfAllGroups(addressBook_)); +#else // GTM_IPHONE_SDK + NSArray *groups + = GTMCFAutorelease(ABCopyArrayOfAllGroups(addressBook_)); +#endif // GTM_IPHONE_SDK + NSMutableArray *result = [NSMutableArray arrayWithCapacity:[groups count]]; + id group; + GTM_FOREACH_OBJECT(group, groups) { + [result addObject:[GTMABGroup recordWithRecord:group]]; + } + return result; +} + +- (ABAddressBookRef)addressBookRef { + return addressBook_; +} + +- (GTMABPerson *)personForId:(GTMABRecordID)uniqueId { + GTMABPerson *person = nil; +#if GTM_IPHONE_SDK + ABRecordRef ref = ABAddressBookGetPersonWithRecordID(addressBook_, uniqueId); +#else // GTM_IPHONE_SDK + ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_, + (CFStringRef)uniqueId); +#endif // GTM_IPHONE_SDK + if (ref) { + person = [GTMABPerson recordWithRecord:ref]; + } + return person; +} + +- (GTMABGroup *)groupForId:(GTMABRecordID)uniqueId { + GTMABGroup *group = nil; +#if GTM_IPHONE_SDK + ABRecordRef ref = ABAddressBookGetGroupWithRecordID(addressBook_, uniqueId); +#else // GTM_IPHONE_SDK + ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_, + (CFStringRef)uniqueId); +#endif // GTM_IPHONE_SDK + if (ref) { + group = [GTMABGroup recordWithRecord:ref]; + } + return group; +} + +// Performs a prefix search on the composite names of people in an address book +// and returns an array of persons that match the search criteria. +- (NSArray *)peopleWithCompositeNameWithPrefix:(NSString *)prefix { + // TODO(dmaclach): Change over to recordsMatchingSearchElement as an + // optimization? + NSArray *people = [self people]; + NSMutableArray *foundPeople = [NSMutableArray array]; + GTMABPerson *person; + GTM_FOREACH_OBJECT(person, people) { + NSString *compositeName = [person compositeName]; + NSRange range = [compositeName rangeOfString:prefix + options:(NSCaseInsensitiveSearch + | NSAnchoredSearch)]; + if (range.location != NSNotFound) { + [foundPeople addObject:person]; + } + } + return foundPeople; +} + +// Performs a prefix search on the composite names of groups in an address book +// and returns an array of groups that match the search criteria. +- (NSArray *)groupsWithCompositeNameWithPrefix:(NSString *)prefix { + NSArray *groups = [self groups]; + NSMutableArray *foundGroups = [NSMutableArray array]; + GTMABGroup *group; + GTM_FOREACH_OBJECT(group, groups) { + NSString *compositeName = [group compositeName]; + NSRange range = [compositeName rangeOfString:prefix + options:(NSCaseInsensitiveSearch + | NSAnchoredSearch)]; + if (range.location != NSNotFound) { + [foundGroups addObject:group]; + } + } + return foundGroups; +} + ++ (NSString *)localizedLabel:(NSString *)label { +#if GTM_IPHONE_SDK + return GTMCFAutorelease(ABAddressBookCopyLocalizedLabel((CFStringRef)label)); +#else // GTM_IPHONE_SDK + return GTMCFAutorelease(ABCopyLocalizedPropertyOrLabel((CFStringRef)label)); +#endif // GTM_IPHONE_SDK +} + +@end + +@implementation GTMABRecord ++ (id)recordWithRecord:(ABRecordRef)record { + return [[[self alloc] initWithRecord:record] autorelease]; +} + +- (id)initWithRecord:(ABRecordRef)record { + if ((self = [super init])) { + if ([self class] == [GTMABRecord class]) { + [self autorelease]; + [self doesNotRecognizeSelector:_cmd]; + } + if (!record) { + [self release]; + self = nil; + } else { + record_ = (ABRecordRef)CFRetain(record); + } + } + return self; +} + +- (NSUInteger)hash { + // This really isn't completely valid due to + // 6203836 ABRecords hash to their address + // but it's the best we can do without knowing what properties + // are in a record, and we don't have an API for that. + return CFHash(record_); +} + +- (BOOL)isEqual:(id)object { + // This really isn't completely valid due to + // 6203836 ABRecords hash to their address + // but it's the best we can do without knowing what properties + // are in a record, and we don't have an API for that. + return [object respondsToSelector:@selector(recordRef)] + && CFEqual(record_, [object recordRef]); +} + +- (void)dealloc { + if (record_) { + CFRelease(record_); + } + [super dealloc]; +} + +- (ABRecordRef)recordRef { + return record_; +} + +- (GTMABRecordID)recordID { +#if GTM_IPHONE_SDK + return ABRecordGetRecordID(record_); +#else // GTM_IPHONE_SDK + return GTMCFAutorelease(ABRecordCopyUniqueId(record_)); +#endif // GTM_IPHONE_SDK +} + +- (id)valueForProperty:(GTMABPropertyID)property { +#ifdef GTM_IPHONE_SDK + id value = GTMCFAutorelease(ABRecordCopyValue(record_, property)); +#else // GTM_IPHONE_SDK + id value = GTMCFAutorelease(ABRecordCopyValue(record_, (CFStringRef)property)); +#endif // GTM_IPHONE_SDK + if (value) { + if ([[self class] typeOfProperty:property] & kABMultiValueMask) { + value = [[[GTMABMultiValue alloc] + initWithMultiValue:(ABMultiValueRef)value] autorelease]; + } + } + return value; +} + +- (BOOL)setValue:(id)value forProperty:(GTMABPropertyID)property { + if (!value) return NO; + // We check the type here because of + // Radar 6201046 ABRecordSetValue returns true even if you pass in a bad type + // for a value + TypeClassNameMap fullTypeMap[] = { + { kGTMABStringPropertyType, [NSString class] }, + { kGTMABIntegerPropertyType, [NSNumber class] }, + { kGTMABRealPropertyType, [NSNumber class] }, + { kGTMABDateTimePropertyType, [NSDate class] }, + { kGTMABDictionaryPropertyType, [NSDictionary class] }, + { kGTMABMultiStringPropertyType, [GTMABMultiValue class] }, + { kGTMABMultiRealPropertyType, [GTMABMultiValue class] }, + { kGTMABMultiDateTimePropertyType, [GTMABMultiValue class] }, + { kGTMABMultiDictionaryPropertyType, [GTMABMultiValue class] } + }; + GTMABPropertyType type = [[self class] typeOfProperty:property]; + BOOL wasFound = NO; + for (size_t i = 0; i < sizeof(fullTypeMap) / sizeof(TypeClassNameMap); ++i) { + if (fullTypeMap[i].pType == type) { + wasFound = YES; + if (![[value class] isSubclassOfClass:fullTypeMap[i].class]) { + return NO; + } + } + } + if (!wasFound) { + return NO; + } + if (type & kABMultiValueMask) { + value = (id)[value multiValueRef]; + } +#if GTM_IPHONE_SDK + CFErrorRef cfError = nil; + bool wasGood = ABRecordSetValue(record_, property, + (CFTypeRef)value, &cfError); + if (cfError) { + // COV_NF_START + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + // COV_NF_END + } +#else // GTM_IPHONE_SDK + bool wasGood = ABRecordSetValue(record_, (CFStringRef)property, (CFTypeRef)value); +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +- (BOOL)removeValueForProperty:(GTMABPropertyID)property { +#if GTM_IPHONE_SDK + CFErrorRef cfError = nil; + // We check to see if the value is in the property because of: + // Radar 6201005 ABRecordRemoveValue returns true for value that aren't + // in the record + id value = [self valueForProperty:property]; + bool wasGood = value && ABRecordRemoveValue(record_, property, &cfError); + if (cfError) { + // COV_NF_START + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + // COV_NF_END + } +#else // GTM_IPHONE_SDK + id value = [self valueForProperty:property]; + bool wasGood = value && ABRecordRemoveValue(record_, (CFStringRef)property); +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +// COV_NF_START +// All of these methods are to be overridden by their subclasses + +- (NSString *)compositeName { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + ++ (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property { + [self doesNotRecognizeSelector:_cmd]; + return kGTMABInvalidPropertyType; +} + ++ (NSString *)localizedPropertyName:(GTMABPropertyID)property { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} +// COV_NF_END +@end + +@implementation GTMABPerson + ++ (GTMABPerson *)personWithFirstName:(NSString *)first + lastName:(NSString *)last { + GTMABPerson *person = [[[self alloc] init] autorelease]; + if (person) { + BOOL isGood = YES; + if (first) { + isGood = [person setValue:first + forProperty:kGTMABPersonFirstNameProperty]; + } + if (isGood && last) { + isGood = [person setValue:last forProperty:kGTMABPersonLastNameProperty]; + } + if (!isGood) { + // COV_NF_START + // Marked as NF because I don't know how to force an error + [person release]; + person = nil; + // COV_NF_END + } + } + return person; +} + +- (id)init { + ABRecordRef person = ABPersonCreate(); + self = [super initWithRecord:person]; + if (person) { + CFRelease(person); + } + return self; +} + +- (BOOL)setImageData:(NSData *)data { +#if GTM_IPHONE_SDK + CFErrorRef cfError = NULL; + bool wasGood = NO; + if (!data) { + wasGood = ABPersonRemoveImageData([self recordRef], &cfError); + } else { + // We verify that the data is good because of: + // Radar 6202868 ABPersonSetImageData should validate image data + UIImage *image = [UIImage imageWithData:data]; + wasGood = image && ABPersonSetImageData([self recordRef], + (CFDataRef)data, &cfError); + } + if (cfError) { + // COV_NF_START + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + // COV_NF_END + } +#else // GTM_IPHONE_SDK + bool wasGood = YES; + if (data) { + wasGood = [[[NSImage alloc] initWithData:data] autorelease] != nil; + } + wasGood = wasGood && ABPersonSetImageData([self recordRef], + (CFDataRef)data); +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +- (GTMABImage *)image { + NSData *data = [self imageData]; +#if GTM_IPHONE_SDK + return [UIImage imageWithData:data]; +#else // GTM_IPHONE_SDK + return [[[NSImage alloc] initWithData:data] autorelease]; +#endif // GTM_IPHONE_SDK +} + +- (BOOL)setImage:(GTMABImage *)image { +#if GTM_IPHONE_SDK + NSData *data = UIImagePNGRepresentation(image); +#else // GTM_IPHONE_SDK + NSData *data = [image TIFFRepresentation]; +#endif // GTM_IPHONE_SDK + return [self setImageData:data]; +} + +- (NSData *)imageData { + return GTMCFAutorelease(ABPersonCopyImageData([self recordRef])); +} + +- (NSString *)compositeName { +#if GTM_IPHONE_SDK + return GTMCFAutorelease(ABRecordCopyCompositeName([self recordRef])); +#else // GTM_IPHONE_SDK + NSNumber *nsFlags = [self valueForProperty:kABPersonFlags]; + NSInteger flags = [nsFlags longValue]; + NSString *compositeName = nil; + if (flags & kABShowAsCompany) { + compositeName = [self valueForProperty:kABOrganizationProperty]; + } else { + NSString *firstName = [self valueForProperty:kGTMABPersonFirstNameProperty]; + NSString *lastName = [self valueForProperty:kGTMABPersonLastNameProperty]; + + if (firstName && lastName) { + GTMABPersonCompositeNameFormat format; + if (flags & kABFirstNameFirst) { + format = kABPersonCompositeNameFormatFirstNameFirst; + } else if (flags & kABLastNameFirst) { + format = kABPersonCompositeNameFormatLastNameFirst; + } else { + format = [[self class] compositeNameFormat]; + } + if (format == kABPersonCompositeNameFormatLastNameFirst) { + NSString *tempStr = lastName; + lastName = firstName; + firstName = tempStr; + } + compositeName = [NSString stringWithFormat:@"%@ %@", firstName, lastName]; + } else if (firstName) { + compositeName = firstName; + } else if (lastName) { + compositeName = lastName; + } else { + compositeName = @""; + } + } + + return compositeName; +#endif // GTM_IPHONE_SDK +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %@ %@ %d", + [self class], + [self valueForProperty:kGTMABPersonFirstNameProperty], + [self valueForProperty:kGTMABPersonLastNameProperty], + [self recordID]]; +} + ++ (NSString *)localizedPropertyName:(GTMABPropertyID)property { +#if GTM_IPHONE_SDK + return GTMCFAutorelease(ABPersonCopyLocalizedPropertyName(property)); +#else // GTM_IPHONE_SDK + return ABLocalizedPropertyOrLabel(property); +#endif // GTM_IPHONE_SDK +} + ++ (GTMABPersonCompositeNameFormat)compositeNameFormat { +#if GTM_IPHONE_SDK + return ABPersonGetCompositeNameFormat(); +#else // GTM_IPHONE_SDK + NSInteger nameOrdering + = [[ABAddressBook sharedAddressBook] defaultNameOrdering]; + return nameOrdering == kABFirstNameFirst ? + kABPersonCompositeNameFormatFirstNameFirst : + kABPersonCompositeNameFormatLastNameFirst; +#endif // GTM_IPHONE_SDK +} + ++ (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property { +#if GTM_IPHONE_SDK + return ABPersonGetTypeOfProperty(property); +#else // GTM_IPHONE_SDK + return ABTypeOfProperty([[GTMABAddressBook addressBook] addressBookRef], + (CFStringRef)kABPersonRecordType, + (CFStringRef)property); +#endif // GTM_IPHONE_SDK +} +@end + +@implementation GTMABGroup + ++ (GTMABGroup *)groupNamed:(NSString *)name { + GTMABGroup *group = [[[self alloc] init] autorelease]; + if (group) { + if (![group setValue:name forProperty:kABGroupNameProperty]) { + // COV_NF_START + // Can't get setValue to fail for me + [group release]; + group = nil; + // COV_NF_END + } + } + return group; +} + +- (id)init { + ABRecordRef group = ABGroupCreate(); + self = [super initWithRecord:group]; + if (group) { + CFRelease(group); + } + return self; +} + +- (NSArray *)members { + NSArray *people + = GTMCFAutorelease(ABGroupCopyArrayOfAllMembers([self recordRef])); + NSMutableArray *gtmPeople = [NSMutableArray arrayWithCapacity:[people count]]; + id person; + GTM_FOREACH_OBJECT(person, people) { + [gtmPeople addObject:[GTMABPerson recordWithRecord:(ABRecordRef)person]]; + } + return gtmPeople; +} + +- (BOOL)addMember:(GTMABPerson *)person { +#if GTM_IPHONE_SDK + CFErrorRef cfError = nil; + // We check for person because of + // Radar 6202860 Passing nil person into ABGroupAddMember crashes + bool wasGood = person && ABGroupAddMember([self recordRef], + [person recordRef], &cfError); + if (cfError) { + // COV_NF_START + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + // COV_NF_END + } +#else // GTM_IPHONE_SDK + bool wasGood = person && ABGroupAddMember([self recordRef], + [person recordRef]); +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +- (BOOL)removeMember:(GTMABPerson *)person { +#if GTM_IPHONE_SDK + CFErrorRef cfError = nil; + // We check for person because of + // Radar 6202860 Passing nil person into ABGroupAddMember crashes + // (I know this is remove, but it crashes there too) + bool wasGood = person && ABGroupRemoveMember([self recordRef], + [person recordRef], &cfError); + if (cfError) { + // COV_NF_START + _GTMDevLog(@"Error in [%@ %@]: %@", + [self class], NSStringFromSelector(_cmd), cfError); + CFRelease(cfError); + // COV_NF_END + } +#else // GTM_IPHONE_SDK + bool wasGood = person != nil; + if (wasGood) { + NSArray *array = GTMCFAutorelease(ABPersonCopyParentGroups([person recordRef])); + if ([array containsObject:[self recordRef]]) { + wasGood = ABGroupRemoveMember([self recordRef], + [person recordRef]); + } else { + wasGood = NO; + } + } +#endif // GTM_IPHONE_SDK + return wasGood ? YES : NO; +} + +- (NSString *)compositeName { +#if GTM_IPHONE_SDK + return GTMCFAutorelease(ABRecordCopyCompositeName([self recordRef])); +#else // GTM_IPHONE_SDK + return [self valueForProperty:kGTMABGroupNameProperty]; +#endif // GTM_IPHONE_SDK +} + ++ (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property { + GTMABPropertyType type = kGTMABInvalidPropertyType; + if (property == kABGroupNameProperty) { + type = kGTMABStringPropertyType; + } + return type; +} + ++ (NSString *)localizedPropertyName:(GTMABPropertyID)property { + NSString *name = kGTMABUnknownPropertyName; + if (property == kABGroupNameProperty) { + name = NSLocalizedStringFromTable(@"Name", + @"GTMABAddressBook", + @"name property"); + } + return name; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %@ %d", + [self class], + [self valueForProperty:kABGroupNameProperty], + [self recordID]]; +} +@end + +@implementation GTMABMultiValue +- (id)init { + // Call super init and release so we don't leak + [[super init] autorelease]; + [self doesNotRecognizeSelector:_cmd]; + return nil; // COV_NF_LINE +} + +- (id)initWithMultiValue:(ABMultiValueRef)multiValue { + if ((self = [super init])) { + if (!multiValue) { + [self release]; + self = nil; + } else { + multiValue_ = CFRetain(multiValue); + } + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + return [[GTMABMultiValue alloc] initWithMultiValue:multiValue_]; +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + return [[GTMABMutableMultiValue alloc] initWithMultiValue:multiValue_]; +} + +- (NSUInteger)hash { + // I'm implementing hash instead of using CFHash(multiValue_) because + // 6203854 ABMultiValues hash to their address + NSUInteger count = [self count]; + NSUInteger hash = 0; + for (NSUInteger i = 0; i < count; ++i) { + NSString *label = [self labelAtIndex:i]; + id value = [self valueAtIndex:i]; + hash += [label hash]; + hash += [value hash]; + } + return hash; +} + +- (BOOL)isEqual:(id)object { + // I'm implementing isEqual instea of using CFEquals(multiValue,...) because + // 6203854 ABMultiValues hash to their address + // and it appears CFEquals just calls through to hash to compare them. + BOOL isEqual = NO; + if ([object respondsToSelector:@selector(multiValueRef)]) { + isEqual = multiValue_ == [object multiValueRef]; + if (!isEqual) { + NSUInteger count = [self count]; + NSUInteger objCount = [object count]; + isEqual = count == objCount; + for (NSUInteger i = 0; isEqual && i < count; ++i) { + NSString *label = [self labelAtIndex:i]; + NSString *objLabel = [object labelAtIndex:i]; + isEqual = [label isEqual:objLabel]; + if (isEqual) { + id value = [self valueAtIndex:i]; + id objValue = [(GTMABMultiValue*)object valueAtIndex:i]; + isEqual = [value isEqual:objValue]; + } + } + } + } + return isEqual; +} + +- (void)dealloc { + if (multiValue_) { + CFRelease(multiValue_); + } + [super dealloc]; +} + +- (ABMultiValueRef)multiValueRef { + return multiValue_; +} + +- (NSUInteger)count { +#if GTM_IPHONE_SDK + return ABMultiValueGetCount(multiValue_); +#else // GTM_IPHONE_SDK + return ABMultiValueCount(multiValue_); +#endif // GTM_IPHONE_SDK +} + +- (id)valueAtIndex:(NSUInteger)idx { + id value = nil; + if (idx < [self count]) { + value = GTMCFAutorelease(ABMultiValueCopyValueAtIndex(multiValue_, idx)); + ABPropertyType type = [self propertyType]; + if (type == kGTMABIntegerPropertyType + || type == kGTMABRealPropertyType + || type == kGTMABDictionaryPropertyType) { + // This is because of + // 6208390 Integer and real values don't work in ABMultiValueRefs + // Apparently they forget to add a ref count on int, real and + // dictionary values in ABMultiValueCopyValueAtIndex, although they do + // remember them for all other types. + // Once they fix this, this will lead to a leak, but I figure the leak + // is better than the crash. Our unittests will test to make sure that + // this is the case, and once we find a system that has this fixed, we + // can conditionalize this code. Look for testRadar6208390 in + // GTMABAddressBookTest.m + // Also, search for 6208390 below and fix the fast enumerator to actually + // be somewhat performant when this is fixed. + [value retain]; + } + } + return value; +} + +- (NSString *)labelAtIndex:(NSUInteger)idx { + NSString *label = nil; + if (idx < [self count]) { + label = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(multiValue_, idx)); + } + return label; +} + +- (GTMABMultiValueIdentifier)identifierAtIndex:(NSUInteger)idx { + GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier; + if (idx < [self count]) { +#if GTM_IPHONE_SDK + identifier = ABMultiValueGetIdentifierAtIndex(multiValue_, idx); +#else // GTM_IPHONE_SDK + identifier = GTMCFAutorelease(ABMultiValueCopyIdentifierAtIndex(multiValue_, + idx)); +#endif // GTM_IPHONE_SDK + } + return identifier; +} + +- (NSUInteger)indexForIdentifier:(GTMABMultiValueIdentifier)identifier { +#if GTM_IPHONE_SDK + NSUInteger idx = ABMultiValueGetIndexForIdentifier(multiValue_, identifier); +#else // GTM_IPHONE_SDK + NSUInteger idx = ABMultiValueIndexForIdentifier(multiValue_, + (CFStringRef)identifier); +#endif // GTM_IPHONE_SDK + return idx == (NSUInteger)kCFNotFound ? (NSUInteger)NSNotFound : idx; +} + +- (GTMABPropertyType)propertyType { +#if GTM_IPHONE_SDK + return ABMultiValueGetPropertyType(multiValue_); +#else // GTM_IPHONE_SDK + return ABMultiValuePropertyType(multiValue_); +#endif // GTM_IPHONE_SDK +} + +- (id)valueForIdentifier:(GTMABMultiValueIdentifier)identifier { + return [self valueAtIndex:[self indexForIdentifier:identifier]]; +} + +- (NSString *)labelForIdentifier:(GTMABMultiValueIdentifier)identifier { + return [self labelAtIndex:[self indexForIdentifier:identifier]]; +} + +- (unsigned long*)mutations { + // We just need some constant non-zero value here so fast enumeration works. + // Dereferencing self should give us the isa which will stay constant + // over the enumeration. + return (unsigned long*)self; +} + +- (NSEnumerator *)valueEnumerator { + return [GTMABMultiValueEnumerator valueEnumeratorFor:self]; +} + +- (NSEnumerator *)labelEnumerator { + return [GTMABMultiValueEnumerator labelEnumeratorFor:self]; +} + +@end + +@implementation GTMABMutableMultiValue ++ (id)valueWithPropertyType:(GTMABPropertyType)type { + return [[[self alloc] initWithPropertyType:type] autorelease]; +} + +- (id)initWithPropertyType:(GTMABPropertyType)type { + ABMutableMultiValueRef ref = nil; + if (type != kGTMABInvalidPropertyType) { +#if GTM_IPHONE_SDK + ref = ABMultiValueCreateMutable(type); +#else // GTM_IPHONE_SDK + ref = ABMultiValueCreateMutable(); +#endif // GTM_IPHONE_SDK + } + self = [super initWithMultiValue:ref]; + if (ref) { + CFRelease(ref); + } + return self; +} + +- (id)initWithMultiValue:(ABMultiValueRef)multiValue { + ABMutableMultiValueRef ref = nil; + if (multiValue) { + ref = ABMultiValueCreateMutableCopy(multiValue); + } + self = [super initWithMultiValue:ref]; + if (ref) { + CFRelease(ref); + } + return self; +} + +- (id)initWithMutableMultiValue:(ABMutableMultiValueRef)multiValue { + return [super initWithMultiValue:multiValue]; +} + +- (BOOL)checkValueType:(id)value { + BOOL isGood = NO; + if (value) { + TypeClassNameMap singleValueTypeMap[] = { + { kGTMABStringPropertyType, [NSString class] }, + { kGTMABIntegerPropertyType, [NSNumber class] }, + { kGTMABRealPropertyType, [NSNumber class] }, + { kGTMABDateTimePropertyType, [NSDate class] }, + { kGTMABDictionaryPropertyType, [NSDictionary class] }, + }; + GTMABPropertyType type = [self propertyType] & ~kABMultiValueMask; +#if GTM_MACOS_SDK + // Since on the desktop mutables don't have a type UNTIL they have + // something in them, return YES if it's empty. + if ((type == 0) && ([self count] == 0)) return YES; +#endif // GTM_MACOS_SDK + for (size_t i = 0; + i < sizeof(singleValueTypeMap) / sizeof(TypeClassNameMap); ++i) { + if (singleValueTypeMap[i].pType == type) { + if ([[value class] isSubclassOfClass:singleValueTypeMap[i].class]) { + isGood = YES; + break; + } + } + } + } + return isGood; +} + +- (GTMABMultiValueIdentifier)addValue:(id)value withLabel:(CFStringRef)label { + GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier; + // We check label and value here because of + // radar 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash + bool wasGood = label && [self checkValueType:value]; + if (wasGood) { +#if GTM_IPHONE_SDK + wasGood = ABMultiValueAddValueAndLabel(multiValue_, + value, + label, + &identifier); +#else // GTM_IPHONE_SDK + wasGood = ABMultiValueAdd((ABMutableMultiValueRef)multiValue_, + value, + label, + (CFStringRef *)&identifier); +#endif // GTM_IPHONE_SDK + } + if (!wasGood) { + identifier = kGTMABMultiValueInvalidIdentifier; + } else { + mutations_++; + } + return identifier; +} + +- (GTMABMultiValueIdentifier)insertValue:(id)value + withLabel:(CFStringRef)label + atIndex:(NSUInteger)idx { + GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier; + // We perform a check here to ensure that we don't get bitten by + // Radar 6202807 ABMultiValueInsertValueAndLabelAtIndex allows you to insert + // values past end + NSUInteger count = [self count]; + // We check label and value here because of + // radar 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash + bool wasGood = idx <= count && label && [self checkValueType:value]; + if (wasGood) { +#if GTM_IPHONE_SDK + wasGood = ABMultiValueInsertValueAndLabelAtIndex(multiValue_, + value, + label, + idx, + &identifier); +#else // GTM_IPHONE_SDK + wasGood = ABMultiValueInsert((ABMutableMultiValueRef)multiValue_, + value, + label, + idx, + (CFStringRef *)&identifier); +#endif // GTM_IPHONE_SDK + } + if (!wasGood) { + identifier = kGTMABMultiValueInvalidIdentifier; + } else { + mutations_++; + } + return identifier; +} + +- (BOOL)removeValueAndLabelAtIndex:(NSUInteger)idx { + BOOL isGood = NO; + NSUInteger count = [self count]; + if (idx < count) { +#if GTM_IPHONE_SDK + bool wasGood = ABMultiValueRemoveValueAndLabelAtIndex(multiValue_, + idx); +#else // GTM_IPHONE_SDK + bool wasGood = ABMultiValueRemove((ABMutableMultiValueRef)multiValue_, + idx); +#endif // GTM_IPHONE_SDK + if (wasGood) { + mutations_++; + isGood = YES; + } + } + return isGood; +} + +- (BOOL)replaceValueAtIndex:(NSUInteger)idx withValue:(id)value { + BOOL isGood = NO; + NSUInteger count = [self count]; + if (idx < count && [self checkValueType:value]) { +#if GTM_IPHONE_SDK + bool goodReplace = ABMultiValueReplaceValueAtIndex(multiValue_, + value, idx); +#else // GTM_IPHONE_SDK + bool goodReplace + = ABMultiValueReplaceValue((ABMutableMultiValueRef)multiValue_, + (CFTypeRef)value, idx); +#endif // GTM_IPHONE_SDK + if (goodReplace) { + mutations_++; + isGood = YES; + } + } + return isGood; +} + +- (BOOL)replaceLabelAtIndex:(NSUInteger)idx withLabel:(CFStringRef)label { + BOOL isGood = NO; + NSUInteger count = [self count]; + if (idx < count) { +#if GTM_IPHONE_SDK + bool goodReplace = ABMultiValueReplaceLabelAtIndex(multiValue_, + label, idx); +#else // GTM_IPHONE_SDK + bool goodReplace + = ABMultiValueReplaceLabel((ABMutableMultiValueRef)multiValue_, + (CFTypeRef)label, idx); +#endif // GTM_IPHONE_SDK + if (goodReplace) { + mutations_++; + isGood = YES; + } + } + return isGood; +} + +- (unsigned long*)mutations { + return &mutations_; +} +@end + + +@implementation GTMABMultiValueEnumerator + ++ (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree { + return [[[self alloc] initWithEnumeree:enumeree useLabels:NO] autorelease]; +} + ++ (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree { + return [[[self alloc] initWithEnumeree:enumeree useLabels:YES] autorelease]; +} + +- (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels { + if ((self = [super init])) { + if (enumeree) { + enumeree_ = [enumeree retain]; + useLabels_ = useLabels; + } else { + // COV_NF_START + // Since this is a private class where the enumeree creates us + // there is no way we should ever get here. + [self release]; + self = nil; + // COV_NF_END + } + } + return self; +} + +- (void)dealloc { + [enumeree_ release]; + [super dealloc]; +} + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(id *)stackbuf + count:(NSUInteger)len { + NSUInteger i; + if (!ref_) { + count_ = [enumeree_ count]; + ref_ = [enumeree_ multiValueRef]; + } + + for (i = 0; state->state < count_ && i < len; ++i, ++state->state) { + if (useLabels_) { + stackbuf[i] = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_, + state->state)); + } else { + // TODO(dmaclach) Check this on Mac Desktop and use fast path if we can + // Yes this is slow, but necessary in light of radar 6208390 + // Once this is fixed we can go to something similar to the label + // case which should speed stuff up again. Hopefully anybody who wants + // real performance is willing to move down to the C API anyways. + stackbuf[i] = [enumeree_ valueAtIndex:state->state]; + } + } + + state->itemsPtr = stackbuf; + state->mutationsPtr = [enumeree_ mutations]; + return i; +} +#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + +- (id)nextObject { + id value = nil; + if (!ref_) { + count_ = [enumeree_ count]; + mutations_ = *[enumeree_ mutations]; + ref_ = [enumeree_ multiValueRef]; + + } + if (mutations_ != *[enumeree_ mutations]) { + NSString *reason = [NSString stringWithFormat:@"*** Collection <%@> was " + "mutated while being enumerated", enumeree_]; + [[NSException exceptionWithName:NSGenericException + reason:reason + userInfo:nil] raise]; + } + if (index_ < count_) { + if (useLabels_) { + value = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_, + index_)); + } else { + // TODO(dmaclach) Check this on Mac Desktop and use fast path if we can + // Yes this is slow, but necessary in light of radar 6208390 + // Once this is fixed we can go to something similar to the label + // case which should speed stuff up again. Hopefully anybody who wants + // real performance is willing to move down to the C API anyways. + value = [enumeree_ valueAtIndex:index_]; + } + index_ += 1; + } + return value; +} +@end + diff --git a/AddressBook/GTMABAddressBook.strings b/AddressBook/GTMABAddressBook.strings new file mode 100644 index 0000000..b5e010e Binary files /dev/null and b/AddressBook/GTMABAddressBook.strings differ diff --git a/AddressBook/GTMABAddressBookTest.m b/AddressBook/GTMABAddressBookTest.m new file mode 100644 index 0000000..73b0eca --- /dev/null +++ b/AddressBook/GTMABAddressBookTest.m @@ -0,0 +1,689 @@ +// +// GTMAddressBookTest.m +// +// Copyright 2008 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. +// + +#import "GTMSenTestCase.h" +#import "GTMABAddressBook.h" + +static NSString *const kGTMABTestFirstName = @"GTMABAddressBookTestFirstName"; +static NSString *const kGTMABTestLastName = @"GTMABAddressBookTestLastName"; +static NSString *const kGTMABTestGroupName = @"GTMABAddressBookTestGroupName"; + +@interface GTMABAddressBookTest : GTMTestCase { + @private + GTMABAddressBook *book_; +} +@end + + +@implementation GTMABAddressBookTest +- (void)setUp { + // Create a book forcing it out of it's autorelease pool. + // I force it out of the release pool, so that we will see any errors + // for it immediately at teardown, and it will be clear which release + // caused us problems. + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + book_ = [[GTMABAddressBook addressBook] retain]; + [pool release]; + STAssertNotNil(book_, nil); + NSArray *people + = [book_ peopleWithCompositeNameWithPrefix:kGTMABTestFirstName]; + GTMABPerson *person; + GTM_FOREACH_OBJECT(person, people) { + [book_ removeRecord:person]; + } + NSArray *groups + = [book_ groupsWithCompositeNameWithPrefix:kGTMABTestGroupName]; + GTMABGroup *group; + GTM_FOREACH_OBJECT(group, groups) { + [book_ removeRecord:group]; + } + [book_ save]; +} + +- (void)tearDown { + [book_ release]; +} + +- (void)testGenericAddressBook { + STAssertEqualObjects([GTMABAddressBook localizedLabel:(NSString *)kABHomeLabel], + @"home", + nil); + STAssertThrows([GTMABRecord recordWithRecord:nil], nil); +} + +- (void)testAddingAndRemovingPerson { + // Create a person + GTMABPerson *person = [GTMABPerson personWithFirstName:kGTMABTestFirstName + lastName:kGTMABTestLastName]; + STAssertNotNil(person, nil); + + // Add person + NSArray *people = [book_ people]; + STAssertFalse([people containsObject:person], nil); + STAssertTrue([book_ addRecord:person], nil); +#if GTM_IPHONE_SDK + // Normally this next line would be STAssertTrue, however due to + // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work + // We will check to make sure it stays broken ;-) + STAssertFalse([book_ hasUnsavedChanges], nil); +#else // GTM_IPHONE_SDK + STAssertTrue([book_ hasUnsavedChanges], nil); +#endif // GTM_IPHONE_SDK + + people = [book_ people]; + STAssertNotNil(people, nil); +#if GTM_IPHONE_SDK + // Normally this next line would be STAssertTrue, however due to + // Radar 6200703: ABAddressBookAddRecord doesn't add an item to the people + // array until it's saved + // We will check to make sure it stays broken ;-) + STAssertFalse([people containsObject:person], nil); +#else // GTM_IPHONE_SDK + STAssertTrue([people containsObject:person], nil); +#endif // GTM_IPHONE_SDK + + // Save book_ + STAssertTrue([book_ save], nil); + people = [book_ people]; + STAssertNotNil(people, nil); + STAssertTrue([people containsObject:person], nil); + people = [book_ peopleWithCompositeNameWithPrefix:kGTMABTestFirstName]; + STAssertEqualObjects([people objectAtIndex:0], person, nil); + + GTMABRecordID recordID = [person recordID]; + STAssertNotEquals(recordID, kGTMABRecordInvalidID, nil); + + GTMABRecord *record = [book_ personForId:recordID]; + STAssertEqualObjects(record, person, nil); + + // Remove person + STAssertTrue([book_ removeRecord:person], nil); + people = [book_ peopleWithCompositeNameWithPrefix:kGTMABTestFirstName]; + STAssertEquals([people count], (NSUInteger)0, nil); + +#if GTM_IPHONE_SDK + // Normally this next line would be STAssertTrue, however due to + // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work + // We will check to make sure it stays broken ;-) + STAssertFalse([book_ hasUnsavedChanges], nil); +#else // GTM_IPHONE_SDK + STAssertTrue([book_ hasUnsavedChanges], nil); +#endif // GTM_IPHONE_SDK + people = [book_ people]; + STAssertFalse([people containsObject:person], nil); + + // Save Book + STAssertTrue([book_ save], nil); + people = [book_ people]; + STAssertFalse([book_ hasUnsavedChanges], nil); + STAssertFalse([people containsObject:person], nil); + record = [book_ personForId:recordID]; + STAssertNil(record, nil); + + // Bogus data + STAssertFalse([book_ addRecord:nil], nil); + STAssertFalse([book_ removeRecord:nil], nil); + + STAssertNotNULL([book_ addressBookRef], nil); + +} + +- (void)testAddingAndRemovingGroup { + // Create a group + GTMABGroup *group = [GTMABGroup groupNamed:kGTMABTestGroupName]; + STAssertNotNil(group, nil); + + // Add group + NSArray *groups = [book_ groups]; + STAssertFalse([groups containsObject:group], nil); + STAssertTrue([book_ addRecord:group], nil); +#if GTM_IPHONE_SDK + // Normally this next line would be STAssertTrue, however due to + // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work + // We will check to make sure it stays broken ;-) + STAssertFalse([book_ hasUnsavedChanges], nil); +#else // GTM_IPHONE_SDK + STAssertTrue([book_ hasUnsavedChanges], nil); +#endif // GTM_IPHONE_SDK + + groups = [book_ groups]; + STAssertNotNil(groups, nil); +#if GTM_IPHONE_SDK + // Normally this next line would be STAssertTrue, however due to + // Radar 6200703: ABAddressBookAddRecord doesn't add an item to the groups + // array until it's saved + // We will check to make sure it stays broken ;-) + STAssertFalse([groups containsObject:group], nil); +#else // GTM_IPHONE_SDK + STAssertTrue([groups containsObject:group], nil); +#endif // GTM_IPHONE_SDK + + // Save book_ + STAssertTrue([book_ save], nil); + groups = [book_ groups]; + STAssertNotNil(groups, nil); + STAssertTrue([groups containsObject:group], nil); + groups = [book_ groupsWithCompositeNameWithPrefix:kGTMABTestGroupName]; + STAssertEqualObjects([groups objectAtIndex:0], group, nil); + + GTMABRecordID recordID = [group recordID]; + STAssertNotEquals(recordID, kGTMABRecordInvalidID, nil); + + GTMABRecord *record = [book_ groupForId:recordID]; + STAssertEqualObjects(record, group, nil); + + // Remove group + STAssertTrue([book_ removeRecord:group], nil); + +#if GTM_IPHONE_SDK + // Normally this next line would be STAssertTrue, however due to + // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work + // We will check to make sure it stays broken ;-) + STAssertFalse([book_ hasUnsavedChanges], nil); +#else // GTM_IPHONE_SDK + STAssertTrue([book_ hasUnsavedChanges], nil); +#endif // GTM_IPHONE_SDK + groups = [book_ groups]; + STAssertFalse([groups containsObject:group], nil); + + // Save Book + STAssertTrue([book_ save], nil); + groups = [book_ groups]; + STAssertFalse([book_ hasUnsavedChanges], nil); + STAssertFalse([groups containsObject:group], nil); + groups = [book_ groupsWithCompositeNameWithPrefix:kGTMABTestGroupName]; + STAssertEquals([groups count], (NSUInteger)0, nil); + record = [book_ groupForId:recordID]; + STAssertNil(record, nil); +} + +- (void)testPerson { + GTMABPerson *person = [[[GTMABPerson alloc] initWithRecord:nil] autorelease]; + STAssertNil(person, nil); + person = [GTMABPerson personWithFirstName:kGTMABTestFirstName + lastName:nil]; + STAssertNotNil(person, nil); + STAssertEqualObjects([person compositeName], kGTMABTestFirstName, nil); + NSString *firstName = [person valueForProperty:kGTMABPersonFirstNameProperty]; + STAssertEqualObjects(firstName, kGTMABTestFirstName, nil); + NSString *lastName = [person valueForProperty:kGTMABPersonLastNameProperty]; + STAssertNil(lastName, nil); + STAssertTrue([person removeValueForProperty:kGTMABPersonFirstNameProperty], nil); + STAssertFalse([person removeValueForProperty:kGTMABPersonFirstNameProperty], nil); + STAssertFalse([person removeValueForProperty:kGTMABPersonLastNameProperty], nil); + STAssertFalse([person setValue:nil forProperty:kGTMABPersonFirstNameProperty], nil); + STAssertFalse([person setValue:[NSNumber numberWithInt:1] + forProperty:kGTMABPersonFirstNameProperty], nil); + STAssertFalse([person setValue:@"Bart" + forProperty:kGTMABPersonBirthdayProperty], nil); + + GTMABPropertyType property + = [GTMABPerson typeOfProperty:kGTMABPersonLastNameProperty]; + STAssertEquals(property, (GTMABPropertyType)kGTMABStringPropertyType, nil); + + NSString *string + = [GTMABPerson localizedPropertyName:kGTMABPersonLastNameProperty]; + STAssertEqualObjects(string, @"Last", nil); + + string = [GTMABPerson localizedPropertyName:kGTMABRecordInvalidID]; +#ifdef GTM_IPHONE_SDK + STAssertEqualObjects(string, kGTMABUnknownPropertyName, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects(string, kGTMABRecordInvalidID, nil); +#endif // GTM_IPHONE_SDK + string = [person description]; + STAssertNotNil(string, nil); + + GTMABPersonCompositeNameFormat format = [GTMABPerson compositeNameFormat]; + STAssertTrue(format == kABPersonCompositeNameFormatFirstNameFirst || + format == kABPersonCompositeNameFormatLastNameFirst, nil); + + NSData *data = [person imageData]; + STAssertNil(data, nil); + STAssertTrue([person setImageData:nil], nil); + data = [person imageData]; + STAssertNil(data, nil); + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *phonePath = [bundle pathForResource:@"phone" ofType:@"png"]; + STAssertNotNil(phonePath, nil); + GTMABImage *image + = [[[GTMABImage alloc] initWithContentsOfFile:phonePath] autorelease]; + STAssertNotNil(image, nil); +#ifdef GTM_IPHONE_SDK + data = UIImagePNGRepresentation(image); +#else // GTM_IPHONE_SDK + data = [image TIFFRepresentation]; +#endif // GTM_IPHONE_SDK + STAssertTrue([person setImageData:data], nil); + NSData *data2 = [person imageData]; + STAssertEqualObjects(data, data2, nil); + STAssertTrue([person setImageData:nil], nil); + data = [person imageData]; + STAssertNil(data, nil); + + STAssertTrue([person setImage:image], nil); + GTMABImage *image2 = [person image]; + STAssertNotNil(image2, nil); +#ifdef GTM_IPHONE_SDK + STAssertEqualObjects(UIImagePNGRepresentation(image), + UIImagePNGRepresentation(image2), nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects([image TIFFRepresentation], + [image2 TIFFRepresentation], nil); +#endif // GTM_IPHONE_SDK + + person = [GTMABPerson personWithFirstName:kGTMABTestFirstName + lastName:kGTMABTestLastName]; + + data = [NSData dataWithBytes:"a" length:1]; + STAssertFalse([person setImageData:data], nil); + + GTMABMutableMultiValue *value + = [GTMABMutableMultiValue valueWithPropertyType:kGTMABStringPropertyType]; + STAssertNotNil(value, nil); + STAssertNotEquals([value addValue:@"222-222-2222" + withLabel:(CFStringRef)kABHomeLabel], + kGTMABMultiValueInvalidIdentifier, nil); + STAssertNotEquals([value addValue:@"333-333-3333" + withLabel:(CFStringRef)kABWorkLabel], + kGTMABMultiValueInvalidIdentifier, nil); + STAssertTrue([person setValue:value + forProperty:kGTMABPersonPhoneProperty], nil); + id value2 = [person valueForProperty:kGTMABPersonPhoneProperty]; + STAssertNotNil(value2, nil); + STAssertEqualObjects(value, value2, nil); + STAssertEquals([value hash], [value2 hash], nil); + STAssertNotEquals([person hash], (NSUInteger)0, nil); +} + +- (void)testGroup { + GTMABGroup *group = [[[GTMABGroup alloc] initWithRecord:nil] autorelease]; + STAssertNil(group, nil); + group = [GTMABGroup groupNamed:kGTMABTestGroupName]; + STAssertNotNil(group, nil); + STAssertEqualObjects([group compositeName], kGTMABTestGroupName, nil); + NSString *name = [group valueForProperty:kABGroupNameProperty]; + STAssertEqualObjects(name, kGTMABTestGroupName, nil); + NSString *lastName = [group valueForProperty:kGTMABPersonLastNameProperty]; + STAssertNil(lastName, nil); + STAssertTrue([group removeValueForProperty:kABGroupNameProperty], nil); + STAssertFalse([group removeValueForProperty:kABGroupNameProperty], nil); + STAssertFalse([group removeValueForProperty:kGTMABPersonLastNameProperty], nil); + STAssertFalse([group setValue:nil forProperty:kABGroupNameProperty], nil); + STAssertFalse([group setValue:[NSNumber numberWithInt:1] + forProperty:kABGroupNameProperty], nil); + STAssertFalse([group setValue:@"Bart" + forProperty:kGTMABPersonBirthdayProperty], nil); + + ABPropertyType property = [GTMABGroup typeOfProperty:kABGroupNameProperty]; + STAssertEquals(property, (ABPropertyType)kGTMABStringPropertyType, nil); + + property = [GTMABGroup typeOfProperty:kGTMABPersonLastNameProperty]; + STAssertEquals(property, (ABPropertyType)kGTMABInvalidPropertyType, nil); + + NSString *string = [GTMABGroup localizedPropertyName:kABGroupNameProperty]; + STAssertEqualObjects(string, @"Name", nil); + + string = [GTMABGroup localizedPropertyName:kGTMABPersonLastNameProperty]; + STAssertEqualObjects(string, kGTMABUnknownPropertyName, nil); + + string = [GTMABGroup localizedPropertyName:kGTMABRecordInvalidID]; + STAssertEqualObjects(string, kGTMABUnknownPropertyName, nil); + + string = [group description]; + STAssertNotNil(string, nil); + + // Adding and removing members + group = [GTMABGroup groupNamed:kGTMABTestGroupName]; + NSArray *members = [group members]; + STAssertEquals([members count], (NSUInteger)0, @"Members: %@", members); + + STAssertFalse([group addMember:nil], nil); + + members = [group members]; + STAssertEquals([members count], (NSUInteger)0, @"Members: %@", members); + + GTMABPerson *person = [GTMABPerson personWithFirstName:kGTMABTestFirstName + lastName:kGTMABTestLastName]; + STAssertNotNil(person, nil); + STAssertTrue([book_ addRecord:person], nil); + STAssertTrue([book_ save], nil); + STAssertTrue([book_ addRecord:group], nil); + STAssertTrue([book_ save], nil); + STAssertTrue([group addMember:person], nil); + STAssertTrue([book_ save], nil); + members = [group members]; + STAssertEquals([members count], (NSUInteger)1, @"Members: %@", members); + STAssertTrue([group removeMember:person], nil); + STAssertFalse([group removeMember:person], nil); + STAssertFalse([group removeMember:nil], nil); + STAssertTrue([book_ removeRecord:group], nil); + STAssertTrue([book_ removeRecord:person], nil); + STAssertTrue([book_ save], nil); +} + + +- (void)testMultiValues { + STAssertThrows([[GTMABMultiValue alloc] init], nil); + STAssertThrows([[GTMABMutableMultiValue alloc] init], nil); + GTMABMultiValue *value = [[GTMABMultiValue alloc] initWithMultiValue:nil]; + STAssertNil(value, nil); + GTMABMutableMultiValue *mutValue + = [GTMABMutableMultiValue valueWithPropertyType:kGTMABInvalidPropertyType]; + STAssertNil(mutValue, nil); + mutValue + = [[[GTMABMutableMultiValue alloc] + initWithMutableMultiValue:nil] autorelease]; + STAssertNil(mutValue, nil); + mutValue + = [[[GTMABMutableMultiValue alloc] + initWithMultiValue:nil] autorelease]; + STAssertNil(mutValue, nil); +#if GTM_IPHONE_SDK + // Only the IPhone version actually allows you to check types of a multivalue + // before you stick anything in it + const GTMABPropertyType types[] = { + kGTMABStringPropertyType, + kGTMABIntegerPropertyType, + kGTMABRealPropertyType, + kGTMABDateTimePropertyType, + kGTMABDictionaryPropertyType, + kGTMABMultiStringPropertyType, + kGTMABMultiIntegerPropertyType, + kGTMABMultiRealPropertyType, + kGTMABMultiDateTimePropertyType, + kGTMABMultiDictionaryPropertyType + }; + for (size_t i = 0; i < sizeof(types) / sizeof(GTMABPropertyType); ++i) { + mutValue = [GTMABMutableMultiValue valueWithPropertyType:types[i]]; + STAssertNotNil(mutValue, nil); + // Oddly the Apple APIs allow you to create a mutable multi value with + // either a property type of kABFooPropertyType or kABMultiFooPropertyType + // and apparently you get back basically the same thing. However if you + // ask a type that you created with kABMultiFooPropertyType for it's type + // it returns just kABFooPropertyType. + STAssertEquals([mutValue propertyType], + (GTMABPropertyType)(types[i] & ~kABMultiValueMask), nil); + } +#endif // GTM_IPHONE_SDK + mutValue + = [GTMABMutableMultiValue valueWithPropertyType:kGTMABStringPropertyType]; + STAssertNotNil(mutValue, nil); + value = [[mutValue copy] autorelease]; + STAssertEqualObjects([value class], [GTMABMultiValue class], nil); + mutValue = [[value mutableCopy] autorelease]; + STAssertEqualObjects([mutValue class], [GTMABMutableMultiValue class], nil); + STAssertEquals([mutValue count], (NSUInteger)0, nil); + STAssertNil([mutValue valueAtIndex:0], nil); + STAssertNil([mutValue labelAtIndex:0], nil); +#if GTM_IPHONE_SDK + STAssertEquals([mutValue identifierAtIndex:0], + kGTMABMultiValueInvalidIdentifier, nil); + STAssertEquals([mutValue propertyType], + (GTMABPropertyType)kGTMABStringPropertyType, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects([mutValue identifierAtIndex:0], + kGTMABMultiValueInvalidIdentifier, nil); +#endif // GTM_IPHONE_SDK + GTMABMultiValueIdentifier ident + = [mutValue addValue:nil withLabel:(CFStringRef)kABHomeLabel]; +#if GTM_IPHONE_SDK + STAssertEquals(ident, kGTMABMultiValueInvalidIdentifier, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects(ident, kGTMABMultiValueInvalidIdentifier, nil); +#endif // GTM_IPHONE_SDK + + ident = [mutValue addValue:@"val1" + withLabel:nil]; +#if GTM_IPHONE_SDK + STAssertEquals(ident, kGTMABMultiValueInvalidIdentifier, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects(ident, kGTMABMultiValueInvalidIdentifier, nil); +#endif // GTM_IPHONE_SDK + ident = [mutValue insertValue:@"val1" + withLabel:nil + atIndex:0]; +#if GTM_IPHONE_SDK + STAssertEquals(ident, kGTMABMultiValueInvalidIdentifier, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects(ident, kGTMABMultiValueInvalidIdentifier, nil); +#endif // GTM_IPHONE_SDK + ident = [mutValue insertValue:nil + withLabel:(CFStringRef)kABHomeLabel + atIndex:0]; +#if GTM_IPHONE_SDK + STAssertEquals(ident, kGTMABMultiValueInvalidIdentifier, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects(ident, kGTMABMultiValueInvalidIdentifier, nil); +#endif // GTM_IPHONE_SDK + ident = [mutValue addValue:@"val1" + withLabel:(CFStringRef)kABHomeLabel]; +#if GTM_IPHONE_SDK + STAssertNotEquals(ident, kGTMABMultiValueInvalidIdentifier, nil); +#else // GTM_IPHONE_SDK + STAssertNotEqualObjects(ident, kGTMABMultiValueInvalidIdentifier, nil); +#endif // GTM_IPHONE_SDK + GTMABMultiValueIdentifier identCheck = [mutValue identifierAtIndex:0]; +#if GTM_IPHONE_SDK + STAssertEquals(ident, identCheck, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects(ident, identCheck, nil); +#endif // GTM_IPHONE_SDK + NSUInteger idx = [mutValue indexForIdentifier:ident]; + STAssertEquals(idx, (NSUInteger)0, nil); + STAssertTrue([mutValue replaceLabelAtIndex:0 + withLabel:(CFStringRef)kABWorkLabel], nil); + STAssertFalse([mutValue replaceLabelAtIndex:10 + withLabel:(CFStringRef)kABWorkLabel], nil); + STAssertTrue([mutValue replaceValueAtIndex:0 + withValue:@"newVal1"], nil); + STAssertFalse([mutValue replaceValueAtIndex:10 + withValue:@"newVal1"], nil); + + STAssertEqualObjects([mutValue valueForIdentifier:ident], @"newVal1", nil); + STAssertEqualObjects([mutValue labelForIdentifier:ident], + (NSString *)kABWorkLabel, nil); + + GTMABMultiValueIdentifier ident2 + = [mutValue insertValue:@"val2" + withLabel:(CFStringRef)kABOtherLabel + atIndex:0]; + STAssertNotEquals(ident2, kGTMABMultiValueInvalidIdentifier, nil); + STAssertNotEquals(ident2, ident, nil); + GTMABMultiValueIdentifier ident3 + = [mutValue insertValue:@"val3" + withLabel:(CFStringRef)kGTMABPersonPhoneMainLabel + atIndex:10]; +#if GTM_IPHONE_SDK + STAssertEquals(ident3, kGTMABMultiValueInvalidIdentifier, nil); +#else // GTM_IPHONE_SDK + STAssertEqualObjects(ident3, kGTMABMultiValueInvalidIdentifier, nil); +#endif // GTM_IPHONE_SDK + NSUInteger idx3 = [mutValue indexForIdentifier:ident3]; + STAssertEquals(idx3, (NSUInteger)NSNotFound, nil); + STAssertTrue([mutValue removeValueAndLabelAtIndex:1], nil); + STAssertFalse([mutValue removeValueAndLabelAtIndex:1], nil); + + NSUInteger idx4 + = [mutValue indexForIdentifier:kGTMABMultiValueInvalidIdentifier]; + STAssertEquals(idx4, (NSUInteger)NSNotFound, nil); + + STAssertNotNULL([mutValue multiValueRef], nil); + + // Enumerator test + mutValue + = [GTMABMutableMultiValue valueWithPropertyType:kGTMABIntegerPropertyType]; + STAssertNotNil(mutValue, nil); + for (int i = 0; i < 100; i++) { + NSString *label = [NSString stringWithFormat:@"label %d", i]; + NSNumber *val = [NSNumber numberWithInt:i]; + STAssertNotEquals([mutValue addValue:val + withLabel:(CFStringRef)label], + kGTMABMultiValueInvalidIdentifier, nil); + } + int count = 0; + NSString *label; + GTM_FOREACH_ENUMEREE(label, [mutValue labelEnumerator]) { + NSString *testLabel = [NSString stringWithFormat:@"label %d", count++]; + STAssertEqualObjects(label, testLabel, nil); + } + count = 0; + value = [[mutValue copy] autorelease]; + NSNumber *val; + GTM_FOREACH_ENUMEREE(val, [value valueEnumerator]) { + STAssertEqualObjects(val, [NSNumber numberWithInt:count++], nil); + } + + // Test messing with the values while we're enumerating them + NSEnumerator *labelEnum = [mutValue labelEnumerator]; + NSEnumerator *valueEnum = [mutValue valueEnumerator]; + STAssertNotNil(labelEnum, nil); + STAssertNotNil(valueEnum, nil); + STAssertNotNil([labelEnum nextObject], nil); + STAssertNotNil([valueEnum nextObject], nil); + STAssertTrue([mutValue removeValueAndLabelAtIndex:0], nil); + STAssertThrows([labelEnum nextObject], nil); + STAssertThrows([valueEnum nextObject], nil); + + // Test messing with the values while we're fast enumerating them + // Should throw an exception on the second access. + BOOL exceptionThrown = NO; + // Start at one because we removed index 0 above. + count = 1; + @try { + GTM_FOREACH_ENUMEREE(label, [mutValue labelEnumerator]) { + NSString *testLabel = [NSString stringWithFormat:@"label %d", count++]; + STAssertEqualObjects(label, testLabel, nil); + STAssertTrue([mutValue removeValueAndLabelAtIndex:50], nil); + } + } @catch(NSException *e) { + STAssertEqualObjects([e name], NSGenericException, @"Got %@ instead", e); + STAssertEquals(count, 2, + @"Should have caught it on the second access"); + exceptionThrown = YES; + } // COV_NF_LINE - because we always catch, this brace doesn't get exec'd + STAssertTrue(exceptionThrown, @"We should have thrown an exception" + @" because the values under the enumerator were modified"); + +} + +#if GTM_IPHONE_SDK +- (void)testRadar6208390 { + GTMABPropertyType types[] = { + kGTMABStringPropertyType, + kGTMABIntegerPropertyType, + kGTMABRealPropertyType, + kGTMABDateTimePropertyType, + kGTMABDictionaryPropertyType + }; + for (size_t j = 0; j < sizeof(types) / sizeof(ABPropertyType); ++j) { + ABPropertyType type = types[j]; +#ifdef GTM_IPHONE_SDK + ABMultiValueRef ref = ABMultiValueCreateMutable(type); +#else // GTM_IPHONE_SDK + ABMutableMultiValueRef ref = ABMultiValueCreateMutable(); +#endif // GTM_IPHONE_SDK + STAssertNotNULL(ref, nil); + NSString *label = [[NSString alloc] initWithString:@"label"]; + STAssertNotNil(label, nil); + id val = nil; + if (type == kGTMABDictionaryPropertyType) { + val = [[NSDictionary alloc] initWithObjectsAndKeys:@"1", @"1", nil]; + } else if (type == kGTMABStringPropertyType) { + val = [[NSString alloc] initWithFormat:@"value %d"]; + } else if (type == kGTMABIntegerPropertyType + || type == kGTMABRealPropertyType ) { + val = [[NSNumber alloc] initWithInt:143]; + } else if (type == kGTMABDateTimePropertyType) { + val = [[NSDate alloc] init]; + } + STAssertNotNil(val, + @"Testing type %d, %@", type, val); + NSUInteger firstRetainCount = [val retainCount]; + STAssertNotEquals(firstRetainCount, + (NSUInteger)0, + @"Testing type %d, %@", type, val); + + GTMABMultiValueIdentifier identifier; + STAssertTrue(ABMultiValueAddValueAndLabel(ref, + val, + (CFStringRef)label, + &identifier), + @"Testing type %d, %@", type, val); + NSUInteger secondRetainCount = [val retainCount]; + STAssertEquals(firstRetainCount + 1, + secondRetainCount, + @"Testing type %d, %@", type, val); + [label release]; + [val release]; + NSUInteger thirdRetainCount = [val retainCount]; + STAssertEquals(firstRetainCount, + thirdRetainCount, + @"Testing type %d, %@", type, val); + + id oldVal = val; + val = (id)ABMultiValueCopyValueAtIndex(ref, 0); + NSUInteger fourthRetainCount = [val retainCount]; + + // kABDictionaryPropertyTypes appear to do an actual copy, so the retain + // count checking trick won't work. We only check the retain count if + // we didn't get a new version. + if (val == oldVal) { + if (type == kGTMABIntegerPropertyType + || type == kGTMABRealPropertyType) { + // We are verifying that yes indeed 6208390 is still broken + STAssertEquals(fourthRetainCount, + thirdRetainCount, + @"Testing type %d, %@. If you see this error it may " + @"be time to update the code to change retain behaviors" + @"with this os version", type, val); + } else { + STAssertEquals(fourthRetainCount, + thirdRetainCount + 1, + @"Testing type %d, %@", type, val); + [val release]; + } + } else { + [val release]; + } + CFRelease(ref); + } +} + +// Globals used by testRadar6240394. +static GTMABPropertyID gGTMTestID; +static const GTMABPropertyID *gGTMTestIDPtr; + +void __attribute__((constructor))SetUpIDForTestRadar6240394(void) { + // These must be set up BEFORE ABAddressBookCreate is called. + gGTMTestID = kGTMABPersonLastNameProperty; + gGTMTestIDPtr = &kGTMABPersonLastNameProperty; +} + +- (void)testRadar6240394 { + // As of iPhone SDK 2.1, the property IDs aren't initialized until + // ABAddressBookCreate is actually called. They will return zero until + // then. Logged as radar 6240394. + STAssertEquals(gGTMTestID, 0, @"If this isn't zero, Apple has fixed 6240394"); + (void)ABAddressBookCreate(); + STAssertEquals(*gGTMTestIDPtr, kGTMABPersonLastNameProperty, + @"If this doesn't work, something else has broken"); +} + +#endif // GTM_IPHONE_SDK +@end diff --git a/AddressBook/TestData/phone.png b/AddressBook/TestData/phone.png new file mode 100644 index 0000000..7e4051f Binary files /dev/null and b/AddressBook/TestData/phone.png differ diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj index dda57d4..d2a0e17 100644 --- a/GTM.xcodeproj/project.pbxproj +++ b/GTM.xcodeproj/project.pbxproj @@ -182,6 +182,17 @@ 8BEEA90D0DA7446300894774 /* GTMUnitTestingImage.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */; }; 8BEEA90E0DA7446300894774 /* GTMUnitTestingWindow.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */; }; 8BEEA90F0DA7446300894774 /* GTMUnitTestingView.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90C0DA7446300894774 /* GTMUnitTestingView.tiff */; }; + 8BFE13B60FB0F2C0001BE894 /* GTMABAddressBook.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BFE13B00FB0F2B9001BE894 /* GTMABAddressBook.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BFE13B70FB0F2C0001BE894 /* GTMABAddressBook.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BFE13B10FB0F2B9001BE894 /* GTMABAddressBook.m */; }; + 8BFE13ED0FB0F2D8001BE894 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; + 8BFE13EE0FB0F2D8001BE894 /* GoogleToolboxForMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E086D0D199A5B00D5DDE0 /* GoogleToolboxForMac.framework */; }; + 8BFE13EF0FB0F2D8001BE894 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; }; + 8BFE14C10FB0F333001BE894 /* GTMABAddressBookTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BFE13B30FB0F2B9001BE894 /* GTMABAddressBookTest.m */; }; + 8BFE158D0FB0F34C001BE894 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFE158C0FB0F34C001BE894 /* AddressBook.framework */; }; + 8BFE158E0FB0F34C001BE894 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFE158C0FB0F34C001BE894 /* AddressBook.framework */; }; + 8BFE15970FB0F3C9001BE894 /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */; }; + 8BFE17F40FB1F6E5001BE894 /* GTMABAddressBook.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8BFE13B20FB0F2B9001BE894 /* GTMABAddressBook.strings */; }; + 8BFE17F50FB1F6EA001BE894 /* phone.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BFE13B50FB0F2B9001BE894 /* phone.png */; }; F413908F0D75F63C00F72B31 /* GTMNSFileManager+Path.h in Headers */ = {isa = PBXBuildFile; fileRef = F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */; settings = {ATTRIBUTES = (Public, ); }; }; F41390900D75F63C00F72B31 /* GTMNSFileManager+Path.m in Sources */ = {isa = PBXBuildFile; fileRef = F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */; }; F41390920D75F64D00F72B31 /* GTMNSFileManager+PathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */; }; @@ -309,6 +320,20 @@ remoteGlobalIDString = 8B45A2890DA49B99001148C5; remoteInfo = UIUnitTestingHarness; }; + 8BFE13BE0FB0F2D8001BE894 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8B45A2890DA49B99001148C5; + remoteInfo = UIUnitTestingHarness; + }; + 8BFE13C00FB0F2D8001BE894 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F42E086C0D199A5B00D5DDE0; + remoteInfo = GTM; + }; F42E08760D199A9B00D5DDE0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; @@ -465,6 +490,13 @@ 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingImage.tiff; sourceTree = ""; }; 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingWindow.tiff; sourceTree = ""; }; 8BEEA90C0DA7446300894774 /* GTMUnitTestingView.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingView.tiff; sourceTree = ""; }; + 8BFE13B00FB0F2B9001BE894 /* GTMABAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMABAddressBook.h; sourceTree = ""; }; + 8BFE13B10FB0F2B9001BE894 /* GTMABAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBook.m; sourceTree = ""; }; + 8BFE13B20FB0F2B9001BE894 /* GTMABAddressBook.strings */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; path = GTMABAddressBook.strings; sourceTree = ""; }; + 8BFE13B30FB0F2B9001BE894 /* GTMABAddressBookTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBookTest.m; sourceTree = ""; }; + 8BFE13B50FB0F2B9001BE894 /* phone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone.png; sourceTree = ""; }; + 8BFE13FC0FB0F2D8001BE894 /* UnitTest - AddressBook.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - AddressBook.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BFE158C0FB0F34C001BE894 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = /System/Library/Frameworks/AddressBook.framework; sourceTree = ""; }; F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSFileManager+Path.h"; sourceTree = ""; }; F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+Path.m"; sourceTree = ""; }; F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+PathTest.m"; sourceTree = ""; }; @@ -614,6 +646,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8BFE13EC0FB0F2D8001BE894 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BFE13ED0FB0F2D8001BE894 /* Cocoa.framework in Frameworks */, + 8BFE13EE0FB0F2D8001BE894 /* GoogleToolboxForMac.framework in Frameworks */, + 8BFE13EF0FB0F2D8001BE894 /* SenTestingKit.framework in Frameworks */, + 8BFE158E0FB0F34C001BE894 /* AddressBook.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F42E081C0D19987200D5DDE0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -635,6 +678,7 @@ F42E09AE0D19A62F00D5DDE0 /* Carbon.framework in Frameworks */, F43E4F6D0D4E60C50041161F /* libz.dylib in Frameworks */, 8B4D78080E40AFFA00EFEDD8 /* QuartzCore.framework in Frameworks */, + 8BFE158D0FB0F34C001BE894 /* AddressBook.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -660,6 +704,7 @@ F42E086D0D199A5B00D5DDE0 /* GoogleToolboxForMac.framework */, 8B45A0280DA4696C001148C5 /* UnitTest - UnitTesting.octest */, 8B45A28A0DA49B99001148C5 /* GTMUIUnitTestingHarness.app */, + 8BFE13FC0FB0F2D8001BE894 /* UnitTest - AddressBook.octest */, ); name = Products; sourceTree = ""; @@ -670,6 +715,7 @@ F4C978090D5B79C7001C29A6 /* ReleaseNotes.txt */, F440EDB70DFECC4B0003E81F /* BuildingAndUsing.txt */, 8B1A16050D90344B00CA1E8E /* GTMDefines.h */, + 8BFE13AF0FB0F2B9001BE894 /* AddressBook */, F48FE26F0D198CBA009257D2 /* AppKit */, F48FE2720D198CCE009257D2 /* Foundation */, F4FF22760D9D47FB003880AC /* DebugUtils */, @@ -689,6 +735,7 @@ 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = { isa = PBXGroup; children = ( + 8BFE158C0FB0F34C001BE894 /* AddressBook.framework */, F99161B40F0B151400213D3B /* libsqlite3.dylib */, 8B45A1990DA46AAA001148C5 /* QuartzCore.framework */, F43E4F6C0D4E60C50041161F /* libz.dylib */, @@ -729,6 +776,26 @@ path = GTMUIUnitTestingHarness; sourceTree = ""; }; + 8BFE13AF0FB0F2B9001BE894 /* AddressBook */ = { + isa = PBXGroup; + children = ( + 8BFE13B00FB0F2B9001BE894 /* GTMABAddressBook.h */, + 8BFE13B10FB0F2B9001BE894 /* GTMABAddressBook.m */, + 8BFE13B20FB0F2B9001BE894 /* GTMABAddressBook.strings */, + 8BFE13B30FB0F2B9001BE894 /* GTMABAddressBookTest.m */, + 8BFE13B40FB0F2B9001BE894 /* TestData */, + ); + path = AddressBook; + sourceTree = ""; + }; + 8BFE13B40FB0F2B9001BE894 /* TestData */ = { + isa = PBXGroup; + children = ( + 8BFE13B50FB0F2B9001BE894 /* phone.png */, + ); + path = TestData; + sourceTree = ""; + }; F435E46C0DC8F23A0069CDE8 /* TestData */ = { isa = PBXGroup; children = ( @@ -1110,6 +1177,7 @@ 10998E920F4B5952007F179D /* GTMTransientRootProxy.h in Headers */, 10998EF50F4B5D1A007F179D /* GTMTransientRootPortProxy.h in Headers */, 8B40994B0F93C5CC00DF540E /* GTMUILocalizer.h in Headers */, + 8BFE13B60FB0F2C0001BE894 /* GTMABAddressBook.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1184,6 +1252,26 @@ productReference = 8B45A28A0DA49B99001148C5 /* GTMUIUnitTestingHarness.app */; productType = "com.apple.product-type.application"; }; + 8BFE13BC0FB0F2D8001BE894 /* UnitTest - AddressBook */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8BFE13F20FB0F2D8001BE894 /* Build configuration list for PBXNativeTarget "UnitTest - AddressBook" */; + buildPhases = ( + 8BFE13C10FB0F2D8001BE894 /* Resources */, + 8BFE13D70FB0F2D8001BE894 /* Sources */, + 8BFE13EC0FB0F2D8001BE894 /* Frameworks */, + 8BFE13F10FB0F2D8001BE894 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 8BFE13BD0FB0F2D8001BE894 /* PBXTargetDependency */, + 8BFE13BF0FB0F2D8001BE894 /* PBXTargetDependency */, + ); + name = "UnitTest - AddressBook"; + productName = "UnitTest - AppKit"; + productReference = 8BFE13FC0FB0F2D8001BE894 /* UnitTest - AddressBook.octest */; + productType = "com.apple.product-type.bundle"; + }; F42E08110D19987200D5DDE0 /* UnitTest - Foundation */ = { isa = PBXNativeTarget; buildConfigurationList = F42E081E0D19987200D5DDE0 /* Build configuration list for PBXNativeTarget "UnitTest - Foundation" */; @@ -1260,6 +1348,7 @@ targets = ( F42E086C0D199A5B00D5DDE0 /* GTM */, F472042B0D33BEAF00E9F378 /* All UnitTests */, + 8BFE13BC0FB0F2D8001BE894 /* UnitTest - AddressBook */, F48FE2630D198C1E009257D2 /* UnitTest - AppKit */, F42E08110D19987200D5DDE0 /* UnitTest - Foundation */, 8B45A0270DA4696C001148C5 /* UnitTest - UnitTesting */, @@ -1297,6 +1386,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8BFE13C10FB0F2D8001BE894 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BFE17F40FB1F6E5001BE894 /* GTMABAddressBook.strings in Resources */, + 8BFE17F50FB1F6EA001BE894 /* phone.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F42E08140D19987200D5DDE0 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1366,7 +1464,20 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "GTMXcodeNote() {\n echo ${ThisScript}:${1}: note: GTM ${2}\n}\n\nGTMXcodeNote ${LINENO} \"Removing gcov data files from ${CONFIGURATION_TEMP_DIR}\"\n( cd \"${CONFIGURATION_TEMP_DIR}\" && find . -type f -name \"*.gcda\" -print0 | xargs -0 rm -f )\n\nxcodebuild -project \"GTM.xcodeproj\" -target \"UnitTest - UnitTesting\" -configuration \"${CONFIGURATION}\" OBJROOT=\"${OBJROOT}\" SYMROOT=\"${SYMROOT}\" CACHE_ROOT=\"${CACHE_ROOT}\" GTM_DO_NOT_REMOVE_GCOV_DATA=1\nxcodebuild -project \"GTM.xcodeproj\" -target \"UnitTest - Foundation\" -configuration \"${CONFIGURATION}\" OBJROOT=\"${OBJROOT}\" SYMROOT=\"${SYMROOT}\" CACHE_ROOT=\"${CACHE_ROOT}\" GTM_DO_NOT_REMOVE_GCOV_DATA=1\nxcodebuild -project \"GTM.xcodeproj\" -target \"UnitTest - AppKit\" -configuration \"${CONFIGURATION}\" OBJROOT=\"${OBJROOT}\" SYMROOT=\"${SYMROOT}\" CACHE_ROOT=\"${CACHE_ROOT}\" GTM_DO_NOT_REMOVE_GCOV_DATA=1"; + shellScript = "GTMXcodeNote() {\n echo ${ThisScript}:${1}: note: GTM ${2}\n}\n\nGTMXcodeNote ${LINENO} \"Removing gcov data files from ${CONFIGURATION_TEMP_DIR}\"\n( cd \"${CONFIGURATION_TEMP_DIR}\" && find . -type f -name \"*.gcda\" -print0 | xargs -0 rm -f )\n\nxcodebuild -project \"GTM.xcodeproj\" -target \"UnitTest - UnitTesting\" -configuration \"${CONFIGURATION}\" OBJROOT=\"${OBJROOT}\" SYMROOT=\"${SYMROOT}\" CACHE_ROOT=\"${CACHE_ROOT}\" GTM_DO_NOT_REMOVE_GCOV_DATA=1\nxcodebuild -project \"GTM.xcodeproj\" -target \"UnitTest - Foundation\" -configuration \"${CONFIGURATION}\" OBJROOT=\"${OBJROOT}\" SYMROOT=\"${SYMROOT}\" CACHE_ROOT=\"${CACHE_ROOT}\" GTM_DO_NOT_REMOVE_GCOV_DATA=1\nxcodebuild -project \"GTM.xcodeproj\" -target \"UnitTest - AppKit\" -configuration \"${CONFIGURATION}\" OBJROOT=\"${OBJROOT}\" SYMROOT=\"${SYMROOT}\" CACHE_ROOT=\"${CACHE_ROOT}\" GTM_DO_NOT_REMOVE_GCOV_DATA=1\nxcodebuild -project \"GTM.xcodeproj\" -target \"UnitTest - AddressBook\" -configuration \"${CONFIGURATION}\" OBJROOT=\"${OBJROOT}\" SYMROOT=\"${SYMROOT}\" CACHE_ROOT=\"${CACHE_ROOT}\" GTM_DO_NOT_REMOVE_GCOV_DATA=1"; + }; + 8BFE13F10FB0F2D8001BE894 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\"\n"; }; F42E081D0D19987200D5DDE0 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -1427,6 +1538,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8BFE13D70FB0F2D8001BE894 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BFE15970FB0F3C9001BE894 /* GTMSenTestCase.m in Sources */, + 8BFE14C10FB0F333001BE894 /* GTMABAddressBookTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F42E08160D19987200D5DDE0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1538,6 +1658,7 @@ 10998E8F0F4B593E007F179D /* GTMTransientRootProxy.m in Sources */, 10998EF40F4B5D1A007F179D /* GTMTransientRootPortProxy.m in Sources */, 8B40994C0F93C5CC00DF540E /* GTMUILocalizer.m in Sources */, + 8BFE13B70FB0F2C0001BE894 /* GTMABAddressBook.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1586,6 +1707,16 @@ target = 8B45A2890DA49B99001148C5 /* UIUnitTestingHarness */; targetProxy = 8BAA9A340F7AF50600DF4F12 /* PBXContainerItemProxy */; }; + 8BFE13BD0FB0F2D8001BE894 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8B45A2890DA49B99001148C5 /* UIUnitTestingHarness */; + targetProxy = 8BFE13BE0FB0F2D8001BE894 /* PBXContainerItemProxy */; + }; + 8BFE13BF0FB0F2D8001BE894 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F42E086C0D199A5B00D5DDE0 /* GTM */; + targetProxy = 8BFE13C00FB0F2D8001BE894 /* PBXContainerItemProxy */; + }; F42E08770D199A9B00D5DDE0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F42E086C0D199A5B00D5DDE0 /* GTM */; @@ -2040,6 +2171,129 @@ }; name = "TigerOrLater-Release"; }; + 8BFE13F30FB0F2D8001BE894 /* TigerOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "TigerOrLater-Debug"; + }; + 8BFE13F40FB0F2D8001BE894 /* TigerOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "TigerOrLater-Debug-gcov"; + }; + 8BFE13F50FB0F2D8001BE894 /* TigerOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "TigerOrLater-Release"; + }; + 8BFE13F60FB0F2D8001BE894 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + GCC_ENABLE_OBJC_GC = supported; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "LeopardOrLater-Debug"; + }; + 8BFE13F70FB0F2D8001BE894 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + GCC_ENABLE_OBJC_GC = supported; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + 8BFE13F80FB0F2D8001BE894 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + GCC_ENABLE_OBJC_GC = supported; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "LeopardOrLater-Release"; + }; + 8BFE13F90FB0F2D8001BE894 /* SnowLeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + GCC_ENABLE_OBJC_GC = supported; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "SnowLeopardOrLater-Debug"; + }; + 8BFE13FA0FB0F2D8001BE894 /* SnowLeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + GCC_ENABLE_OBJC_GC = supported; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "SnowLeopardOrLater-Debug-gcov"; + }; + 8BFE13FB0FB0F2D8001BE894 /* SnowLeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + GCC_ENABLE_OBJC_GC = supported; + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AddressBook"; + }; + name = "SnowLeopardOrLater-Release"; + }; F41A6EE10E02DB4F00788A6C /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2630,6 +2884,22 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "TigerOrLater-Release"; }; + 8BFE13F20FB0F2D8001BE894 /* Build configuration list for PBXNativeTarget "UnitTest - AddressBook" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8BFE13F30FB0F2D8001BE894 /* TigerOrLater-Debug */, + 8BFE13F40FB0F2D8001BE894 /* TigerOrLater-Debug-gcov */, + 8BFE13F50FB0F2D8001BE894 /* TigerOrLater-Release */, + 8BFE13F60FB0F2D8001BE894 /* LeopardOrLater-Debug */, + 8BFE13F70FB0F2D8001BE894 /* LeopardOrLater-Debug-gcov */, + 8BFE13F80FB0F2D8001BE894 /* LeopardOrLater-Release */, + 8BFE13F90FB0F2D8001BE894 /* SnowLeopardOrLater-Debug */, + 8BFE13FA0FB0F2D8001BE894 /* SnowLeopardOrLater-Debug-gcov */, + 8BFE13FB0FB0F2D8001BE894 /* SnowLeopardOrLater-Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "TigerOrLater-Release"; + }; F41A6EED0E02DB6800788A6C /* Build configuration list for PBXLegacyTarget "Build GTM All SDKs" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/GTMDefines.h b/GTMDefines.h index 846e56c..bd87b82 100644 --- a/GTMDefines.h +++ b/GTMDefines.h @@ -166,17 +166,20 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); // 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) + #define GTM_FOREACH_ENUMEREE(element, enumeration) \ + for (element in enumeration) #define GTM_FOREACH_OBJECT(element, collection) \ for (element in collection) #define GTM_FOREACH_KEY(element, collection) \ for (element in collection) #else - #define GTM_FOREACH_OBJECT(element, collection) \ - for (NSEnumerator * _ ## element ## _enum = [collection objectEnumerator]; \ + #define GTM_FOREACH_ENUMEREE(element, enumeration) \ + for (NSEnumerator *_ ## element ## _enum = enumeration; \ (element = [_ ## element ## _enum nextObject]) != nil; ) + #define GTM_FOREACH_OBJECT(element, collection) \ + GTM_FOREACH_ENUMEREE(element, [collection objectEnumerator]) #define GTM_FOREACH_KEY(element, collection) \ - for (NSEnumerator * _ ## element ## _enum = [collection keyEnumerator]; \ - (element = [_ ## element ## _enum nextObject]) != nil; ) + GTM_FOREACH_ENUMEREE(element, [collection keyEnumerator]) #endif #endif @@ -203,6 +206,17 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); #define GTM_MACOS_SDK 1 #endif +// Some of our own availability macros +#if GTM_MACOS_SDK +#define GTM_WEAK_ON_MACOS __weak +#define GTM_AVAILABLE_ONLY_ON_IPHONE UNAVAILABLE_ATTRIBUTE +#define GTM_AVAILABLE_ONLY_ON_MACOS +#else +#define GTM_WEAK_ON_MACOS +#define GTM_AVAILABLE_ONLY_ON_IPHONE +#define GTM_AVAILABLE_ONLY_ON_MACOS UNAVAILABLE_ATTRIBUTE +#endif + // Provide a symbol to include/exclude extra code for GC support. (This mainly // just controls the inclusion of finalize methods). #ifndef GTM_SUPPORT_GC diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj index 46d0a0f..8fc1472 100644 --- a/GTMiPhone.xcodeproj/project.pbxproj +++ b/GTMiPhone.xcodeproj/project.pbxproj @@ -42,9 +42,6 @@ 8B6C18750F3769D200E51E5D /* GTMNSObject+KeyValueObservingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6C18730F3769D200E51E5D /* GTMNSObject+KeyValueObservingTest.m */; }; 8B7DCEAA0DFF4C760017E983 /* GTMDevLogUnitTestingBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCEA90DFF4C760017E983 /* GTMDevLogUnitTestingBridge.m */; }; 8B7DCEAD0DFF4CA60017E983 /* GTMUnitTestDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */; }; - 8BA5F40B0E75669000798036 /* GTMABAddressBook.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BA5F4080E75669000798036 /* GTMABAddressBook.m */; }; - 8BA5F40C0E75669000798036 /* phone.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BA5F40A0E75669000798036 /* phone.png */; }; - 8BA5F52C0E7567AB00798036 /* GTMABAddressBookTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BA5F52B0E7567AB00798036 /* GTMABAddressBookTest.m */; }; 8BC0480F0DAE928A00C2D1CA /* GTMCalculatedRange.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */; }; 8BC048100DAE928A00C2D1CA /* GTMCalculatedRangeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047790DAE928A00C2D1CA /* GTMCalculatedRangeTest.m */; }; 8BC048130DAE928A00C2D1CA /* GTMNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0477F0DAE928A00C2D1CA /* GTMNSData+zlib.m */; }; @@ -74,6 +71,10 @@ 8BDA25140E759A6500C9769D /* GTMNSData+zlibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */; }; 8BE839890E89C74B00C611B0 /* GTMDebugThreadValidation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BE839870E89C74A00C611B0 /* GTMDebugThreadValidation.m */; }; 8BE83A660E8B059A00C611B0 /* GTMDebugThreadValidationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BE83A650E8B059A00C611B0 /* GTMDebugThreadValidationTest.m */; }; + 8BFE15C60FB0F764001BE894 /* GTMABAddressBook.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BFE15C10FB0F764001BE894 /* GTMABAddressBook.m */; }; + 8BFE15C70FB0F764001BE894 /* GTMABAddressBook.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8BFE15C20FB0F764001BE894 /* GTMABAddressBook.strings */; }; + 8BFE15C80FB0F764001BE894 /* GTMABAddressBookTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BFE15C30FB0F764001BE894 /* GTMABAddressBookTest.m */; }; + 8BFE15C90FB0F764001BE894 /* phone.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BFE15C50FB0F764001BE894 /* phone.png */; }; F417115A0ECDFF0400B9B276 /* GTMLightweightProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = F41711580ECDFF0400B9B276 /* GTMLightweightProxy.m */; }; F417115B0ECDFF0400B9B276 /* GTMLightweightProxyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F41711590ECDFF0400B9B276 /* GTMLightweightProxyTest.m */; }; F418AF990E7558EC004FB565 /* GTMExceptionalInlines.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AF940E7558DC004FB565 /* GTMExceptionalInlines.m */; }; @@ -140,10 +141,6 @@ 8B7DCEA90DFF4C760017E983 /* GTMDevLogUnitTestingBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDevLogUnitTestingBridge.m; sourceTree = ""; }; 8B7DCEAB0DFF4CA60017E983 /* GTMUnitTestDevLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestDevLog.h; sourceTree = ""; }; 8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestDevLog.m; sourceTree = ""; }; - 8BA5F4070E75669000798036 /* GTMABAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMABAddressBook.h; sourceTree = ""; }; - 8BA5F4080E75669000798036 /* GTMABAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBook.m; sourceTree = ""; }; - 8BA5F40A0E75669000798036 /* phone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone.png; sourceTree = ""; }; - 8BA5F52B0E7567AB00798036 /* GTMABAddressBookTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBookTest.m; sourceTree = ""; }; 8BC047750DAE926E00C2D1CA /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = ""; }; 8BC047770DAE928A00C2D1CA /* GTMCalculatedRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCalculatedRange.h; sourceTree = ""; }; 8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRange.m; sourceTree = ""; }; @@ -194,6 +191,11 @@ 8BE839870E89C74A00C611B0 /* GTMDebugThreadValidation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDebugThreadValidation.m; sourceTree = ""; }; 8BE839880E89C74A00C611B0 /* GTMDebugThreadValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDebugThreadValidation.h; sourceTree = ""; }; 8BE83A650E8B059A00C611B0 /* GTMDebugThreadValidationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDebugThreadValidationTest.m; sourceTree = ""; }; + 8BFE15C00FB0F764001BE894 /* GTMABAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMABAddressBook.h; sourceTree = ""; }; + 8BFE15C10FB0F764001BE894 /* GTMABAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBook.m; sourceTree = ""; }; + 8BFE15C20FB0F764001BE894 /* GTMABAddressBook.strings */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; path = GTMABAddressBook.strings; sourceTree = ""; }; + 8BFE15C30FB0F764001BE894 /* GTMABAddressBookTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBookTest.m; sourceTree = ""; }; + 8BFE15C50FB0F764001BE894 /* phone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone.png; sourceTree = ""; }; F41711570ECDFF0400B9B276 /* GTMLightweightProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLightweightProxy.h; sourceTree = ""; }; F41711580ECDFF0400B9B276 /* GTMLightweightProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLightweightProxy.m; sourceTree = ""; }; F41711590ECDFF0400B9B276 /* GTMLightweightProxyTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLightweightProxyTest.m; sourceTree = ""; }; @@ -259,6 +261,7 @@ children = ( 8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */, 8BC047750DAE926E00C2D1CA /* GTMDefines.h */, + 8BFE15BF0FB0F764001BE894 /* AddressBook */, 8BA5F4060E75669000798036 /* iPhone */, 8BC047760DAE928A00C2D1CA /* Foundation */, 8BC0479A0DAE928A00C2D1CA /* DebugUtils */, @@ -289,25 +292,13 @@ 8BA5F4060E75669000798036 /* iPhone */ = { isa = PBXGroup; children = ( - 8BA5F4070E75669000798036 /* GTMABAddressBook.h */, - 8BA5F4080E75669000798036 /* GTMABAddressBook.m */, - 8BA5F52B0E7567AB00798036 /* GTMABAddressBookTest.m */, F4E3B3D60EB5EF2400CB713D /* GTMUIFont+LineHeight.h */, F4E3B3D70EB5EF2400CB713D /* GTMUIFont+LineHeight.m */, F4E3B3E10EB5EF9A00CB713D /* GTMUIFont+LineHeightTest.m */, - 8BA5F4090E75669000798036 /* TestData */, ); path = iPhone; sourceTree = ""; }; - 8BA5F4090E75669000798036 /* TestData */ = { - isa = PBXGroup; - children = ( - 8BA5F40A0E75669000798036 /* phone.png */, - ); - path = TestData; - sourceTree = ""; - }; 8BC047760DAE928A00C2D1CA /* Foundation */ = { isa = PBXGroup; children = ( @@ -452,6 +443,26 @@ path = Project; sourceTree = ""; }; + 8BFE15BF0FB0F764001BE894 /* AddressBook */ = { + isa = PBXGroup; + children = ( + 8BFE15C00FB0F764001BE894 /* GTMABAddressBook.h */, + 8BFE15C10FB0F764001BE894 /* GTMABAddressBook.m */, + 8BFE15C20FB0F764001BE894 /* GTMABAddressBook.strings */, + 8BFE15C30FB0F764001BE894 /* GTMABAddressBookTest.m */, + 8BFE15C40FB0F764001BE894 /* TestData */, + ); + path = AddressBook; + sourceTree = ""; + }; + 8BFE15C40FB0F764001BE894 /* TestData */ = { + isa = PBXGroup; + children = ( + 8BFE15C50FB0F764001BE894 /* phone.png */, + ); + path = TestData; + sourceTree = ""; + }; F435E49F0DC8F5290069CDE8 /* TestData */ = { isa = PBXGroup; children = ( @@ -525,7 +536,8 @@ 8BC0486B0DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.gtmUTState in Resources */, 8BC0486C0DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.png in Resources */, 8BC04DE80DB023D400C2D1CA /* ReleaseNotes.txt in Resources */, - 8BA5F40C0E75669000798036 /* phone.png in Resources */, + 8BFE15C70FB0F764001BE894 /* GTMABAddressBook.strings in Resources */, + 8BFE15C90FB0F764001BE894 /* phone.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -598,8 +610,6 @@ F418AFCE0E755C94004FB565 /* GTMNSDictionary+URLArgumentsTest.m in Sources */, F418AFD70E755D44004FB565 /* GTMPath.m in Sources */, F418AFD80E755D44004FB565 /* GTMPathTest.m in Sources */, - 8BA5F40B0E75669000798036 /* GTMABAddressBook.m in Sources */, - 8BA5F52C0E7567AB00798036 /* GTMABAddressBookTest.m in Sources */, 8BDA25130E759A6400C9769D /* GTMHTTPServerTest.m in Sources */, 8BDA25140E759A6500C9769D /* GTMNSData+zlibTest.m in Sources */, 8BE839890E89C74B00C611B0 /* GTMDebugThreadValidation.m in Sources */, @@ -614,6 +624,8 @@ 6294461D0EDE17A0009295EA /* GTMNSArray+Merge.m in Sources */, 8B6C18740F3769D200E51E5D /* GTMNSObject+KeyValueObserving.m in Sources */, 8B6C18750F3769D200E51E5D /* GTMNSObject+KeyValueObservingTest.m in Sources */, + 8BFE15C60FB0F764001BE894 /* GTMABAddressBook.m in Sources */, + 8BFE15C80FB0F764001BE894 /* GTMABAddressBookTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index d2fd649..24b836f 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -214,9 +214,9 @@ Changes since 1.5.1 - If you are using GTMUnitTestDevLog, it also tries to capture logs from NSAssert. -- Added GTM_FOREACH_OBJECT/GTM_FOREACH_KEY that uses NSEnumerator and - objectEnumerator/keyEnumerator on 10.4, but on 10.5+/iPhone uses - FastEnumeration. +- Added GTM_FOREACH_OBJECT/GTM_FOREACH_KEY/GTM_FOREACH_ENUMEREE that uses + NSEnumerator and objectEnumerator/keyEnumerator on 10.4, but on 10.5+/iPhone + uses FastEnumeration. - GTMNSWorkspace+Running gives a variety of ways of determining the attributes of running processes. @@ -285,6 +285,10 @@ Changes since 1.5.1 - Added gtm_launchedApplications to GTMNSWorkspace+Running. It is significantly faster than calling [NSWorkspace launchedApplications] +- Moved GTMABAddressBook out of iPhone and into the AddressBook directory, + because it now works on both the Desktop and the iPhone giving you a single + interface to do AddressBook work on both platforms. + Release 1.5.1 Changes since 1.5.0 -- cgit v1.2.3