Как правильно использовать метод ABAddressBookCreateWithOptions в iOS 6?
Я пытаюсь понять методы ABAdressBookCreateWithOptions
и ABAddressBookRequestAccessWithCompletion
в iOS 6.
Наибольшей информацией, которую я смог найти, является следующее: "Чтобы запросить доступ к контактным данным, вызовите функцию ABAddressBookRequestAccessWithCompletion
после вызова функции ABAddressBookCreateWithOptions
".
Я считаю, что вместе эти методы должны предупреждать пользователя о том, разрешать ли приложение доступ к контактам, однако, когда я их использую, я не вижу подсказки.
Может ли кто-нибудь предоставить примерный код того, как эти методы должны быть вызваны вместе в реальном мире? Как создать (CFDictionary
) параметры? У меня есть рабочий код с использованием устаревшего метода ABAddressBookCreate
, но вам нужно обновить iOS 6, чтобы учесть проблемы конфиденциальности.
Спасибо заранее всем, кто может пролить свет здесь!
Ответы
Ответ 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);
});
}
});