美文网首页iOS 知识收集
iOS 通讯录获取使用

iOS 通讯录获取使用

作者: 小和大大 | 来源:发表于2021-04-11 13:28 被阅读0次

    通讯录简介

    image

    通讯录使用场景:

    1. 电商类的 App,设置收货人电话号码。
    2. 即时通讯类 App,添加手机联系人好友。

    通讯录获取方案:

    一、iOS 9 以前的通讯录框架

    1. AddressBookUI.framework 框架

      1. 提供了联系人列表界面、联系人详情界面、添加联系人界面等。
      2. 一般用于选择联系人。
    2. AddressBook.framework 框架

      1. 纯 C 语言的 API,仅仅是获得联系人数据。
      2. 没有提供 UI 界面展示,需要自己搭建联系人展示界面。
      3. 里面的数据类型大部分基于 Core Foundation 框架,使用起来炒鸡复杂。

    二、 iOS 9 以后最新通讯录框架

    1. ContactsUI.framework 框架。

      • 拥有 AddressBookUI.framework 框架的所有功能,使用起来更加的面向对象。
    2. Contacts.framework 框架。

      • 拥有 AddressBook.framework 框架的所有功能,不再是 C 语言的 API,使用起来非常简单。

    iOS 9 以前的通讯录框架

    AddressBookUI

    实现步骤

    一、创建选择联系人的控制器

    // 创建联系人选择控制器    
    ABPeoplePickerNavigationController *pvc = [[ABPeoplePickerNavigationController alloc] init];
    
    

    二、设置代理(用来接收用户选择的联系人信息)

    // 设置代理
    pvc.peoplePickerDelegate = self;
    
    

    三、弹出联系人控制器

    if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
    {
       ABAddressBookRef bookRef = ABAddressBookCreate();
       ABAddressBookRequestAccessWithCompletion(bookRef, ^(bool granted, CFErrorRef error) {
           if (granted)
           {
               NSLog(@"授权成功!");
               [self presentViewController:pvc animated:YES completion:nil];
           }
           else
           {
               NSLog(@"授权失败!");
           }
       });
    }
    else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
    {
       [self presentViewController:pvc animated:YES completion:nil];
    }
    
    

    四、实现代理方法

    // 选择某个联系人时调用
    - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person
    {
        NSLog(@"选中联系人");
        CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
        CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
    
        NSString *fir = CFBridgingRelease(firstName);
        NSString *las = CFBridgingRelease(lastName);
    
        NSLog(@"%@---%@", fir, las);
    
        ABMultiValueRef multi = ABRecordCopyValue(person, kABPersonPhoneProperty);
        CFIndex count = ABMultiValueGetCount(multi);
        for (int i = 0; i  < count; i++) 
        {
            NSString *label = (__bridge_transfer NSString *)ABMultiValueCopyLabelAtIndex(multi, i);
            NSString *phone =(__bridge_transfer NSString *)  ABMultiValueCopyValueAtIndex(multi, i);
            NSLog(@"%@---%@", label, phone);
        }
    }
    
    

    Core Foundation 对象手动管理内存,如果是 Create、Copy、Retain 等字样创建的对象,需要手动 CFRelease。类似 Objective-C 的 MRC。

    拓展:__bridge__bridge_retained__bridge_transfer 三个转换关键字的区别。

    1. __bridge 只做类型转换,但是不修改对象(内存)管理权;
    2. __bridge_retained(也可以使用CFBridgingRetain)将 Objective-C 的对象转换为 Core Foundation 的对象,同时将对象(内存)的管理权交给我们,后续需要使用 CFRelease 或者相关方法来释放对象;
    3. __bridge_transfer(也可以使用 CFBridgingRelease )将 Core Foundation 的对象转换为 Objective-C 的对象,同时将对象(内存)的管理权交给 ARC。

    五、在对应的代理方法中获取联系人信息

    // 1.选择联系人时使用(不展开详情)
    - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;
    
    // 2.选择联系人某个属性时调用(展开详情)
    - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier;
    
    // 3.取消选中联系人时调用
    - (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;
    
    

    注意:选择联系人的不展开详情(代理方法1)和展开详情(代理方法2)的代理方法都写了的时候,展开详情的代理方法就不执行。

    AddressBook

    一、请求授权

    从 iOS 6 开始,需要得到用户的授权才能访问通讯录,因此在使用之前,需要检查用户是否已经授权。

    // 获得通讯录的授权状态
    ABAddressBookGetAuthorizationStatus()
    
    

    授权状态

    1. 用户还没有决定是否授权你的程序进行访问:kABAuthorizationStatusNotDetermined

    2. iOS 设备上一些许可配置阻止程序与通讯录数据库进行交互:kABAuthorizationStatusRestricted

    3. 用户明确的拒绝了你的程序对通讯录的访问:kABAuthorizationStatusDenied

    4. 用户已经授权给你的程序对通讯录进行访问:kABAuthorizationStatusAuthorized

    // 判断当前的授权状态是否是用户还未选择的状态
    if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) 
    {
       ABAddressBookRef bookRef = ABAddressBookCreate();
       ABAddressBookRequestAccessWithCompletion(bookRef, ^(bool granted, CFErrorRef error) {
           if (granted) 
           {
               NSLog(@"授权成功!");
           }
           else
           {
               NSLog(@"授权失败!");
           }
       });
    }
    
    

    二、判断授权状态

    如果已授权,则继续;未授权,则提示用户,并返回。

    // 判断当前的授权状态
    if (ABAddressBookGetAuthorizationStatus() != kABAuthorizationStatusAuthorized) 
    {
        NSLog(@"您的通讯录暂未允许访问,请去设置->隐私里面授权!");
        return;
    }
    
    

    三、创建通讯录对象

    // 创建通讯录对象
    ABAddressBookRef bookRef = ABAddressBookCreate();
    
    

    四、从通信录对象中, 获取所有的联系人

    // 获取通讯录中所有的联系人
    CFArrayRef arrayRef = ABAddressBookCopyArrayOfAllPeople(bookRef);
    
    

    五、遍历所有的联系人

    // 遍历所有联系人
    CFIndex count = CFArrayGetCount(arrayRef);
    for (int i = 0; i < count; i++) 
    {
       ABRecordRef record = CFArrayGetValueAtIndex(arrayRef, i);
    
       // 获取姓名
       NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty);
       NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty);
       NSLog(@"firstName = %@, lastName = %@", firstName, lastName);
    
       // 获取电话号码
       ABMultiValueRef multiValue = ABRecordCopyValue(record, kABPersonPhoneProperty);
       CFIndex count = ABMultiValueGetCount(multiValue);
       for (int i = 0; i < count; i ++) 
       {
           NSString *label = (__bridge_transfer NSString *)ABMultiValueCopyLabelAtIndex(multiValue, i);
           NSString *phone = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(multiValue, i);
           NSLog(@"label = %@, phone = %@", label, phone);
       }
    
       CFRelease(multiValue);
    }
    
    

    六、释放不再使用的对象

    CFRelease(bookRef);
    CFRelease(arrayRef);
    
    

    联系人属性定义

    所有的属性常量值都定义在了 ABPerson.h 头文件中。

    联系人属性包括以下类型:

    1. 简单属性:姓、名等
    2. 多重属性:电话号码、电子邮件等
    3. 组合属性:地址等

    注意:使用 ABRecordCopyValue 可以从一条 Person 记录中获取到对应的记录,但是后续处理则需要根据记录的具体类型加以区分。

    简单属性

    一个联系人就是一个 ABRecordRef,每个联系人都有自己的属性,比如名字、电话、邮件等。
    使用 ABRecordCopyValue 函数可以从 ABRecordRef 中获得联系人的简单属性(例如:一个字符串)。
    ABRecordCopyValue 函数接收 2 个参数。
    第 1 个参数是 ABRecordRef 实例。
    第 2 个参数是属性关键字,定义在 ABPerson.h 中。
    ABPersonCopyLocalizedPropertyName 函数可以根据指定的关键字获取对应的标签文本。

    获得所有的联系人数据

    // 获取所有联系人记录
    CFArrayRef array = ABAddressBookCopyArrayOfAllPeople(addressBook);
    NSInteger count = CFArrayGetCount(array);
    
    for (NSInteger i = 0; i < count; ++i) {
        // 取出一条记录
        ABRecordRef person = CFArrayGetValueAtIndex(array, i);
    
        // 取出个人记录中的详细信息
        // 名
        CFStringRef firstNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty);
        CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
        CFStringRef lastNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty);
        // 姓
        CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
    
        NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName);
    }
    
    

    CoreFoundation 与 Foundation之间的桥接

    // 1\. 获取通讯录引用
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
    // 2\. 获取所有联系人记录
    NSArray *array = (__bridge NSArray *)(ABAddressBookCopyArrayOfAllPeople(addressBook));
    for (NSInteger i = 0; i < array.count; i++) {
        // 取出一条记录
        ABRecordRef person = (__bridge ABRecordRef)(array[i]);
        // 取出个人记录中的详细信息
        NSString *firstNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty));
        NSString *firstName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty));
        NSString *lastNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty));
        NSString *lastName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonLastNameProperty));
        NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName);
    }
    CFRelease(addressBook);
    
    

    多重属性

    联系人的有些属性值就没这么简单,一个属性可能会包含多个值
    比如邮箱,分为工作邮箱、住宅邮箱、其他邮箱等
    比如电话,分为工作电话、住宅电话、其他电话等
    如果是复杂属性,那么 ABRecordCopyValue 函数返回的就是 ABMultiValueRef 类型的数据,例如邮箱或者电话

    // 取电话号码
    ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
    // 取记录数量
    NSInteger phoneCount = ABMultiValueGetCount(phones);
    // 遍历所有的电话号码
    for (NSInteger i = 0; i < phoneCount; i++) 
    {
    
    }
    
    

    获取复杂属性的方法

    // 电话标签
    CFStringRef phoneLabel = ABMultiValueCopyLabelAtIndex(phones, i);
    // 本地化电话标签
    CFStringRef phoneLocalLabel = ABAddressBookCopyLocalizedLabel(phoneLabel);
    // 电话号码
    CFStringRef phoneNumber = ABMultiValueCopyValueAtIndex(phones, i);
    
    

    添加联系人的步骤

    添加联系人的步骤:

    1. 通过 ABPersonCreate 函数创建一个新的联系人(返回 ABRecordRef)。
    2. 通过 ABRecordSetValue 函数设置联系人的属性。
    3. 通过 ABAddressBookAddRecord 函数将联系人添加到通讯录数据库中。
    4. 通过 ABAddressBookSave 函数保存刚才所作的修改。

    可以通过 ABAddressBookHasUnsavedChanges 函数判断是否有未保存的修改
    当决定是否更改通讯录数据库后,你可以分别使用 AbAddressBookSaveABAddressBookRevert 方式来保存或放弃更改 。

    添加群组的步骤

    添加群组的步骤大体和添加联系人一致:

    1. 通过 ABPersonCreate 函数创建一个新的组。(返回 ABRecordRef
    2. 通过 ABRecordSetValue 函数设置组名。
    3. 通过 ABAddressBookAddRecord 函数将组添加到通讯录数据库中。
    4. 通过 ABAddressBookSave 函数保存刚才所作的修改。

    操作联系人的头像

    想操作联系人的头像,有以下函数
    BPersonHasImageData
    判断通讯录中的联系人是否有图片

    ABPersonCopyImageData
    取得图片数据(假如有的话)

    ABPersonSetImageData
    设置联系人的图片数据

    通讯录的修改回调

    // 创建通讯录
    self.addressBook = ABAddressBookCreate();  
    // 注册通知  
    ABAddressBookRegisterExternalChangeCallback(self.addressBook, _addressBookChange, nil);            
    
    
    // 处理收到通知的 Action
    void _addressBookChange(ABAddressBookRef addressBook, CFDictionaryRef info, void *context)
    {
    }
    
    
    - (void)dealloc
    {
        // 注销通知
        ABAddressBookUnregisterExternalChangeCallback(self.addressBook, _addressBookChange, nil);
        // 释放对象
        CFRelease(self.addressBook);
    }
    
    

    iOS 9 以后的通讯录新框架

    iOS 9 之前操作通讯录还是比较麻烦的,iOS 9 以后苹果推出了全新的通讯录框架,使用起来更加的面向对象。

    CNContactUI

    实现步骤

    一、创建选择联系人的控制器

    // 创建联系人选择控制器    
    CNMutableContact *contact = [[CNMutableContact alloc] init];
    CNLabeledValue *labelValue = [CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMobile
    ontact.phoneNumbers = @[labelValue];                                                                     value:[CNPhoneNumber phoneNumberWithStringValue:phoneNum]];
    CNContactViewController *contactController = [CNContactViewController viewControllerForNewContact:contact];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:contactController];
    
    

    二、设置代理(用来接收用户选择的联系人信息)

    // 设置代理
    contactController.delegate = self;
    
    

    三、弹出联系人控制器

    [controller presentViewController:nav animated:YES completion:nil];
    
    

    四、实现代理方法

    // 选择某个联系人时调用
    - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty
    {
        CNContact *contact = contactProperty.contact;
        NSString *name = [CNContactFormatter stringFromContact:contact style:CNContactFormatterStyleFullName];
        CNPhoneNumber *phoneValue= contactProperty.value;
        NSString *phoneNumber = phoneValue.stringValue;
        NSLog(@"%@--%@",name, phoneNumber);
    }
    
    

    五、在对应的代理方法中获取联系人信息

    // 1.选择联系人时使用(不展开详情)
    - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact;
    
    // 2.选择联系人某个属性时调用(展开详情)
    - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty;
    
    // 3.取消选中联系人时调用
    - (void)contactPickerDidCancel:(CNContactPickerViewController *)picker;
    
    

    注意:与 AddressBookUI 一样,选择联系人的不展开详情(代理方法1)和展开详情(代理方法2)的代理方法都写了的时候,展开详情的代理方法就不执行。

    CNContact

    实现步骤

    一、请求授权

    // 获得通讯录的授权状态
    CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts]
    
    

    授权状态

    1. 用户还没有决定是否授权你的程序进行访问:CNAuthorizationStatusNotDetermined

    2. iOS 设备上一些许可配置阻止程序与通讯录数据库进行交互:CNAuthorizationStatusRestricted

    3. 用户明确的拒绝了你的程序对通讯录的访问:CNAuthorizationStatusDenied

    4. 用户已经授权给你的程序对通讯录进行访问:CNAuthorizationStatusAuthorized

    // 判断当前的授权状态是否是用户还未选择的状态
    if (status == CNAuthorizationStatusNotDetermined)
    {
        CNContactStore *store = [CNContactStore new];
        [store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted) 
            {
                NSLog(@"授权成功!");
            }
            else
            {
                NSLog(@"授权失败!");
            }
        }];
    }
    
    

    二、判断授权状态

    如果已授权,则继续;未授权,则提示用户,并返回。

    // 判断当前的授权状态
    if (status != CNAuthorizationStatusAuthorized) 
    {
        NSLog(@"您的通讯录暂未允许访问,请去设置->隐私里面授权!");
        return;
    }
    
    

    三、创建通讯录对象

    // 创建通讯录对象
    CNContactStore *contactStore = [CNContactStore new];
    
    

    四、设置访问的属性 Key,每个 Key 对应一个属性,iOS 9 新增,如果没有设置,访问该属性就会崩溃。

    // 姓名前缀
    CNContactNamePrefixKey     
    // 名                 
    CNContactGivenNameKey                       
    // 中间名
    CNContactMiddleNameKey  
    // 姓                   
    CNContactFamilyNameKey            
    // 婚前姓         
    CNContactPreviousFamilyNameKey
    // 姓名后缀
    CNContactNameSuffixKey   
    // 昵称                   
    CNContactNicknameKey                        
    // 公司
    CNContactOrganizationNameKey                
    // 部门
    CNContactDepartmentNameKey                  
    // 职位
    CNContactJobTitleKey                        
    // 名字拼音或音标
    CNContactPhoneticGivenNameKey
    // 中间名拼音或音标              
    CNContactPhoneticMiddleNameKey
    // 姓拼音或音标
    CNContactPhoneticFamilyNameKey  
    // 公司拼音或音标            
    CNContactPhoneticOrganizationNameKey      
    // 生日  
    CNContactBirthdayKey   
    // 农历                    
    CNContactNonGregorianBirthdayKey    
    // 备注        
    CNContactNoteKey                            
    // 图片
    CNContactImageDataKey                       
    // 缩略图
    CNContactThumbnailImageDataKey              
    // 图片是否允许访问
    CNContactImageDataAvailableKey              
    // 类型
    CNContactTypeKey                            
    // 号码
    CNContactPhoneNumbersKey                    
    // 电子邮件
    CNContactEmailAddressesKey                  
    // 地址
    CNContactPostalAddressesKey                 
    // 日期
    CNContactDatesKey   
    // URL                        
    CNContactUrlAddressesKey                    
    // 关联人
    CNContactRelationsKey                       
    // 社交
    CNContactSocialProfilesKey                  
    // 即时通讯
    CNContactInstantMessageAddressesKey         
    
    
    NSArray *keys = @[CNContactPhoneNumbersKey,CNContactGivenNameKey];
    
    

    五、从通信录对象中, 获取所有的联系人,并遍历

    // 获取通讯录中所有的联系人
    CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
    
    [contactStore enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
        // 获取姓名
        NSString *firstName = contact.familyName;
        NSString *lastName = contact.givenName;
    
        NSLog(@"%@--%@",firstName,lastName);
    
        // 获取电话号码
    
        for (CNLabeledValue *labeledValue in contact.phoneNumbers)
        {
             CNPhoneNumber *phoneValue = labeledValue.value;
             NSString *phoneNumber = phoneValue.stringValue;
             NSString *label = [CNLabeledValue localizedStringForLabel:labeledValue.label];
             NSLog(@"%@--%@",label,phoneNumber);
        }
    
    }];
    
    

    通讯录的修改回调

    // 注册通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_contactStoreDidChange) name:CNContactStoreDidChangeNotification object:nil]            
    
    
    // 处理收到通知的 Action
    - (void)_contactStoreDidChange
    {
    }
    
    
    - (void)dealloc
    {   
        // 注销通知
        [[NSNotificationCenter defaultCenter] removeObserver:self name:CNContactStoreDidChangeNotification object:nil];
    }
    
    

    当 App 活跃(前台+后台活动期间)的时候,当通讯录修改的时候,会收到通知
    当 App 不活跃的时候(挂起的时候),App 收不到通知;而是,当 App 到前台的时候收到延迟的通知。

    LJContactManager

    介绍

    LJContanctManager 是我写的一款操作通讯录的类库,iOS 9 之前使用的是 AddressBook 和 AddressBookUI 系统库,iOS 9 之后使用苹果新推出的 Contacts 和 ContactsUI 框架。

    安装

    CocoaPods

    1. 在 Podfile 中添加 pod 'LJContactManager'
    2. 执行 pod installpod update
    3. 导入 <LJContactManager.h>。

    手动安装

    1. 下载 LJContactManager 文件夹内的所有内容。
    2. 将 LJContactManager 内的源文件添加(拖放)到你的工程。
    3. 导入 LJContactManager.h

    使用

    主要提供以下的方法:

    • 选择联系人
    /**
     选择联系人
    
     @param controller 控制器
     @param completcion 回调
     */
    - (void)selectContactAtController:(UIViewController *)controller
                          complection:(void (^)(NSString *name, NSString *phone))completcion;
    
    
    image
    • 创建新联系人
    /**
     创建新联系人
    
     @param phoneNum 手机号
     @param controller 当前 Controller
     */
    - (void)createNewContactWithPhoneNum:(NSString *)phoneNum controller:(UIViewController *)controller;
    
    
    image
    • 添加到现有联系人
    /**
     添加到现有联系人
    
     @param phoneNum 手机号
     @param controller 当前 Controller
     */
    - (void)addToExistingContactsWithPhoneNum:(NSString *)phoneNum controller:(UIViewController *)controller;
    
    
    image
    • 获取联系人列表(未分组的通讯录)
    /**
     获取联系人列表(未分组的通讯录)
    
     @param completcion 回调
     */
    - (void)accessContactsComplection:(void (^)(BOOL succeed, NSArray <LJPerson *> *contacts))completcion;
    
    
    image
    • 获取联系人列表(已分组的通讯录)
    /**
     获取联系人列表(已分组的通讯录)
    
     @param completcion 回调
     */
    - (void)accessSectionContactsComplection:(void (^)(BOOL succeed, NSArray <LJSectionPerson *> *contacts, NSArray <NSString *> *keys))completcion;
    
    
    image
    • 通讯录变更回调(未分组的通讯录)
    /**
     通讯录变更回调(未分组的通讯录)
     */
    @property (nonatomic, copy) void (^contactsChangeHanlder) (BOOL succeed, NSArray <LJPerson *> *newContacts);
    
    
    • 通讯录变更回调(已分组的通讯录)
    /**
     通讯录变更回调(已分组的通讯录)
     */
    @property (nonatomic, copy) void (^sectionContactsHanlder) (BOOL succeed, NSArray <LJSectionPerson *> *newSectionContacts, NSArray <NSString *> *keys);
    
    

    最后

    由于笔者水平有限,文中如果有错误的地方,或者有更好的方法,还望大神指出。
    附上本文的所有 demo 下载链接,【GitHub】
    如果你看完后觉得对你有所帮助,还望在 GitHub 上点个 star。赠人玫瑰,手有余香。

    作者:LeeJay
    链接:https://www.jianshu.com/p/55d1c90f62c8

    相关文章

      网友评论

        本文标题:iOS 通讯录获取使用

        本文链接:https://www.haomeiwen.com/subject/rxwukltx.html