Подтвердить что ты не робот

Как правильно использовать метод ABAddressBookCreateWithOptions в iOS 6?

Я пытаюсь понять методы ABAdressBookCreateWithOptions и ABAddressBookRequestAccessWithCompletion в iOS 6.

Наибольшей информацией, которую я смог найти, является следующее: "Чтобы запросить доступ к контактным данным, вызовите функцию ABAddressBookRequestAccessWithCompletion после вызова функции ABAddressBookCreateWithOptions".

Я считаю, что вместе эти методы должны предупреждать пользователя о том, разрешать ли приложение доступ к контактам, однако, когда я их использую, я не вижу подсказки.

Может ли кто-нибудь предоставить примерный код того, как эти методы должны быть вызваны вместе в реальном мире? Как создать (CFDictionary) параметры? У меня есть рабочий код с использованием устаревшего метода ABAddressBookCreate, но вам нужно обновить iOS 6, чтобы учесть проблемы конфиденциальности.

Спасибо заранее всем, кто может пролить свет здесь!

4b9b3361

Ответ 1

Теперь, когда NDA снят, вот мое решение для этого, где вам нужно заменить метод, который возвращает массив. (Если вы предпочитаете не блокировать пока пользователь решает и готовы переписать часть вашего существующего кода, посмотрите ниже решение David):

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...

}

Надеюсь, это поможет кому-то...

Ответ 2

Большинство ответов, которые я видел в этом вопросе, делают сумасшедшие сложные вещи с помощью GCD и заканчивают блокирование основного потока. Это не нужно!

Здесь решение, которое я использовал (работает на iOS 5 и 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);
}

Ответ 3

В другом высокопоставленном ответе есть проблемы:

  • он безоговорочно вызывает API, которого нет в iOS старше 6, поэтому ваша программа будет аварийно завершена на старых устройствах.
  • он блокирует основной поток, поэтому ваше приложение не отвечает и не делает успеваемости, в то время, когда системное предупреждение срабатывает.

Вот мой MRC:

        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];
          }
        }
      }

Ответ 4

Это периферийно связано с исходным вопросом, но я не видел его упоминания нигде, и мне потребовалось около двух дней, чтобы понять это. Если вы зарегистрируете обратный вызов для изменения адресной книги, он ДОЛЖЕН находиться в основном потоке.

Например, в этом коде будет вызываться только sync_address_book_two():

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);
        });
    }
});