В своем исходном ответе в конце этого ответа я показываю, как получить контакты в версиях iOS до 9.0 таким образом, чтобы решить некоторые из проблем, связанных с другими ответами здесь.
Но,если поддерживается только iOS 9 и более поздние версии, следует использовать инфраструктуру Contacts
, избегая некоторых раздражающих проблем с мостами, возникающих при использовании старой платформы AddressBook
.
Итак, в iOS 9 вы должны использоватьContacts
framework:
@import Contacts;
Вам также необходимо обновить Info.plist
, добавив NSContactsUsageDescription
, чтобы объяснить, почему вашему приложению требуется доступ к контактам.
А затем сделать что-тонапример:
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Access to contacts." message:@"This app requires access to contacts because ..." preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"Go to Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:TRUE completion:nil];
return;
}
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
// make sure the user granted us access
if (!granted) {
dispatch_async(dispatch_get_main_queue(), ^{
// user didn't grant access;
// so, again, tell user here why app needs permissions in order to do it's job;
// this is dispatched to the main queue because this request could be running on background thread
});
return;
}
// build array of contacts
NSMutableArray *contacts = [NSMutableArray array];
NSError *fetchError;
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactIdentifierKey, [CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName]]];
BOOL success = [store enumerateContactsWithFetchRequest:request error:&fetchError usingBlock:^(CNContact *contact, BOOL *stop) {
[contacts addObject:contact];
}];
if (!success) {
NSLog(@"error = %@", fetchError);
}
// you can now do something with the list of contacts, for example, to show the names
CNContactFormatter *formatter = [[CNContactFormatter alloc] init];
for (CNContact *contact in contacts) {
NSString *string = [formatter stringFromContact:contact];
NSLog(@"contact = %@", string);
}
}];
Ниже приведен мой ответ, применимый при поддержке версий iOS до iOS 9.0.
-
Пара реакций не только на ваш вопрос,но также многие ответы, представленные здесь (которые либо не запрашивают разрешение, либо неправильно обрабатывают ABAddressBookCreateWithOptions
ошибки, либо вызывают утечку):
Очевидно, импортируйте структуру AddressBook
:
#import <AddressBook/AddressBook.h>
или
@import AddressBook;
Вы должны запросить разрешение для доступа приложения к контактам.Например:
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
if (status == kABAuthorizationStatusDenied || status == kABAuthorizationStatusRestricted) {
// if you got here, user had previously denied/revoked permission for your
// app to access the contacts and all you can do is handle this gracefully,
// perhaps telling the user that they have to go to settings to grant access
// to contacts
[[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
return;
}
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
if (!addressBook) {
NSLog(@"ABAddressBookCreateWithOptions error: %@", CFBridgingRelease(error));
return;
}
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
if (error) {
NSLog(@"ABAddressBookRequestAccessWithCompletion error: %@", CFBridgingRelease(error));
}
if (granted) {
// if they gave you permission, then just carry on
[self listPeopleInAddressBook:addressBook];
} else {
// however, if they didn't give you permission, handle it gracefully, for example...
dispatch_async(dispatch_get_main_queue(), ^{
// BTW, this is not on the main thread, so dispatch UI updates back to the main queue
[[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
});
}
CFRelease(addressBook);
});
Обратите внимание, что выше я не использовал шаблон, предложенный другими:
CFErrorRef *error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, error);
Это не правильно.Как вы увидите выше, вы хотите:
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
Первый шаблон не будет правильно фиксировать ошибку, тогда как последний будет.Если error
не было NULL
, не забудьте CFRelease
его (или передать право собственности ARC, как я), иначе вы утечете этот объект.
Чтобы перебрать контакты, вы хотите:
- (void)listPeopleInAddressBook:(ABAddressBookRef)addressBook
{
NSArray *allPeople = CFBridgingRelease(ABAddressBookCopyArrayOfAllPeople(addressBook));
NSInteger numberOfPeople = [allPeople count];
for (NSInteger i = 0; i < numberOfPeople; i++) {
ABRecordRef person = (__bridge ABRecordRef)allPeople[i];
NSString *firstName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
NSString *lastName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonLastNameProperty));
NSLog(@"Name:%@ %@", firstName, lastName);
ABMultiValueRef phoneNumbers = ABRecordCopyValue(person, kABPersonPhoneProperty);
CFIndex numberOfPhoneNumbers = ABMultiValueGetCount(phoneNumbers);
for (CFIndex j = 0; j < numberOfPhoneNumbers; j++) {
NSString *phoneNumber = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phoneNumbers, j));
NSLog(@" phone:%@", phoneNumber);
}
CFRelease(phoneNumbers);
NSLog(@"=============================================");
}
}
Я хочу обратить ваше внимание на довольно важную деталь, а именно «Правило создания» :
Функции Core Foundation имеют имена, которые указывают, когда вы владеете возвращаемым объектом:
Функции создания объектов, в которые встроено «Create
» в имени;
Функции дублирования объектов, в имени которых встроено «Copy
».
Если вы владеете объектом, это вашответственность за отказ от владения (используя CFRelease), когда вы закончили с ним.
Это означает, что вы несете ответственность за освобождение любого объекта, возвращенного любой функцией Core Foundation с Create
или Copy
вназвание.Вы можете либо вызвать CFRelease
явно (как я делал выше с addressBook
и phoneNumbers
), либо, для объектов, которые поддерживают бесплатное мостовое соединение, вы можете передать владение ARC с помощью __bridge_transfer
или CFBridgingRelease
(как яделал выше с allPeople
, lastName
, firstName
и phoneNumber
).
Статический анализатор (нажмите shift + , команду + B в Xcode или выберите «Анализ» в меню «Продукт») может определитьво многих ситуациях, когда вы пренебрегли этим «правилом создания» и не смогли выпустить соответствующие объекты.Поэтому при написании кода Core Foundation, подобного этому, всегда запускайте его через статический анализатор, чтобы убедиться в отсутствии явных утечек.