How do I correctly use ABAddressBookCreateWithOptions method in iOS 6?

余生颓废 提交于 2019-11-27 17:37:02
Engin Kurutepe

Now that the NDA has been lifted, here is my solution for this for the where you need replace a method which returns an Array. (If you'd rather not block while the user is deciding and are ready to potentially rewrite some of your existing code, please look at David's solution below):

ABAddressBookRef addressBook = ABAddressBookCreate();

__block BOOL accessGranted = NO;

if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
        accessGranted = granted;
        dispatch_semaphore_signal(sema);
    });

    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_release(sema);    
}
else { // we're on iOS 5 or older
    accessGranted = YES;
}


if (accessGranted) {

    NSArray *thePeople = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
    // Do whatever you need with thePeople...

}

Hope this helps somebody...

Most answers I've seen to this question do crazy complicated things with GCD and end up blocking the main thread. It's not necessary!

Here's the solution I've been using (works on iOS 5 and iOS 6):

- (void)fetchContacts:(void (^)(NSArray *contacts))success failure:(void (^)(NSError *error))failure {
  if (ABAddressBookRequestAccessWithCompletion) {
    // on iOS 6

    CFErrorRef err;
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &err);

    if (err) {
      // handle error
      CFRelease(err);
      return;
    }

    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
      // ABAddressBook doesn't gaurantee execution of this block on main thread, but we want our callbacks to be
      dispatch_async(dispatch_get_main_queue(), ^{
        if (!granted) {
          failure((__bridge NSError *)error);
        } else {
          readAddressBookContacts(addressBook, success);
        }
        CFRelease(addressBook);
      });
    });
  } else {
    // on iOS < 6

    ABAddressBookRef addressBook = ABAddressBookCreate();
    readAddressBookContacts(addressBook, success);
    CFRelease(addressBook);
  }
}

static void readAddressBookContacts(ABAddressBookRef addressBook, void (^completion)(NSArray *contacts)) {
  // do stuff with addressBook
  NSArray *contacts = @[];

  completion(contacts);
}

The other high ranking answer has problems:

  • it unconditionally calls API that don't exist in iOS older than 6, so your program will crash on old devices.
  • it blocks the main thread, so your app is unresponsive, and not making progress, during the time the system alert s up.

Here's my MRC take on it:

        ABAddressBookRef ab = NULL;
        // ABAddressBookCreateWithOptions is iOS 6 and up.
        if (&ABAddressBookCreateWithOptions) {
          NSError *error = nil;
          ab = ABAddressBookCreateWithOptions(NULL, (CFErrorRef *)&error);
    #if DEBUG
          if (error) { NSLog(@"%@", error); }
    #endif
          if (error) { CFRelease((CFErrorRef *) error); error = nil; }
        }
        if (ab == NULL) {
          ab = ABAddressBookCreate();
        }
        if (ab) {
          // ABAddressBookRequestAccessWithCompletion is iOS 6 and up.
          if (&ABAddressBookRequestAccessWithCompletion) {
            ABAddressBookRequestAccessWithCompletion(ab,
               ^(bool granted, CFErrorRef error) {
                 if (granted) {
                   // constructInThread: will CFRelease ab.
                   [NSThread detachNewThreadSelector:@selector(constructInThread:)
                                            toTarget:self
                                          withObject:ab];
                 } else {
                   CFRelease(ab);
                   // Ignore the error
                 }
                 // CFErrorRef should be owned by caller, so don't Release it.
               });
          } else {
            // constructInThread: will CFRelease ab.
            [NSThread detachNewThreadSelector:@selector(constructInThread:)
                                     toTarget:self
                                   withObject:ab];
          }
        }
      }

This is peripherally related to the original question, but I have not seen it mentioned anywhere else, and it took me about two days to figure it out. If you register a callback for address book changes, it MUST be on the main thread.

For example, in this code, only sync_address_book_two() will ever be called:

ABAddressBookRequestAccessWithCompletion(_addressBook, ^(bool granted, CFErrorRef error) {
    if (granted) {
        ABAddressBookRegisterExternalChangeCallback (_addressBook, sync_address_book_one, NULL);
        dispatch_async(dispatch_get_main_queue(), ^{
            ABAddressBookRegisterExternalChangeCallback (_addressBook, sync_address_book_two, NULL);
        });
    }
});
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!