iOS通讯录读取 -- AddressBook framework&Contacts Framework 使用总结

前言

在平时开发中,或许会需要去读取通讯录的数据,在iOS9之前我们使用的是AddressBook framework,而iOS9 苹果开始使用新的Contacts framework代替AddressBook framework。我们开发时经常需要既要支持iOS9,又要去支持iOS9之前的版本。所以我们需要了解这两个Framework的区别。

读取通讯录流程

  1. 校验是否有读取通讯录的权限
  2. 没有权限则向用户申请权限
  3. 得到权限则读取通讯录信息

权限校验

AddressBook framework

AddressBook framework 为我们提供了ABAddressBookGetAuthorizationStatus方法获取你的app授权状态。此方法返回ABAuthorizationStatus

1
2
3
4
5
6
enum ABAuthorizationStatus : CFIndex {
case NotDetermined //未授权
case Restricted //受限制
case Denied //用户拒绝
case Authorized //经授权
}

Contacts Framework

Contacts Framework 为我们通过了authorizationStatusForEntityType方法获取你的app授权状态。次方法返回CNAuthorizationStatus

1
2
3
4
5
6
enum CNAuthorizationStatus : Int {
case NotDetermined //未授权
case Restricted //受限制
case Denied //用户拒绝
case Authorized //经授权
}

示例

AddressBook framework

1
2
3
4
5
6
7
8
9
10
11
12
switch ABAddressBookGetAuthorizationStatus() {
case .Denied, .Restricted:
print("用户拒绝或受限制直接返回")
case .NotDetermined:
print("未授权,请求授权")
case .Authorized:
print("经授权,访问通讯录")
}

Contacts Framework

1
2
3
4
5
6
7
8
9
10
11
switch CNContactStore.authorizationStatusForEntityType(.Contacts) {
case .Denied, .Restricted:
print("用户拒绝或受限制直接返回")
case .NotDetermined:
print("未授权,请求授权")
case .Authorized:
print("经授权,访问通讯录")
}

从例子看使用形式基本一样,就是函数有稍微的变更。

请求授权

AddressBook framework

AddressBook framework 为我们提供了ABAddressBookRequestAccessWithCompletion方法进行授权请求。此方法通过闭包completion返回授权结果。

Contacts Framework

Contacts Framework 为我们通过了requestAccessForEntityType方法进行授权请求。此方法通过闭包completionHandler返回授权结果。

示例

AddressBookUI

1
2
3
4
5
6
7
8
9
10
11
12
let contact = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
ABAddressBookRequestAccessWithCompletion(contact) { (granted, error) in
guard granted else {
print("用户拒绝授权,直接返回")
return
}
print("用户允许访问,访问通讯录")
}

Contacts Framework

1
2
3
4
5
6
7
8
9
10
11
12
let contact = CNContactStore()
contact.requestAccessForEntityType(.Contacts) { (granted, error) in
guard granted else {
print("用户拒绝授权,直接返回")
return
}
print("用户允许访问,访问通讯录")
}

读取通讯录信息

AddressBook framework

AddressBook framework 为我们提供了ABAddressBookCopyArrayOfAllPeople方法获取通信录所有人的记录。然后我们可以通过ABRecordCopyValue读取记录中属性。

Contacts Framework

Contacts Framework 为我们通过了enumerateContactsWithFetchRequest方法获取通信录的联系人信息。我们需要同CNContactFetchRequest指定获取联系人信息的属性。

ABAddressBookCopyArrayOfAllPeopleenumerateContactsWithFetchRequest有着明显的区别:

  1. 返回值,前者调用后直接通过数组的形式返回所有联系人的信息;后者则返回成功或失败,联系人信息则通过usingBlock闭包逐个返回。
  2. 参数,前者需要接收一个ABAddressBook对象;后者则是CNContactStore对象中的一个成员函数,但他需要接收一个CNContactFetchRequest对象,告诉它我们需要获取哪些属性信息。

示例

AddressBook framework

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let peoples = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() as NSArray
for people: ABRecordRef in peoples {
var firstName = ""
var lastName = ""
if let firstNameUnmanaged = ABRecordCopyValue(people, kABPersonLastNameProperty) {
firstName = firstNameUnmanaged.takeRetainedValue() as? String ?? ""
}
if let lastNameUnmanaged = ABRecordCopyValue(people, kABPersonFirstNameProperty) {
lastName = lastNameUnmanaged.takeRetainedValue() as? String ?? ""
}
let phoneNums: ABMultiValueRef = ABRecordCopyValue(people, kABPersonPhoneProperty).takeRetainedValue()
guard let phoneNumUnmanaged = ABMultiValueCopyValueAtIndex(phoneNums, 0) else {
continue
}
var phoneNum = phoneNumUnmanaged.takeRetainedValue() as? String ?? ""
}

我们先看看ABAddressBookCopyArrayOfAllPeople这个函数的定义。

在swift中这个函数居然返回个Unmanaged<CFArray>!!!!瞬间蒙圈了。这要怎么用!!

首先我们先要了解什么是Unmanaged

在apple文档中的定义是:

A type for propagating an unmanaged object reference.
When you use this type, you become partially responsible for keeping the object alive.

还是不懂,不是有ARC吗?于是我Google。。。

原来在有了ARC后,所有的 Objective-C 和 从 Objective-C 方法返回的 Core Foundation 类型的内存都被自动管理,只剩下由 C 函数返回的 Core Foundation 类型还没有收编。对于后者而言,对象所有权的管理仍然停留在调用 CFRetain() 和 CFRelease()、或通过某个 __bridge 函数桥接到 Objective-C 对象的方式的层面上。

但是swift只支持ARC,所以也没有地方调用 CFRelease 或 __bridge_retained。那么 Swift 是如何让这种 “在上下文中内存管理” 的哲学融入自己的内存安全体系呢?

事情分两种情况。注明 的 API,Swift 能够在上下文中严格遵循注释描述对 CoreFoundation API 进行内存管理,并以同样内存安全的方式桥接到 Objective-C 或 Swift 类型上。对于没有明确注明的 API,Swift 则会通过 Unmanaged 类型把工作交给开发者。

从一个 Unmanaged 实例中获取一个 Swift 值的方法有两种:

  • takeRetainedValue():返回该实例中 Swift 管理的引用,并在调用的同时减少一次引用次数,所以可以按照 Create 规则来对待其返回值。
  • takeUnretainedValue():返回该实例中 Swift 管理的引用而 不减少 引用次数,所以可以按照 Get 规则来对待其返回值。

好了,对Unmanaged了解这么多久就足够了。对这一块有兴趣的同学可以看下面链接的这篇文章:
http://nshipster.cn/unmanaged

了解Unmanaged是什么后,接下来就简单了:

  • 我们通过 for in 的方式得到 ABRecordRef对象
  • 再通过ABRecordCopyValue传入对应的ABPropertyID获取对应的属性值。

Contacts Framework

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactPhoneNumbersKey]
try! addressBook.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keysToFetch)) {(contact, pointer) in
let phone = contact.phoneNumbers.first
guard let phoneNumber = phone?.value as? CNPhoneNumber else {
return
}
var phoneString = phoneNumber.stringValue
var firstName = contact.familyName
var lastName = contact.givenName
}

iOS9 之后一切都变得简单的了,我们也看看enumerateContactsWithFetchRequest的接口说明吧。

接口说明并没有特别难懂的地方,我们通过闭包返回的contact就能轻松的获取联系人的信息。

调用的系统通讯录UI获取联系人信息

有的需求场景是需要我们在这两个不同的framework上又有什么区别呢?

iOS9之前我们用的是 AddressBookUI, iOS9 apple 提供了 ContactsUI来替代。

调用系统通信录UI需要:

  1. 让系统通讯录UI给显示出来
  2. 但我们选择联系人后获取联系人的信息

显示 UI

AddressBookUI

我们先看看 AddressBookUI 上需要怎么做。先看Address Book UI Framework的定义。

ABPeoplePickerNavigationController 是我想要的。我们只需要吧它给presentViewController出来就好了。

ContactsUI

我们再看看 ContactsUI Framework的定义。

CNContactPickerViewController 是我想要的。presentViewController它就好了

看起来很简单嘛#^_^#

获取联系人信息

AddressBookUI

AddressBookUI 为我们提供了ABPeoplePickerNavigationControllerDelegate的代理

1
func peoplePickerNavigationController(_ peoplePicker: ABPeoplePickerNavigationController, didSelectPerson person: ABRecord)

此方法为我们返回选择的联系人信息。

ContactsUI

ContactsUI 为我们提供了CNContactPickerDelegate的代理

1
func contactPicker(_ picker: CNContactPickerViewController, didSelectContact contact: CNContact)

此方法为我们返回选择的联系人信息。

AddressBookUI 和 ContactsUI 使用方法看起来基本一样,挺简单的Y^o^Y

总结

不知道apple为什么决定的使用Contacts 替换 AddressBook。本人猜测估计和Unmanaged在swift中并不那么友好的使用有关吧。但在我们日常开发App是必须考虑前向兼容,所以我们了解这两个framework的使用方法和差异。

为了平时使用方便于是我对这两个framework做了简单的封装统一了接口。SCAddressBook,目前这个库只是为了满足自己项目的使用场景,并没有对AddressBook和Contacts的所以功能进行封装。希望这个库对学习AddressBook和Contacts有所帮助。