diff options
Diffstat (limited to 'iPhone/GTMABAddressBook.m')
-rw-r--r-- | iPhone/GTMABAddressBook.m | 902 |
1 files changed, 902 insertions, 0 deletions
diff --git a/iPhone/GTMABAddressBook.m b/iPhone/GTMABAddressBook.m new file mode 100644 index 0000000..0c54364 --- /dev/null +++ b/iPhone/GTMABAddressBook.m @@ -0,0 +1,902 @@ +// +// 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 { + ABPropertyType 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; +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(id *)stackbuf + count:(NSUInteger)len; +@end + +@implementation GTMABAddressBook ++ (GTMABAddressBook *)addressBook { + return [[[self alloc] init] autorelease]; +} + +- (id)init { + if ((self = [super init])) { + addressBook_ = ABAddressBookCreate(); + if (!addressBook_) { + // COV_NF_START + [self release]; + self = nil; + // COV_NF_END + } + } + return self; +} + +- (void)dealloc { + if (addressBook_) { + CFRelease(addressBook_); + } + [super dealloc]; +} + +- (BOOL)save { + return [self saveAndReturnError:NULL]; +} + +- (BOOL)saveAndReturnError:(NSError **)error { + CFErrorRef cfError = NULL; + bool wasGood = ABAddressBookSave(addressBook_, &cfError); + GTMCFAutorelease(cfError); + if (error) { + *error = (NSError *)cfError; // COV_NF_LINE + } + return wasGood ? YES : NO; +} + +- (BOOL)hasUnsavedChanges { + return ABAddressBookHasUnsavedChanges(addressBook_); +} + +- (void)revert { + ABAddressBookRevert(addressBook_); +} + +- (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; + 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 + } + 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; + 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 + } + return wasGood ? YES : NO; +} + +- (NSArray *)people { + NSArray *people + = GTMCFAutorelease(ABAddressBookCopyArrayOfAllPeople(addressBook_)); + NSMutableArray *result = [NSMutableArray arrayWithCapacity:[people count]]; + for (id person in people) { + [result addObject:[GTMABPerson recordWithRecord:person]]; + } + return result; +} + +- (NSArray *)groups { + NSArray *groups + = GTMCFAutorelease(ABAddressBookCopyArrayOfAllGroups(addressBook_)); + NSMutableArray *result = [NSMutableArray arrayWithCapacity:[groups count]]; + for (id group in groups) { + [result addObject:[GTMABGroup recordWithRecord:group]]; + } + return result; +} + +- (ABAddressBookRef)addressBookRef { + return addressBook_; +} + +- (GTMABPerson *)personForId:(ABRecordID)uniqueId { + GTMABPerson *person = nil; + ABRecordRef ref = ABAddressBookGetPersonWithRecordID(addressBook_, uniqueId); + if (ref) { + person = [GTMABPerson recordWithRecord:ref]; + } + return person; +} + +- (GTMABGroup *)groupForId:(ABRecordID)uniqueId { + GTMABGroup *group = nil; + ABRecordRef ref = ABAddressBookGetGroupWithRecordID(addressBook_, uniqueId); + if (ref) { + group = [GTMABGroup recordWithRecord:ref]; + } + return group; +} + ++ (NSString *)localizedLabel:(CFStringRef)label { + return GTMCFAutorelease(ABAddressBookCopyLocalizedLabel(label)); +} + +@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 doesNotRecognizeSelector:_cmd]; + } + if (!record) { + [self release]; + self = nil; + } else { + record_ = 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_; +} + +- (ABRecordID)recordID { + return ABRecordGetRecordID(record_); +} + +- (id)valueForProperty:(ABPropertyID)property { + id value = GTMCFAutorelease(ABRecordCopyValue(record_, property)); + if (value) { + if ([[self class] typeOfProperty:property] & kABMultiValueMask) { + value = [[[GTMABMultiValue alloc] initWithMultiValue:value] autorelease]; + } + } + return value; +} + +- (BOOL)setValue:(id)value forProperty:(ABPropertyID)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[] = { + { kABStringPropertyType, [NSString class] }, + { kABIntegerPropertyType, [NSNumber class] }, + { kABRealPropertyType, [NSNumber class] }, + { kABDateTimePropertyType, [NSDate class] }, + { kABDictionaryPropertyType, [NSDictionary class] }, + { kABMultiStringPropertyType, [GTMABMultiValue class] }, + { kABMultiRealPropertyType, [GTMABMultiValue class] }, + { kABMultiDateTimePropertyType, [GTMABMultiValue class] }, + { kABMultiDictionaryPropertyType, [GTMABMultiValue class] } + }; + ABPropertyType 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]; + } + 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 + } + return wasGood ? YES : NO; +} + +- (BOOL)removeValueForProperty:(ABPropertyID)property { + 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 + } + return wasGood ? YES : NO; +} + +- (NSString *)compositeName { + return GTMCFAutorelease(ABRecordCopyCompositeName(record_)); +} + +// COV_NF_START +// Both of these methods are to be overridden by their subclasses ++ (ABPropertyType)typeOfProperty:(ABPropertyID)property { + [self doesNotRecognizeSelector:_cmd]; + return kABInvalidPropertyType; +} + ++ (NSString *)localizedPropertyName:(ABPropertyID)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:kABPersonFirstNameProperty]; + } + if (isGood && last) { + isGood = [person setValue:last forProperty:kABPersonLastNameProperty]; + } + 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 { + 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 + } + return wasGood ? YES : NO; +} + +- (UIImage *)image { + return [UIImage imageWithData:[self imageData]]; +} + +- (BOOL)setImage:(UIImage *)image { + NSData *data = UIImagePNGRepresentation(image); + return [self setImageData:data]; +} + +- (NSData *)imageData { + return GTMCFAutorelease(ABPersonCopyImageData([self recordRef])); +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %@ %@ %d", + [self class], + [self valueForProperty:kABPersonFirstNameProperty], + [self valueForProperty:kABPersonLastNameProperty], + [self recordID]]; +} + ++ (NSString *)localizedPropertyName:(ABPropertyID)property { + return GTMCFAutorelease(ABPersonCopyLocalizedPropertyName(property)); +} + ++ (ABPersonCompositeNameFormat)compositeNameFormat { + return ABPersonGetCompositeNameFormat(); +} + ++ (ABPropertyType)typeOfProperty:(ABPropertyID)property { + return ABPersonGetTypeOfProperty(property); +} +@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]]; + NSEnumerator *enumerator = [people objectEnumerator]; + ABRecordRef person; + while ((person = [enumerator nextObject])) { + [gtmPeople addObject:[GTMABPerson recordWithRecord:person]]; + } + return gtmPeople; +} + +- (BOOL)addMember:(GTMABPerson *)person { + 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 + } + return wasGood ? YES : NO; +} + +- (BOOL)removeMember:(GTMABPerson *)person { + 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 + } + return wasGood ? YES : NO; +} + ++ (ABPropertyType)typeOfProperty:(ABPropertyID)property { + ABPropertyType type = kABInvalidPropertyType; + if (property == kABGroupNameProperty) { + type = kABStringPropertyType; + } + return type; +} + ++ (NSString *)localizedPropertyName:(ABPropertyID)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 { + [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 = [object valueAtIndex:i]; + isEqual = [value isEqual:objValue]; + } + } + } + } + return isEqual; +} + +- (void)dealloc { + if (multiValue_) { + CFRelease(multiValue_); + } + [super dealloc]; +} + +- (ABMultiValueRef)multiValueRef { + return multiValue_; +} + +- (NSUInteger)count { + return ABMultiValueGetCount(multiValue_); +} + +- (id)valueAtIndex:(NSUInteger)idx { + id value = nil; + if (idx < [self count]) { + value = GTMCFAutorelease(ABMultiValueCopyValueAtIndex(multiValue_, idx)); + ABPropertyType type = [self propertyType]; + if (type == kABIntegerPropertyType + || type == kABRealPropertyType + || type == kABDictionaryPropertyType) { + // 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; +} + +- (ABMultiValueIdentifier)identifierAtIndex:(NSUInteger)idx { + ABMultiValueIdentifier identifier = kABMultiValueInvalidIdentifier; + if (idx < [self count]) { + identifier = ABMultiValueGetIdentifierAtIndex(multiValue_, idx); + } + return identifier; +} + +- (NSUInteger)indexForIdentifier:(ABMultiValueIdentifier)identifier { + NSUInteger idx = ABMultiValueGetIndexForIdentifier(multiValue_, identifier); + return idx == (NSUInteger)kCFNotFound ? (NSUInteger)NSNotFound : idx; +} + +- (ABPropertyType)propertyType { + return ABMultiValueGetPropertyType(multiValue_); +} + +- (id)valueForIdentifier:(ABMultiValueIdentifier)identifier { + return [self valueAtIndex:[self indexForIdentifier:identifier]]; +} + +- (NSString *)labelForIdentifier:(ABMultiValueIdentifier)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:(ABPropertyType)type { + return [[[self alloc] initWithPropertyType:type] autorelease]; +} + +- (id)initWithPropertyType:(ABPropertyType)type { + ABMutableMultiValueRef ref = nil; + if (!(type & kABMultiValueMask) && (type != kABInvalidPropertyType)) { + ref = ABMultiValueCreateMutable(type); + } + 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[] = { + { kABStringPropertyType, [NSString class] }, + { kABIntegerPropertyType, [NSNumber class] }, + { kABRealPropertyType, [NSNumber class] }, + { kABDateTimePropertyType, [NSDate class] }, + { kABDictionaryPropertyType, [NSDictionary class] }, + }; + ABPropertyType type = [self propertyType]; + 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; +} + +- (ABMultiValueIdentifier)addValue:(id)value withLabel:(CFStringRef)label { + ABMultiValueIdentifier identifier = kABMultiValueInvalidIdentifier; + // We check label and value here because of + // radar 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash + if (!label + || ![self checkValueType:value] + || !ABMultiValueAddValueAndLabel(multiValue_, + value, + label, + &identifier)) { + identifier = kABMultiValueInvalidIdentifier; + } else { + mutations_++; + } + return identifier; +} + +- (ABMultiValueIdentifier)insertValue:(id)value + withLabel:(CFStringRef)label + atIndex:(NSUInteger)idx { + ABMultiValueIdentifier identifier = kABMultiValueInvalidIdentifier; + // 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 + if (idx > count + || !label + || ![self checkValueType:value] + || !ABMultiValueInsertValueAndLabelAtIndex(multiValue_, + value, + label, + idx, + &identifier)) { + identifier = kABMultiValueInvalidIdentifier; + } else { + mutations_++; + } + return identifier; +} + +- (BOOL)removeValueAndLabelAtIndex:(NSUInteger)idx { + BOOL isGood = NO; + NSUInteger count = [self count]; + if (idx < count) { + if (ABMultiValueRemoveValueAndLabelAtIndex(multiValue_, + idx)) { + 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 (ABMultiValueReplaceValueAtIndex(multiValue_, + value, idx)) { + mutations_++; + isGood = YES; + } + } + return isGood; +} + +- (BOOL)replaceLabelAtIndex:(NSUInteger)idx withLabel:(CFStringRef)label{ + BOOL isGood = NO; + NSUInteger count = [self count]; + if (idx < count) { + if (ABMultiValueReplaceLabelAtIndex(multiValue_, + label, + idx)) { + 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]; +} + +- (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 { + // 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; +} + +- (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 { + // 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 + |