/core/externals/update-engine/externals/google-toolbox-for-mac/AddressBook/GTMABAddressBook.m

http://macfuse.googlecode.com/ · Objective C · 1241 lines · 1014 code · 105 blank · 122 comment · 147 complexity · f4bb39731b724e82dbd03673f93d8cae MD5 · raw file

  1. //
  2. // GTMAddressBook.m
  3. //
  4. // Copyright 2008 Google Inc.
  5. //
  6. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7. // use this file except in compliance with the License. You may obtain a copy
  8. // of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. // License for the specific language governing permissions and limitations under
  16. // the License.
  17. //
  18. #import "GTMABAddressBook.h"
  19. #import "GTMTypeCasting.h"
  20. #if GTM_IPHONE_SDK
  21. #import <UIKit/UIKit.h>
  22. #else // GTM_IPHONE_SDK
  23. #import <Cocoa/Cocoa.h>
  24. #endif // GTM_IPHONE_SDK
  25. #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
  26. // Tiger does not have this functionality, so we just set them to 0
  27. // as they are "or'd" in. This does change the functionality slightly.
  28. enum {
  29. NSDiacriticInsensitiveSearch = 0,
  30. NSWidthInsensitiveSearch = 0
  31. };
  32. #endif
  33. NSString *const kGTMABUnknownPropertyName = @"UNKNOWN_PROPERTY";
  34. typedef struct {
  35. GTMABPropertyType pType;
  36. Class class;
  37. } TypeClassNameMap;
  38. @interface GTMABMultiValue ()
  39. - (unsigned long*)mutations;
  40. @end
  41. @interface GTMABMutableMultiValue ()
  42. // Checks to see if a value is a valid type to be stored in this multivalue
  43. - (BOOL)checkValueType:(id)value;
  44. @end
  45. @interface GTMABMultiValueEnumerator : NSEnumerator {
  46. @private
  47. __weak ABMultiValueRef ref_; // ref_ cached from enumeree_
  48. GTMABMultiValue *enumeree_;
  49. unsigned long mutations_;
  50. NSUInteger count_;
  51. NSUInteger index_;
  52. BOOL useLabels_;
  53. }
  54. + (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree;
  55. + (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree;
  56. - (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels;
  57. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  58. - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
  59. objects:(id *)stackbuf
  60. count:(NSUInteger)len;
  61. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  62. @end
  63. @implementation GTMABAddressBook
  64. + (GTMABAddressBook *)addressBook {
  65. return [[[self alloc] init] autorelease];
  66. }
  67. - (id)init {
  68. if ((self = [super init])) {
  69. #if GTM_IPHONE_SDK
  70. CFErrorRef error = nil;
  71. addressBook_ = ABAddressBookCreateWithOptions(NULL, &error);
  72. if (error) {
  73. _GTMDevLog(@"ABAddressBookCreate: %@", error);
  74. CFRelease(error);
  75. }
  76. #else // GTM_IPHONE_SDK
  77. addressBook_ = ABGetSharedAddressBook();
  78. CFRetain(addressBook_);
  79. #endif // GTM_IPHONE_SDK
  80. if (!addressBook_) {
  81. // COV_NF_START
  82. [self release];
  83. self = nil;
  84. // COV_NF_END
  85. }
  86. }
  87. return self;
  88. }
  89. - (void)dealloc {
  90. if (addressBook_) {
  91. CFRelease(addressBook_);
  92. }
  93. [super dealloc];
  94. }
  95. - (BOOL)save {
  96. #if GTM_IPHONE_SDK
  97. CFErrorRef cfError = NULL;
  98. bool wasGood = ABAddressBookSave(addressBook_, &cfError);
  99. if (!wasGood) {
  100. _GTMDevLog(@"Error in [%@ %@]: %@",
  101. [self class], NSStringFromSelector(_cmd), cfError);
  102. CFRelease(cfError);
  103. }
  104. #else // GTM_IPHONE_SDK
  105. bool wasGood = ABSave(addressBook_);
  106. #endif // GTM_IPHONE_SDK
  107. return wasGood ? YES : NO;
  108. }
  109. - (BOOL)hasUnsavedChanges {
  110. bool hasUnsavedChanges;
  111. #if GTM_IPHONE_SDK
  112. hasUnsavedChanges = ABAddressBookHasUnsavedChanges(addressBook_);
  113. #else // GTM_IPHONE_SDK
  114. hasUnsavedChanges = ABHasUnsavedChanges(addressBook_);
  115. #endif // GTM_IPHONE_SDK
  116. return hasUnsavedChanges ? YES : NO;
  117. }
  118. - (BOOL)addRecord:(GTMABRecord *)record {
  119. // Note: we check for bad data here because of radar
  120. // 6201258 Adding a NULL record using ABAddressBookAddRecord crashes
  121. if (!record) return NO;
  122. #if GTM_IPHONE_SDK
  123. CFErrorRef cfError = NULL;
  124. bool wasGood = ABAddressBookAddRecord(addressBook_,
  125. [record recordRef], &cfError);
  126. if (cfError) {
  127. // COV_NF_START
  128. _GTMDevLog(@"Error in [%@ %@]: %@",
  129. [self class], NSStringFromSelector(_cmd), cfError);
  130. CFRelease(cfError);
  131. // COV_NF_END
  132. }
  133. #else // GTM_IPHONE_SDK
  134. bool wasGood = ABAddRecord(addressBook_, [record recordRef]);
  135. #endif // GTM_IPHONE_SDK
  136. return wasGood ? YES : NO;
  137. }
  138. - (BOOL)removeRecord:(GTMABRecord *)record {
  139. // Note: we check for bad data here because of radar
  140. // 6201276 Removing a NULL record using ABAddressBookRemoveRecord crashes
  141. if (!record) return NO;
  142. #if GTM_IPHONE_SDK
  143. CFErrorRef cfError = NULL;
  144. bool wasGood = ABAddressBookRemoveRecord(addressBook_,
  145. [record recordRef], &cfError);
  146. if (cfError) {
  147. // COV_NF_START
  148. _GTMDevLog(@"Error in [%@ %@]: %@",
  149. [self class], NSStringFromSelector(_cmd), cfError);
  150. CFRelease(cfError);
  151. // COV_NF_END
  152. }
  153. #else // GTM_IPHONE_SDK
  154. GTMABRecordID recID = [record recordID];
  155. ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_, (CFStringRef)recID);
  156. bool wasGood = NO;
  157. if (ref) {
  158. wasGood = ABRemoveRecord(addressBook_, [record recordRef]);
  159. CFRelease(ref);
  160. }
  161. #endif // GTM_IPHONE_SDK
  162. return wasGood ? YES : NO;
  163. }
  164. - (NSArray *)people {
  165. #if GTM_IPHONE_SDK
  166. NSArray *people
  167. = GTMCFAutorelease(ABAddressBookCopyArrayOfAllPeople(addressBook_));
  168. #else // GTM_IPHONE_SDK
  169. NSArray *people
  170. = GTMCFAutorelease(ABCopyArrayOfAllPeople(addressBook_));
  171. #endif // GTM_IPHONE_SDK
  172. NSMutableArray *result = [NSMutableArray arrayWithCapacity:[people count]];
  173. id person;
  174. GTM_FOREACH_OBJECT(person, people) {
  175. [result addObject:[GTMABPerson recordWithRecord:person]];
  176. }
  177. return result;
  178. }
  179. - (NSArray *)groups {
  180. #if GTM_IPHONE_SDK
  181. NSArray *groups
  182. = GTMCFAutorelease(ABAddressBookCopyArrayOfAllGroups(addressBook_));
  183. #else // GTM_IPHONE_SDK
  184. NSArray *groups
  185. = GTMCFAutorelease(ABCopyArrayOfAllGroups(addressBook_));
  186. #endif // GTM_IPHONE_SDK
  187. NSMutableArray *result = [NSMutableArray arrayWithCapacity:[groups count]];
  188. id group;
  189. GTM_FOREACH_OBJECT(group, groups) {
  190. [result addObject:[GTMABGroup recordWithRecord:group]];
  191. }
  192. return result;
  193. }
  194. - (ABAddressBookRef)addressBookRef {
  195. return addressBook_;
  196. }
  197. - (GTMABPerson *)personForId:(GTMABRecordID)uniqueId {
  198. GTMABPerson *person = nil;
  199. #if GTM_IPHONE_SDK
  200. ABRecordRef ref = ABAddressBookGetPersonWithRecordID(addressBook_, uniqueId);
  201. #else // GTM_IPHONE_SDK
  202. ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_,
  203. (CFStringRef)uniqueId);
  204. #endif // GTM_IPHONE_SDK
  205. if (ref) {
  206. person = [GTMABPerson recordWithRecord:ref];
  207. }
  208. return person;
  209. }
  210. - (GTMABGroup *)groupForId:(GTMABRecordID)uniqueId {
  211. GTMABGroup *group = nil;
  212. #if GTM_IPHONE_SDK
  213. ABRecordRef ref = ABAddressBookGetGroupWithRecordID(addressBook_, uniqueId);
  214. #else // GTM_IPHONE_SDK
  215. ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_,
  216. (CFStringRef)uniqueId);
  217. #endif // GTM_IPHONE_SDK
  218. if (ref) {
  219. group = [GTMABGroup recordWithRecord:ref];
  220. }
  221. return group;
  222. }
  223. // Performs a prefix search on the composite names of people in an address book
  224. // and returns an array of persons that match the search criteria.
  225. - (NSArray *)peopleWithCompositeNameWithPrefix:(NSString *)prefix {
  226. #if GTM_IPHONE_SDK
  227. NSArray *people =
  228. GTMCFAutorelease(ABAddressBookCopyPeopleWithName(addressBook_,
  229. (CFStringRef)prefix));
  230. NSMutableArray *gtmPeople = [NSMutableArray arrayWithCapacity:[people count]];
  231. id person;
  232. GTM_FOREACH_OBJECT(person, people) {
  233. GTMABPerson *gtmPerson = [GTMABPerson recordWithRecord:person];
  234. [gtmPeople addObject:gtmPerson];
  235. }
  236. return gtmPeople;
  237. #else
  238. // TODO(dmaclach): Change over to recordsMatchingSearchElement as an
  239. // optimization?
  240. // TODO(dmaclach): Make this match the way that the iPhone does it (by
  241. // checking both first and last names) and adding unittests for all this.
  242. NSArray *people = [self people];
  243. NSMutableArray *foundPeople = [NSMutableArray array];
  244. GTMABPerson *person;
  245. GTM_FOREACH_OBJECT(person, people) {
  246. NSString *compositeName = [person compositeName];
  247. NSRange range = [compositeName rangeOfString:prefix
  248. options:(NSCaseInsensitiveSearch
  249. | NSDiacriticInsensitiveSearch
  250. | NSWidthInsensitiveSearch
  251. | NSAnchoredSearch)];
  252. if (range.location != NSNotFound) {
  253. [foundPeople addObject:person];
  254. }
  255. }
  256. return foundPeople;
  257. #endif
  258. }
  259. // Performs a prefix search on the composite names of groups in an address book
  260. // and returns an array of groups that match the search criteria.
  261. - (NSArray *)groupsWithCompositeNameWithPrefix:(NSString *)prefix {
  262. NSArray *groups = [self groups];
  263. NSMutableArray *foundGroups = [NSMutableArray array];
  264. GTMABGroup *group;
  265. GTM_FOREACH_OBJECT(group, groups) {
  266. NSString *compositeName = [group compositeName];
  267. NSRange range = [compositeName rangeOfString:prefix
  268. options:(NSCaseInsensitiveSearch
  269. | NSDiacriticInsensitiveSearch
  270. | NSWidthInsensitiveSearch
  271. | NSAnchoredSearch)];
  272. if (range.location != NSNotFound) {
  273. [foundGroups addObject:group];
  274. }
  275. }
  276. return foundGroups;
  277. }
  278. + (NSString *)localizedLabel:(NSString *)label {
  279. #if GTM_IPHONE_SDK
  280. return GTMCFAutorelease(ABAddressBookCopyLocalizedLabel((CFStringRef)label));
  281. #else // GTM_IPHONE_SDK
  282. return GTMCFAutorelease(ABCopyLocalizedPropertyOrLabel((CFStringRef)label));
  283. #endif // GTM_IPHONE_SDK
  284. }
  285. @end
  286. @implementation GTMABRecord
  287. + (id)recordWithRecord:(ABRecordRef)record {
  288. return [[[self alloc] initWithRecord:record] autorelease];
  289. }
  290. - (id)initWithRecord:(ABRecordRef)record {
  291. if ((self = [super init])) {
  292. if ([self class] == [GTMABRecord class]) {
  293. [self autorelease];
  294. [self doesNotRecognizeSelector:_cmd];
  295. }
  296. if (!record) {
  297. [self release];
  298. self = nil;
  299. } else {
  300. record_ = (ABRecordRef)CFRetain(record);
  301. }
  302. }
  303. return self;
  304. }
  305. - (NSUInteger)hash {
  306. // This really isn't completely valid due to
  307. // 6203836 ABRecords hash to their address
  308. // but it's the best we can do without knowing what properties
  309. // are in a record, and we don't have an API for that.
  310. return CFHash(record_);
  311. }
  312. - (BOOL)isEqual:(id)object {
  313. // This really isn't completely valid due to
  314. // 6203836 ABRecords hash to their address
  315. // but it's the best we can do without knowing what properties
  316. // are in a record, and we don't have an API for that.
  317. return [object respondsToSelector:@selector(recordRef)]
  318. && CFEqual(record_, [object recordRef]);
  319. }
  320. - (void)dealloc {
  321. if (record_) {
  322. CFRelease(record_);
  323. }
  324. [super dealloc];
  325. }
  326. - (ABRecordRef)recordRef {
  327. return record_;
  328. }
  329. - (GTMABRecordID)recordID {
  330. #if GTM_IPHONE_SDK
  331. return ABRecordGetRecordID(record_);
  332. #else // GTM_IPHONE_SDK
  333. return GTMCFAutorelease(ABRecordCopyUniqueId(record_));
  334. #endif // GTM_IPHONE_SDK
  335. }
  336. - (id)valueForProperty:(GTMABPropertyID)property {
  337. #if GTM_IPHONE_SDK
  338. id value = GTMCFAutorelease(ABRecordCopyValue(record_, property));
  339. #else // GTM_IPHONE_SDK
  340. id value = GTMCFAutorelease(ABRecordCopyValue(record_, (CFStringRef)property));
  341. #endif // GTM_IPHONE_SDK
  342. if (value) {
  343. if ([[self class] typeOfProperty:property] & kABMultiValueMask) {
  344. value = [[[GTMABMultiValue alloc]
  345. initWithMultiValue:(ABMultiValueRef)value] autorelease];
  346. }
  347. }
  348. return value;
  349. }
  350. - (BOOL)setValue:(id)value forProperty:(GTMABPropertyID)property {
  351. if (!value) return NO;
  352. // We check the type here because of
  353. // Radar 6201046 ABRecordSetValue returns true even if you pass in a bad type
  354. // for a value
  355. TypeClassNameMap fullTypeMap[] = {
  356. { kGTMABStringPropertyType, [NSString class] },
  357. { kGTMABIntegerPropertyType, [NSNumber class] },
  358. { kGTMABRealPropertyType, [NSNumber class] },
  359. { kGTMABDateTimePropertyType, [NSDate class] },
  360. { kGTMABDictionaryPropertyType, [NSDictionary class] },
  361. { kGTMABMultiStringPropertyType, [GTMABMultiValue class] },
  362. { kGTMABMultiRealPropertyType, [GTMABMultiValue class] },
  363. { kGTMABMultiDateTimePropertyType, [GTMABMultiValue class] },
  364. { kGTMABMultiDictionaryPropertyType, [GTMABMultiValue class] }
  365. };
  366. GTMABPropertyType type = [[self class] typeOfProperty:property];
  367. BOOL wasFound = NO;
  368. for (size_t i = 0; i < sizeof(fullTypeMap) / sizeof(TypeClassNameMap); ++i) {
  369. if (fullTypeMap[i].pType == type) {
  370. wasFound = YES;
  371. if (![[value class] isSubclassOfClass:fullTypeMap[i].class]) {
  372. return NO;
  373. }
  374. }
  375. }
  376. if (!wasFound) {
  377. return NO;
  378. }
  379. if (type & kABMultiValueMask) {
  380. value = (id)[value multiValueRef];
  381. }
  382. #if GTM_IPHONE_SDK
  383. CFErrorRef cfError = nil;
  384. bool wasGood = ABRecordSetValue(record_, property,
  385. (CFTypeRef)value, &cfError);
  386. if (cfError) {
  387. // COV_NF_START
  388. _GTMDevLog(@"Error in [%@ %@]: %@",
  389. [self class], NSStringFromSelector(_cmd), cfError);
  390. CFRelease(cfError);
  391. // COV_NF_END
  392. }
  393. #else // GTM_IPHONE_SDK
  394. bool wasGood = ABRecordSetValue(record_, (CFStringRef)property, (CFTypeRef)value);
  395. #endif // GTM_IPHONE_SDK
  396. return wasGood ? YES : NO;
  397. }
  398. - (BOOL)removeValueForProperty:(GTMABPropertyID)property {
  399. #if GTM_IPHONE_SDK
  400. CFErrorRef cfError = nil;
  401. // We check to see if the value is in the property because of:
  402. // Radar 6201005 ABRecordRemoveValue returns true for value that aren't
  403. // in the record
  404. id value = [self valueForProperty:property];
  405. bool wasGood = value && ABRecordRemoveValue(record_, property, &cfError);
  406. if (cfError) {
  407. // COV_NF_START
  408. _GTMDevLog(@"Error in [%@ %@]: %@",
  409. [self class], NSStringFromSelector(_cmd), cfError);
  410. CFRelease(cfError);
  411. // COV_NF_END
  412. }
  413. #else // GTM_IPHONE_SDK
  414. id value = [self valueForProperty:property];
  415. bool wasGood = value && ABRecordRemoveValue(record_, (CFStringRef)property);
  416. #endif // GTM_IPHONE_SDK
  417. return wasGood ? YES : NO;
  418. }
  419. // COV_NF_START
  420. // All of these methods are to be overridden by their subclasses
  421. - (NSString *)compositeName {
  422. [self doesNotRecognizeSelector:_cmd];
  423. return nil;
  424. }
  425. + (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property {
  426. [self doesNotRecognizeSelector:_cmd];
  427. return kGTMABInvalidPropertyType;
  428. }
  429. + (NSString *)localizedPropertyName:(GTMABPropertyID)property {
  430. [self doesNotRecognizeSelector:_cmd];
  431. return nil;
  432. }
  433. // COV_NF_END
  434. @end
  435. @implementation GTMABPerson
  436. + (GTMABPerson *)personWithFirstName:(NSString *)first
  437. lastName:(NSString *)last {
  438. GTMABPerson *person = [[[self alloc] init] autorelease];
  439. if (person) {
  440. BOOL isGood = YES;
  441. if (first) {
  442. isGood = [person setValue:first
  443. forProperty:kGTMABPersonFirstNameProperty];
  444. }
  445. if (isGood && last) {
  446. isGood = [person setValue:last forProperty:kGTMABPersonLastNameProperty];
  447. }
  448. if (!isGood) {
  449. // COV_NF_START
  450. // Marked as NF because I don't know how to force an error
  451. person = nil;
  452. // COV_NF_END
  453. }
  454. }
  455. return person;
  456. }
  457. - (id)init {
  458. ABRecordRef person = ABPersonCreate();
  459. self = [super initWithRecord:person];
  460. if (person) {
  461. CFRelease(person);
  462. }
  463. return self;
  464. }
  465. - (BOOL)setImageData:(NSData *)data {
  466. #if GTM_IPHONE_SDK
  467. CFErrorRef cfError = NULL;
  468. bool wasGood = NO;
  469. if (!data) {
  470. wasGood = ABPersonRemoveImageData([self recordRef], &cfError);
  471. } else {
  472. // We verify that the data is good because of:
  473. // Radar 6202868 ABPersonSetImageData should validate image data
  474. UIImage *image = [UIImage imageWithData:data];
  475. wasGood = image && ABPersonSetImageData([self recordRef],
  476. (CFDataRef)data, &cfError);
  477. }
  478. if (cfError) {
  479. // COV_NF_START
  480. _GTMDevLog(@"Error in [%@ %@]: %@",
  481. [self class], NSStringFromSelector(_cmd), cfError);
  482. CFRelease(cfError);
  483. // COV_NF_END
  484. }
  485. #else // GTM_IPHONE_SDK
  486. bool wasGood = YES;
  487. if (data) {
  488. NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
  489. wasGood = image != nil;
  490. }
  491. wasGood = wasGood && ABPersonSetImageData([self recordRef], (CFDataRef)data);
  492. #endif // GTM_IPHONE_SDK
  493. return wasGood ? YES : NO;
  494. }
  495. - (GTMABImage *)image {
  496. NSData *data = [self imageData];
  497. #if GTM_IPHONE_SDK
  498. return [UIImage imageWithData:data];
  499. #else // GTM_IPHONE_SDK
  500. return [[[NSImage alloc] initWithData:data] autorelease];
  501. #endif // GTM_IPHONE_SDK
  502. }
  503. - (BOOL)setImage:(GTMABImage *)image {
  504. #if GTM_IPHONE_SDK
  505. NSData *data = UIImagePNGRepresentation(image);
  506. #else // GTM_IPHONE_SDK
  507. NSData *data = [image TIFFRepresentation];
  508. #endif // GTM_IPHONE_SDK
  509. return [self setImageData:data];
  510. }
  511. - (NSData *)imageData {
  512. return GTMCFAutorelease(ABPersonCopyImageData([self recordRef]));
  513. }
  514. - (NSString *)compositeName {
  515. #if GTM_IPHONE_SDK
  516. return GTMCFAutorelease(ABRecordCopyCompositeName([self recordRef]));
  517. #else // GTM_IPHONE_SDK
  518. NSNumber *nsFlags = [self valueForProperty:kABPersonFlags];
  519. NSInteger flags = [nsFlags longValue];
  520. NSString *compositeName = nil;
  521. if (flags & kABShowAsCompany) {
  522. compositeName = [self valueForProperty:kABOrganizationProperty];
  523. } else {
  524. NSString *firstName = [self valueForProperty:kGTMABPersonFirstNameProperty];
  525. NSString *lastName = [self valueForProperty:kGTMABPersonLastNameProperty];
  526. if (firstName && lastName) {
  527. GTMABPersonCompositeNameFormat format;
  528. if (flags & kABFirstNameFirst) {
  529. format = kABPersonCompositeNameFormatFirstNameFirst;
  530. } else if (flags & kABLastNameFirst) {
  531. format = kABPersonCompositeNameFormatLastNameFirst;
  532. } else {
  533. format = [[self class] compositeNameFormat];
  534. }
  535. if (format == kABPersonCompositeNameFormatLastNameFirst) {
  536. NSString *tempStr = lastName;
  537. lastName = firstName;
  538. firstName = tempStr;
  539. }
  540. compositeName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
  541. } else if (firstName) {
  542. compositeName = firstName;
  543. } else if (lastName) {
  544. compositeName = lastName;
  545. } else {
  546. compositeName = @"";
  547. }
  548. }
  549. return compositeName;
  550. #endif // GTM_IPHONE_SDK
  551. }
  552. - (NSString *)description {
  553. #if GTM_IPHONE_SDK
  554. return [NSString stringWithFormat:@"%@ %@ %@ %d",
  555. [self class],
  556. [self valueForProperty:kGTMABPersonFirstNameProperty],
  557. [self valueForProperty:kGTMABPersonLastNameProperty],
  558. [self recordID]];
  559. #else // GTM_IPHONE_SDK
  560. return [NSString stringWithFormat:@"%@ %@ %@ %@",
  561. [self class],
  562. [self valueForProperty:kGTMABPersonFirstNameProperty],
  563. [self valueForProperty:kGTMABPersonLastNameProperty],
  564. [self recordID]];
  565. #endif // GTM_IPHONE_SDK
  566. }
  567. + (NSString *)localizedPropertyName:(GTMABPropertyID)property {
  568. #if GTM_IPHONE_SDK
  569. return GTMCFAutorelease(ABPersonCopyLocalizedPropertyName(property));
  570. #else // GTM_IPHONE_SDK
  571. return ABLocalizedPropertyOrLabel(property);
  572. #endif // GTM_IPHONE_SDK
  573. }
  574. + (GTMABPersonCompositeNameFormat)compositeNameFormat {
  575. #if GTM_IPHONE_SDK
  576. return ABPersonGetCompositeNameFormat();
  577. #else // GTM_IPHONE_SDK
  578. NSInteger nameOrdering
  579. = [[ABAddressBook sharedAddressBook] defaultNameOrdering];
  580. return nameOrdering == kABFirstNameFirst ?
  581. kABPersonCompositeNameFormatFirstNameFirst :
  582. kABPersonCompositeNameFormatLastNameFirst;
  583. #endif // GTM_IPHONE_SDK
  584. }
  585. + (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property {
  586. #if GTM_IPHONE_SDK
  587. return ABPersonGetTypeOfProperty(property);
  588. #else // GTM_IPHONE_SDK
  589. return ABTypeOfProperty([[GTMABAddressBook addressBook] addressBookRef],
  590. (CFStringRef)kABPersonRecordType,
  591. (CFStringRef)property);
  592. #endif // GTM_IPHONE_SDK
  593. }
  594. @end
  595. @implementation GTMABGroup
  596. + (GTMABGroup *)groupNamed:(NSString *)name {
  597. GTMABGroup *group = [[[self alloc] init] autorelease];
  598. if (group) {
  599. if (![group setValue:name forProperty:kABGroupNameProperty]) {
  600. // COV_NF_START
  601. // Can't get setValue to fail for me
  602. group = nil;
  603. // COV_NF_END
  604. }
  605. }
  606. return group;
  607. }
  608. - (id)init {
  609. ABRecordRef group = ABGroupCreate();
  610. self = [super initWithRecord:group];
  611. if (group) {
  612. CFRelease(group);
  613. }
  614. return self;
  615. }
  616. - (NSArray *)members {
  617. NSArray *people
  618. = GTMCFAutorelease(ABGroupCopyArrayOfAllMembers([self recordRef]));
  619. NSMutableArray *gtmPeople = [NSMutableArray arrayWithCapacity:[people count]];
  620. id person;
  621. GTM_FOREACH_OBJECT(person, people) {
  622. [gtmPeople addObject:[GTMABPerson recordWithRecord:(ABRecordRef)person]];
  623. }
  624. return gtmPeople;
  625. }
  626. - (BOOL)addMember:(GTMABPerson *)person {
  627. #if GTM_IPHONE_SDK
  628. CFErrorRef cfError = nil;
  629. // We check for person because of
  630. // Radar 6202860 Passing nil person into ABGroupAddMember crashes
  631. bool wasGood = person && ABGroupAddMember([self recordRef],
  632. [person recordRef], &cfError);
  633. if (cfError) {
  634. // COV_NF_START
  635. _GTMDevLog(@"Error in [%@ %@]: %@",
  636. [self class], NSStringFromSelector(_cmd), cfError);
  637. CFRelease(cfError);
  638. // COV_NF_END
  639. }
  640. #else // GTM_IPHONE_SDK
  641. bool wasGood = person && ABGroupAddMember([self recordRef],
  642. [person recordRef]);
  643. #endif // GTM_IPHONE_SDK
  644. return wasGood ? YES : NO;
  645. }
  646. - (BOOL)removeMember:(GTMABPerson *)person {
  647. #if GTM_IPHONE_SDK
  648. CFErrorRef cfError = nil;
  649. // We check for person because of
  650. // Radar 6202860 Passing nil person into ABGroupAddMember crashes
  651. // (I know this is remove, but it crashes there too)
  652. bool wasGood = person && ABGroupRemoveMember([self recordRef],
  653. [person recordRef], &cfError);
  654. if (cfError) {
  655. // COV_NF_START
  656. _GTMDevLog(@"Error in [%@ %@]: %@",
  657. [self class], NSStringFromSelector(_cmd), cfError);
  658. CFRelease(cfError);
  659. // COV_NF_END
  660. }
  661. #else // GTM_IPHONE_SDK
  662. bool wasGood = person != nil;
  663. if (wasGood) {
  664. NSArray *array = GTMCFAutorelease(ABPersonCopyParentGroups([person recordRef]));
  665. if ([array containsObject:[self recordRef]]) {
  666. wasGood = ABGroupRemoveMember([self recordRef],
  667. [person recordRef]);
  668. } else {
  669. wasGood = NO;
  670. }
  671. }
  672. #endif // GTM_IPHONE_SDK
  673. return wasGood ? YES : NO;
  674. }
  675. - (NSString *)compositeName {
  676. #if GTM_IPHONE_SDK
  677. return GTMCFAutorelease(ABRecordCopyCompositeName([self recordRef]));
  678. #else // GTM_IPHONE_SDK
  679. return [self valueForProperty:kGTMABGroupNameProperty];
  680. #endif // GTM_IPHONE_SDK
  681. }
  682. + (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property {
  683. GTMABPropertyType type = kGTMABInvalidPropertyType;
  684. if (property == kABGroupNameProperty) {
  685. type = kGTMABStringPropertyType;
  686. }
  687. return type;
  688. }
  689. + (NSString *)localizedPropertyName:(GTMABPropertyID)property {
  690. NSString *name = kGTMABUnknownPropertyName;
  691. if (property == kABGroupNameProperty) {
  692. name = NSLocalizedStringFromTable(@"Name",
  693. @"GTMABAddressBook",
  694. @"name property");
  695. }
  696. return name;
  697. }
  698. - (NSString *)description {
  699. #if GTM_IPHONE_SDK
  700. return [NSString stringWithFormat:@"%@ %@ %d",
  701. [self class],
  702. [self valueForProperty:kABGroupNameProperty],
  703. [self recordID]];
  704. #else // GTM_IPHONE_SDK
  705. return [NSString stringWithFormat:@"%@ %@ %@",
  706. [self class],
  707. [self valueForProperty:kABGroupNameProperty],
  708. [self recordID]];
  709. #endif // GTM_IPHONE_SDK
  710. }
  711. @end
  712. @implementation GTMABMultiValue
  713. - (id)init {
  714. // Call super init and release so we don't leak
  715. [[super init] autorelease];
  716. [self doesNotRecognizeSelector:_cmd];
  717. return nil; // COV_NF_LINE
  718. }
  719. - (id)initWithMultiValue:(ABMultiValueRef)multiValue {
  720. if ((self = [super init])) {
  721. if (!multiValue) {
  722. [self release];
  723. self = nil;
  724. } else {
  725. multiValue_ = CFRetain(multiValue);
  726. }
  727. }
  728. return self;
  729. }
  730. - (id)copyWithZone:(NSZone *)zone {
  731. return [[GTMABMultiValue alloc] initWithMultiValue:multiValue_];
  732. }
  733. - (id)mutableCopyWithZone:(NSZone *)zone {
  734. return [[GTMABMutableMultiValue alloc] initWithMultiValue:multiValue_];
  735. }
  736. - (NSUInteger)hash {
  737. // I'm implementing hash instead of using CFHash(multiValue_) because
  738. // 6203854 ABMultiValues hash to their address
  739. NSUInteger count = [self count];
  740. NSUInteger hash = 0;
  741. for (NSUInteger i = 0; i < count; ++i) {
  742. NSString *label = [self labelAtIndex:i];
  743. id value = [self valueAtIndex:i];
  744. hash += [label hash];
  745. hash += [value hash];
  746. }
  747. return hash;
  748. }
  749. - (BOOL)isEqual:(id)object {
  750. // I'm implementing isEqual instea of using CFEquals(multiValue,...) because
  751. // 6203854 ABMultiValues hash to their address
  752. // and it appears CFEquals just calls through to hash to compare them.
  753. BOOL isEqual = NO;
  754. if ([object respondsToSelector:@selector(multiValueRef)]) {
  755. isEqual = multiValue_ == [object multiValueRef];
  756. if (!isEqual) {
  757. NSUInteger count = [self count];
  758. NSUInteger objCount = [object count];
  759. isEqual = count == objCount;
  760. for (NSUInteger i = 0; isEqual && i < count; ++i) {
  761. NSString *label = [self labelAtIndex:i];
  762. NSString *objLabel = [object labelAtIndex:i];
  763. isEqual = [label isEqual:objLabel];
  764. if (isEqual) {
  765. id value = [self valueAtIndex:i];
  766. GTMABMultiValue *multiValueObject
  767. = GTM_STATIC_CAST(GTMABMultiValue, object);
  768. id objValue = [multiValueObject valueAtIndex:i];
  769. isEqual = [value isEqual:objValue];
  770. }
  771. }
  772. }
  773. }
  774. return isEqual;
  775. }
  776. - (void)dealloc {
  777. if (multiValue_) {
  778. CFRelease(multiValue_);
  779. }
  780. [super dealloc];
  781. }
  782. - (ABMultiValueRef)multiValueRef {
  783. return multiValue_;
  784. }
  785. - (NSUInteger)count {
  786. #if GTM_IPHONE_SDK
  787. return ABMultiValueGetCount(multiValue_);
  788. #else // GTM_IPHONE_SDK
  789. return ABMultiValueCount(multiValue_);
  790. #endif // GTM_IPHONE_SDK
  791. }
  792. - (id)valueAtIndex:(NSUInteger)idx {
  793. id value = nil;
  794. if (idx < [self count]) {
  795. value = GTMCFAutorelease(ABMultiValueCopyValueAtIndex(multiValue_, idx));
  796. ABPropertyType type = [self propertyType];
  797. if (type == kGTMABIntegerPropertyType
  798. || type == kGTMABRealPropertyType
  799. || type == kGTMABDictionaryPropertyType) {
  800. // This is because of
  801. // 6208390 Integer and real values don't work in ABMultiValueRefs
  802. // Apparently they forget to add a ref count on int, real and
  803. // dictionary values in ABMultiValueCopyValueAtIndex, although they do
  804. // remember them for all other types.
  805. // Once they fix this, this will lead to a leak, but I figure the leak
  806. // is better than the crash. Our unittests will test to make sure that
  807. // this is the case, and once we find a system that has this fixed, we
  808. // can conditionalize this code. Look for testRadar6208390 in
  809. // GTMABAddressBookTest.m
  810. // Also, search for 6208390 below and fix the fast enumerator to actually
  811. // be somewhat performant when this is fixed.
  812. #ifndef __clang_analyzer__
  813. [value retain];
  814. #endif // __clang_analyzer__
  815. }
  816. }
  817. return value;
  818. }
  819. - (NSString *)labelAtIndex:(NSUInteger)idx {
  820. NSString *label = nil;
  821. if (idx < [self count]) {
  822. label = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(multiValue_, idx));
  823. }
  824. return label;
  825. }
  826. - (GTMABMultiValueIdentifier)identifierAtIndex:(NSUInteger)idx {
  827. GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier;
  828. if (idx < [self count]) {
  829. #if GTM_IPHONE_SDK
  830. identifier = ABMultiValueGetIdentifierAtIndex(multiValue_, idx);
  831. #else // GTM_IPHONE_SDK
  832. identifier = GTMCFAutorelease(ABMultiValueCopyIdentifierAtIndex(multiValue_,
  833. idx));
  834. #endif // GTM_IPHONE_SDK
  835. }
  836. return identifier;
  837. }
  838. - (NSUInteger)indexForIdentifier:(GTMABMultiValueIdentifier)identifier {
  839. #if GTM_IPHONE_SDK
  840. NSUInteger idx = ABMultiValueGetIndexForIdentifier(multiValue_, identifier);
  841. #else // GTM_IPHONE_SDK
  842. NSUInteger idx = ABMultiValueIndexForIdentifier(multiValue_,
  843. (CFStringRef)identifier);
  844. #endif // GTM_IPHONE_SDK
  845. return idx == (NSUInteger)kCFNotFound ? (NSUInteger)NSNotFound : idx;
  846. }
  847. - (GTMABPropertyType)propertyType {
  848. #if GTM_IPHONE_SDK
  849. return ABMultiValueGetPropertyType(multiValue_);
  850. #else // GTM_IPHONE_SDK
  851. return ABMultiValuePropertyType(multiValue_);
  852. #endif // GTM_IPHONE_SDK
  853. }
  854. - (id)valueForIdentifier:(GTMABMultiValueIdentifier)identifier {
  855. return [self valueAtIndex:[self indexForIdentifier:identifier]];
  856. }
  857. - (NSString *)labelForIdentifier:(GTMABMultiValueIdentifier)identifier {
  858. return [self labelAtIndex:[self indexForIdentifier:identifier]];
  859. }
  860. - (unsigned long*)mutations {
  861. // We just need some constant non-zero value here so fast enumeration works.
  862. // Dereferencing self should give us the isa which will stay constant
  863. // over the enumeration.
  864. return (unsigned long*)self;
  865. }
  866. - (NSEnumerator *)valueEnumerator {
  867. return [GTMABMultiValueEnumerator valueEnumeratorFor:self];
  868. }
  869. - (NSEnumerator *)labelEnumerator {
  870. return [GTMABMultiValueEnumerator labelEnumeratorFor:self];
  871. }
  872. @end
  873. @implementation GTMABMutableMultiValue
  874. + (id)valueWithPropertyType:(GTMABPropertyType)type {
  875. return [[[self alloc] initWithPropertyType:type] autorelease];
  876. }
  877. - (id)initWithPropertyType:(GTMABPropertyType)type {
  878. ABMutableMultiValueRef ref = nil;
  879. if (type != kGTMABInvalidPropertyType) {
  880. #if GTM_IPHONE_SDK
  881. ref = ABMultiValueCreateMutable(type);
  882. #else // GTM_IPHONE_SDK
  883. ref = ABMultiValueCreateMutable();
  884. #endif // GTM_IPHONE_SDK
  885. }
  886. self = [super initWithMultiValue:ref];
  887. if (ref) {
  888. CFRelease(ref);
  889. }
  890. return self;
  891. }
  892. - (id)initWithMultiValue:(ABMultiValueRef)multiValue {
  893. ABMutableMultiValueRef ref = nil;
  894. if (multiValue) {
  895. ref = ABMultiValueCreateMutableCopy(multiValue);
  896. }
  897. self = [super initWithMultiValue:ref];
  898. if (ref) {
  899. CFRelease(ref);
  900. }
  901. return self;
  902. }
  903. - (id)initWithMutableMultiValue:(ABMutableMultiValueRef)multiValue {
  904. return [super initWithMultiValue:multiValue];
  905. }
  906. - (BOOL)checkValueType:(id)value {
  907. BOOL isGood = NO;
  908. if (value) {
  909. TypeClassNameMap singleValueTypeMap[] = {
  910. { kGTMABStringPropertyType, [NSString class] },
  911. { kGTMABIntegerPropertyType, [NSNumber class] },
  912. { kGTMABRealPropertyType, [NSNumber class] },
  913. { kGTMABDateTimePropertyType, [NSDate class] },
  914. { kGTMABDictionaryPropertyType, [NSDictionary class] },
  915. };
  916. GTMABPropertyType type = [self propertyType] & ~kABMultiValueMask;
  917. #if GTM_MACOS_SDK
  918. // Since on the desktop mutables don't have a type UNTIL they have
  919. // something in them, return YES if it's empty.
  920. if ((type == 0) && ([self count] == 0)) return YES;
  921. #endif // GTM_MACOS_SDK
  922. for (size_t i = 0;
  923. i < sizeof(singleValueTypeMap) / sizeof(TypeClassNameMap); ++i) {
  924. if (singleValueTypeMap[i].pType == type) {
  925. if ([[value class] isSubclassOfClass:singleValueTypeMap[i].class]) {
  926. isGood = YES;
  927. break;
  928. }
  929. }
  930. }
  931. }
  932. return isGood;
  933. }
  934. - (GTMABMultiValueIdentifier)addValue:(id)value withLabel:(CFStringRef)label {
  935. GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier;
  936. // We check label and value here because of
  937. // radar 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash
  938. bool wasGood = label && [self checkValueType:value];
  939. if (wasGood) {
  940. #if GTM_IPHONE_SDK
  941. wasGood = ABMultiValueAddValueAndLabel(multiValue_,
  942. value,
  943. label,
  944. &identifier);
  945. #else // GTM_IPHONE_SDK
  946. wasGood = ABMultiValueAdd((ABMutableMultiValueRef)multiValue_,
  947. value,
  948. label,
  949. (CFStringRef *)&identifier);
  950. #endif // GTM_IPHONE_SDK
  951. }
  952. if (!wasGood) {
  953. identifier = kGTMABMultiValueInvalidIdentifier;
  954. } else {
  955. mutations_++;
  956. }
  957. return identifier;
  958. }
  959. - (GTMABMultiValueIdentifier)insertValue:(id)value
  960. withLabel:(CFStringRef)label
  961. atIndex:(NSUInteger)idx {
  962. GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier;
  963. // We perform a check here to ensure that we don't get bitten by
  964. // Radar 6202807 ABMultiValueInsertValueAndLabelAtIndex allows you to insert
  965. // values past end
  966. NSUInteger count = [self count];
  967. // We check label and value here because of
  968. // radar 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash
  969. bool wasGood = idx <= count && label && [self checkValueType:value];
  970. if (wasGood) {
  971. #if GTM_IPHONE_SDK
  972. wasGood = ABMultiValueInsertValueAndLabelAtIndex(multiValue_,
  973. value,
  974. label,
  975. idx,
  976. &identifier);
  977. #else // GTM_IPHONE_SDK
  978. wasGood = ABMultiValueInsert((ABMutableMultiValueRef)multiValue_,
  979. value,
  980. label,
  981. idx,
  982. (CFStringRef *)&identifier);
  983. #endif // GTM_IPHONE_SDK
  984. }
  985. if (!wasGood) {
  986. identifier = kGTMABMultiValueInvalidIdentifier;
  987. } else {
  988. mutations_++;
  989. }
  990. return identifier;
  991. }
  992. - (BOOL)removeValueAndLabelAtIndex:(NSUInteger)idx {
  993. BOOL isGood = NO;
  994. NSUInteger count = [self count];
  995. if (idx < count) {
  996. #if GTM_IPHONE_SDK
  997. bool wasGood = ABMultiValueRemoveValueAndLabelAtIndex(multiValue_,
  998. idx);
  999. #else // GTM_IPHONE_SDK
  1000. bool wasGood = ABMultiValueRemove((ABMutableMultiValueRef)multiValue_,
  1001. idx);
  1002. #endif // GTM_IPHONE_SDK
  1003. if (wasGood) {
  1004. mutations_++;
  1005. isGood = YES;
  1006. }
  1007. }
  1008. return isGood;
  1009. }
  1010. - (BOOL)replaceValueAtIndex:(NSUInteger)idx withValue:(id)value {
  1011. BOOL isGood = NO;
  1012. NSUInteger count = [self count];
  1013. if (idx < count && [self checkValueType:value]) {
  1014. #if GTM_IPHONE_SDK
  1015. bool goodReplace = ABMultiValueReplaceValueAtIndex(multiValue_,
  1016. value, idx);
  1017. #else // GTM_IPHONE_SDK
  1018. bool goodReplace
  1019. = ABMultiValueReplaceValue((ABMutableMultiValueRef)multiValue_,
  1020. (CFTypeRef)value, idx);
  1021. #endif // GTM_IPHONE_SDK
  1022. if (goodReplace) {
  1023. mutations_++;
  1024. isGood = YES;
  1025. }
  1026. }
  1027. return isGood;
  1028. }
  1029. - (BOOL)replaceLabelAtIndex:(NSUInteger)idx withLabel:(CFStringRef)label {
  1030. BOOL isGood = NO;
  1031. NSUInteger count = [self count];
  1032. if (idx < count) {
  1033. #if GTM_IPHONE_SDK
  1034. bool goodReplace = ABMultiValueReplaceLabelAtIndex(multiValue_,
  1035. label, idx);
  1036. #else // GTM_IPHONE_SDK
  1037. bool goodReplace
  1038. = ABMultiValueReplaceLabel((ABMutableMultiValueRef)multiValue_,
  1039. (CFTypeRef)label, idx);
  1040. #endif // GTM_IPHONE_SDK
  1041. if (goodReplace) {
  1042. mutations_++;
  1043. isGood = YES;
  1044. }
  1045. }
  1046. return isGood;
  1047. }
  1048. - (unsigned long*)mutations {
  1049. return &mutations_;
  1050. }
  1051. @end
  1052. @implementation GTMABMultiValueEnumerator
  1053. + (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree {
  1054. return [[[self alloc] initWithEnumeree:enumeree useLabels:NO] autorelease];
  1055. }
  1056. + (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree {
  1057. return [[[self alloc] initWithEnumeree:enumeree useLabels:YES] autorelease];
  1058. }
  1059. - (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels {
  1060. if ((self = [super init])) {
  1061. if (enumeree) {
  1062. enumeree_ = [enumeree retain];
  1063. useLabels_ = useLabels;
  1064. } else {
  1065. // COV_NF_START
  1066. // Since this is a private class where the enumeree creates us
  1067. // there is no way we should ever get here.
  1068. [self release];
  1069. self = nil;
  1070. // COV_NF_END
  1071. }
  1072. }
  1073. return self;
  1074. }
  1075. - (void)dealloc {
  1076. [enumeree_ release];
  1077. [super dealloc];
  1078. }
  1079. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  1080. - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
  1081. objects:(id *)stackbuf
  1082. count:(NSUInteger)len {
  1083. NSUInteger i;
  1084. if (!ref_) {
  1085. count_ = [enumeree_ count];
  1086. ref_ = [enumeree_ multiValueRef];
  1087. }
  1088. for (i = 0; state->state < count_ && i < len; ++i, ++state->state) {
  1089. if (useLabels_) {
  1090. stackbuf[i] = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_,
  1091. state->state));
  1092. } else {
  1093. // TODO(dmaclach) Check this on Mac Desktop and use fast path if we can
  1094. // Yes this is slow, but necessary in light of radar 6208390
  1095. // Once this is fixed we can go to something similar to the label
  1096. // case which should speed stuff up again. Hopefully anybody who wants
  1097. // real performance is willing to move down to the C API anyways.
  1098. stackbuf[i] = [enumeree_ valueAtIndex:state->state];
  1099. }
  1100. }
  1101. state->itemsPtr = stackbuf;
  1102. state->mutationsPtr = [enumeree_ mutations];
  1103. return i;
  1104. }
  1105. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  1106. - (id)nextObject {
  1107. id value = nil;
  1108. if (!ref_) {
  1109. count_ = [enumeree_ count];
  1110. mutations_ = *[enumeree_ mutations];
  1111. ref_ = [enumeree_ multiValueRef];
  1112. }
  1113. if (mutations_ != *[enumeree_ mutations]) {
  1114. NSString *reason = [NSString stringWithFormat:@"*** Collection <%@> was "
  1115. "mutated while being enumerated", enumeree_];
  1116. [[NSException exceptionWithName:NSGenericException
  1117. reason:reason
  1118. userInfo:nil] raise];
  1119. }
  1120. if (index_ < count_) {
  1121. if (useLabels_) {
  1122. value = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_,
  1123. index_));
  1124. } else {
  1125. // TODO(dmaclach) Check this on Mac Desktop and use fast path if we can
  1126. // Yes this is slow, but necessary in light of radar 6208390
  1127. // Once this is fixed we can go to something similar to the label
  1128. // case which should speed stuff up again. Hopefully anybody who wants
  1129. // real performance is willing to move down to the C API anyways.
  1130. value = [enumeree_ valueAtIndex:index_];
  1131. }
  1132. index_ += 1;
  1133. }
  1134. return value;
  1135. }
  1136. @end