aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AddressBook/GTMABAddressBook.h403
-rw-r--r--AddressBook/GTMABAddressBook.m1186
-rw-r--r--AddressBook/GTMABAddressBook.stringsbin0 -> 1428 bytes
-rw-r--r--AddressBook/GTMABAddressBookTest.m689
-rw-r--r--AddressBook/TestData/phone.pngbin0 -> 3242 bytes
-rw-r--r--GTM.xcodeproj/project.pbxproj272
-rw-r--r--GTMDefines.h22
-rw-r--r--GTMiPhone.xcodeproj/project.pbxproj56
-rw-r--r--ReleaseNotes.txt10
9 files changed, 2608 insertions, 30 deletions
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 <Foundation/Foundation.h>
+
+#if GTM_IPHONE_SDK
+#import <AddressBook/AddressBook.h>
+#else // GTM_IPHONE_SDK
+#import <AddressBook/AddressBook.h>
+#import <AddressBook/ABAddressBookC.h>
+#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 <NSCopying, NSMutableCopying> {
+ @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
--- /dev/null
+++ b/AddressBook/GTMABAddressBook.strings
Binary files 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
--- /dev/null
+++ b/AddressBook/TestData/phone.png
Binary files 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 = "<group>"; };
8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingWindow.tiff; sourceTree = "<group>"; };
8BEEA90C0DA7446300894774 /* GTMUnitTestingView.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingView.tiff; sourceTree = "<group>"; };
+ 8BFE13B00FB0F2B9001BE894 /* GTMABAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMABAddressBook.h; sourceTree = "<group>"; };
+ 8BFE13B10FB0F2B9001BE894 /* GTMABAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBook.m; sourceTree = "<group>"; };
+ 8BFE13B20FB0F2B9001BE894 /* GTMABAddressBook.strings */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; path = GTMABAddressBook.strings; sourceTree = "<group>"; };
+ 8BFE13B30FB0F2B9001BE894 /* GTMABAddressBookTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBookTest.m; sourceTree = "<group>"; };
+ 8BFE13B50FB0F2B9001BE894 /* phone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone.png; sourceTree = "<group>"; };
+ 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 = "<absolute>"; };
F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSFileManager+Path.h"; sourceTree = "<group>"; };
F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+Path.m"; sourceTree = "<group>"; };
F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+PathTest.m"; sourceTree = "<group>"; };
@@ -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 = "<group>";
@@ -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 = "<group>";
};
+ 8BFE13AF0FB0F2B9001BE894 /* AddressBook */ = {
+ isa = PBXGroup;
+ children = (
+ 8BFE13B00FB0F2B9001BE894 /* GTMABAddressBook.h */,
+ 8BFE13B10FB0F2B9001BE894 /* GTMABAddressBook.m */,
+ 8BFE13B20FB0F2B9001BE894 /* GTMABAddressBook.strings */,
+ 8BFE13B30FB0F2B9001BE894 /* GTMABAddressBookTest.m */,
+ 8BFE13B40FB0F2B9001BE894 /* TestData */,
+ );
+ path = AddressBook;
+ sourceTree = "<group>";
+ };
+ 8BFE13B40FB0F2B9001BE894 /* TestData */ = {
+ isa = PBXGroup;
+ children = (
+ 8BFE13B50FB0F2B9001BE894 /* phone.png */,
+ );
+ path = TestData;
+ sourceTree = "<group>";
+ };
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 = "<group>"; };
8B7DCEAB0DFF4CA60017E983 /* GTMUnitTestDevLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestDevLog.h; sourceTree = "<group>"; };
8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestDevLog.m; sourceTree = "<group>"; };
- 8BA5F4070E75669000798036 /* GTMABAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMABAddressBook.h; sourceTree = "<group>"; };
- 8BA5F4080E75669000798036 /* GTMABAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBook.m; sourceTree = "<group>"; };
- 8BA5F40A0E75669000798036 /* phone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone.png; sourceTree = "<group>"; };
- 8BA5F52B0E7567AB00798036 /* GTMABAddressBookTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBookTest.m; sourceTree = "<group>"; };
8BC047750DAE926E00C2D1CA /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = "<group>"; };
8BC047770DAE928A00C2D1CA /* GTMCalculatedRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCalculatedRange.h; sourceTree = "<group>"; };
8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRange.m; sourceTree = "<group>"; };
@@ -194,6 +191,11 @@
8BE839870E89C74A00C611B0 /* GTMDebugThreadValidation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDebugThreadValidation.m; sourceTree = "<group>"; };
8BE839880E89C74A00C611B0 /* GTMDebugThreadValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDebugThreadValidation.h; sourceTree = "<group>"; };
8BE83A650E8B059A00C611B0 /* GTMDebugThreadValidationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDebugThreadValidationTest.m; sourceTree = "<group>"; };
+ 8BFE15C00FB0F764001BE894 /* GTMABAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMABAddressBook.h; sourceTree = "<group>"; };
+ 8BFE15C10FB0F764001BE894 /* GTMABAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBook.m; sourceTree = "<group>"; };
+ 8BFE15C20FB0F764001BE894 /* GTMABAddressBook.strings */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; path = GTMABAddressBook.strings; sourceTree = "<group>"; };
+ 8BFE15C30FB0F764001BE894 /* GTMABAddressBookTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBookTest.m; sourceTree = "<group>"; };
+ 8BFE15C50FB0F764001BE894 /* phone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone.png; sourceTree = "<group>"; };
F41711570ECDFF0400B9B276 /* GTMLightweightProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLightweightProxy.h; sourceTree = "<group>"; };
F41711580ECDFF0400B9B276 /* GTMLightweightProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLightweightProxy.m; sourceTree = "<group>"; };
F41711590ECDFF0400B9B276 /* GTMLightweightProxyTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLightweightProxyTest.m; sourceTree = "<group>"; };
@@ -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 = "<group>";
};
- 8BA5F4090E75669000798036 /* TestData */ = {
- isa = PBXGroup;
- children = (
- 8BA5F40A0E75669000798036 /* phone.png */,
- );
- path = TestData;
- sourceTree = "<group>";
- };
8BC047760DAE928A00C2D1CA /* Foundation */ = {
isa = PBXGroup;
children = (
@@ -452,6 +443,26 @@
path = Project;
sourceTree = "<group>";
};
+ 8BFE15BF0FB0F764001BE894 /* AddressBook */ = {
+ isa = PBXGroup;
+ children = (
+ 8BFE15C00FB0F764001BE894 /* GTMABAddressBook.h */,
+ 8BFE15C10FB0F764001BE894 /* GTMABAddressBook.m */,
+ 8BFE15C20FB0F764001BE894 /* GTMABAddressBook.strings */,
+ 8BFE15C30FB0F764001BE894 /* GTMABAddressBookTest.m */,
+ 8BFE15C40FB0F764001BE894 /* TestData */,
+ );
+ path = AddressBook;
+ sourceTree = "<group>";
+ };
+ 8BFE15C40FB0F764001BE894 /* TestData */ = {
+ isa = PBXGroup;
+ children = (
+ 8BFE15C50FB0F764001BE894 /* phone.png */,
+ );
+ path = TestData;
+ sourceTree = "<group>";
+ };
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