在开发的时候我们经常会用到调用系统的通讯录, 直接写代理很容易实现, 网上也很多例子, 例如:
iOS调用系统通讯录(适配iOS9、iOS10)(转载)
如果只是一个地方用到还好, 直接在controller里面签代理.
但是遇到很多地方调用, 每次都签代理是不是感觉很麻烦, 这时候没有有想过封装出来一个工具类, 然后在用的时候直接调用就可以了, 比如一个block回调 (swift叫闭包).
本次封装的主要目的是把每个用到的地方都变成一个block, 直接回调, 方便以后别的地方用.
现在问题来了:
问题1: 通讯录的vc需要当前的vc 给present出来, 需要把当前的vc(context)传到工具类里
问题2: 代理签到传进来的vc里还是工具类里, 签到传进来的vc没法代理了, 就想办法签到工具类里
所以大体思路是 写一个类 初始化的时候把vc和另一个参数回调的block都传进来, 然后写一个方法调用通讯录, 把代理签到工具类上,在代理执行的时候返回block
那么开始写代码, 我的工具类是ContactTool 直接考虑iOS9.0以上的 导入头文件
import ContactsUI
回调的block
typealias ContactPickBlock = ( _ info: ContactInfo?) -> Void
定一个属性保留传进来的vc
private var context:UIViewController
private var callBack:ContactPickBlock?
初始化的时候传递或者属性传也可以
init(context:UIViewController) { self.context = context }
///调用联系人
publicfuncjudgeAddressBookPower(didPcik:@escaping ContactPickBlock) {
//检查权限
checkAddressBookAuthorization{ [weakself] (isAuthorized)in
guardlet`self` =selfelse{return}
ifisAuthorized ==true{
self.callAddressBook()
}else{
self.showMsg(msg:"请到设置>隐私>通讯录打开本应用的权限设置")
}
}
self.callBack= didPcik
}
检查权限
///获取通讯录权限
privatefunccheckAddressBookAuthorization(handler:@escaping((_:Bool) ->Void)) {
let contactStore =CNContactStore()
if CNContactStore.authorizationStatus(for: .contacts) == .notDetermined {
contactStore.requestAccess(for: .contacts, completionHandler: { (granted, error)in
iferror !=nil{
print(errorasAny)
self.showMsg(msg: error.debugDescription)
}elseifgranted ==false{
handler(false)
}else{
handler(true)
}
})
}else if CNContactStore.authorizationStatus(for: .contacts) == .authorized {
handler(true)
}else{
handler(false)
}
}
调用通讯录
///调用通讯录
privatefunccallAddressBook() {
let contactPicker = CNContactPickerViewController()
contactPicker.delegate=self
contactPicker.displayedPropertyKeys = [CNContactPhoneNumbersKey]
context.present(contactPicker, animated:true, completion:nil)
}
提示信息的Alert
///显示信息
privatefuncshowMsg(msg:String) {
letalert =UIAlertController(title:"没有权限", message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title:"知道了", style: .cancel, handler:nil))
context.present(alert, animated:true)
}
接下来实现代理
///代理
extension ContactTool : CNContactPickerDelegate {
//取消了
funccontactPickerDidCancel(_picker:CNContactPickerViewController) {
self.filtration(nil)
}
//选择完联系人
funccontactPicker(_picker:CNContactPickerViewController, didSelect contactProperty:CNContactProperty) {
letphoneNumber = contactProperty.valueas!CNPhoneNumber
context.dismiss(animated:true) {
// 联系人
letname = contactProperty.contact.familyName+ contactProperty.contact.givenName
// 电话
letphone = phoneNumber.stringValue
self.filtration(ContactInfo(name: name, phone: phone))
}
}
}
filtration 这个方法里就是回调的
self.callBack?(info);
本以为这样就结束了, 新的问题出现, 当你调用通讯录的时候, 会发现代理根本没有走, 是因为在CNContactPickerViewController出现的时候, ContactTool 就被释放了, 这个怎么办...
那怎么才能让ContactTool 不被释放呢, 必须得有个地方引用他, 用传过来的vc引用他还得写个属性, 思来想去想出一个比较奇葩的放法, 我们经常会提到避免循环引用, 循环引用会造成对象无法释放, 影响内存释放, 那正是我们想到的结果, 不让ContactTool释放, 我们为何不制造一个循环引用呢, 然后在使用之后打开循环释放.
创建一个类让他持有工具类的对象ContactTool
class Abbot: NSObject {
let monk:ContactTool
init(monk:ContactTool) {
self.monk = monk
}
}
在judgeAddressBookPower方法中添加如下代码:
///调用联系人
publicfuncjudgeAddressBookPower(didPcik:@escapingContactPickBlock) {
//检查权限
checkAddressBookAuthorization{ [weakself] (isAuthorized)in
guardlet`self` =selfelse{return}
ifisAuthorized ==true{
self.callAddressBook()
}else{
self.showMsg(msg:"请到设置>隐私>通讯录打开本应用的权限设置")
}
}
self.callBack= didPcik
//制造循环引用
letabbot_p =Abbot(monk:self)
self.abbot= abbot_p
}
然后在filtration里打开循环引用
///过滤处理
private func filtration(_info:ContactInfo?) {
self.callBack?(info);
//释放
DispatchQueue.main.asyncAfter(deadline:DispatchTime.now()+0.1) {
self.abbot=nil
}
}
这些就是所有逻辑, 虽然只是一个简单的封装, 但通过制造循环, 然后再释放, 貌似让我看到所有的代理都能通过这种方式封装成block, 也是一个通用的思想. 请各位大神指点
代码:ContactTest