1. 程式人生 > >iOS開發------獲取系統聯絡人(Contacts篇)

iOS開發------獲取系統聯絡人(Contacts篇)

Contacts.framework是Apple在 iOS9.0 替代AddressBook.framework的框架,至於AddressBook是做什麼的框架,樓主預設看到博文的開發者是知道的 O(∩_∩)O。

每次iOS釋出新的版本(甚至每年的WWDC大會舉行完畢)很多敏銳的開發者都準備或者對新版本特性進行適配。當然這些大神肯定會在iOS9釋出後在第一時間對通訊錄功能進行適配,一些稍微不太敏銳的開發者鑑於AddressBook在iOS9下初次提醒以及討厭適配的繁瑣,也就不以為然。

但隨著iOS10的釋出,那麼適配相關框架就顯得格外重要(不是說AddressBook不能使用了,但為了專案的健壯性以及良好的體驗性,還是非常建議第一時間適配的。當然,這句話不僅限於Contacts部分)。

如果大家的專案還需要適配iOS8(當然,大多數公司肯定是也不會拋棄iOS7的使用者),那麼使用AddressBook是必然的;但如果在iOS9+的系統上,樓主還是非常建議使用最新的Contacts.framework框架的.

個人推薦的主要是下面兩點原因(來源於樓主檢視官方文件,編寫Demo以及使用instruments的體會):

  1. AddressBook與其他相關廢棄框架相似一樣 (ex:ALAsset-圖片庫),語言風格更接近於C語言(當然也可以說就是C語言),不在ARC管理之下(對於習慣使用ARC下的開發者算是不小的挑戰),使用不太便利並容易造成記憶體洩露。

  2. 新的框架無論在檢視開發文件、使用、讀取速度還是靈活性都遠好於廢棄框架,記憶體洩露易於查詢以及補漏。

預覽圖

左邊為AddressBook框架進行的演示,右邊為Contact框架進行的演示.
根據不同的版本進行自動適配,如果是iOS9,自動使用Contact.framework.

 

許可權描述

在iOS10上由於許可權有很多的坑,本博文的內容需要使用通訊錄許可權.
那麼不要忘記在專案的info.plist檔案中加入如下描述:Privacy - Contacts Usage Description,描述字串:RITL want to use your Contacts(這個隨意),儘可能的寫點東西吧,聽說如果不寫上線可能會被Apple拒絕..

獲取許可權-CNContactStore

負責獲得許可權、請求許可權以及執行操作請求的類就是CNContactStore,具體Demo中的程式碼如下:

/**
 檢測許可權並作響應的操作
 */
- (void)__checkAuthorizationStatus
{
    //這裡有一個列舉類:CNEntityType,不過沒關係,只有一個值:CNEntityTypeContacts
    switch ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts])
    {
            //存在許可權
        case CNAuthorizationStatusAuthorized:
            //獲取通訊錄
            [self __obtainContacts:self.completeBlock];
            break;

            //許可權未知
        case CNAuthorizationStatusNotDetermined:
            //請求許可權
            [self __requestAuthorizationStatus];break;

            //如果沒有許可權
        case CNAuthorizationStatusRestricted:
        case CNAuthorizationStatusDenied://需要提示
            self.defendBlock();break;
    }
}

請求聯絡人列表-CNContactStore

這裡有幾種比較常用的思路

1.使用自帶的列舉方法一次性獲得所有的屬性

// 使用列舉方法對所有的聯絡人物件(CNContact)進行列出,該方法是同步的
- (BOOL)enumerateContactsWithFetchRequest:(CNContactFetchRequest *)fetchRequest
                                    error:(NSError *__nullable *__nullable)error
                               usingBlock:(void (^)(CNContact *contact, BOOL *stop))block;

2.先獲取所有聯絡人的identifier,再根據identifier讀取聯絡人資訊(Demo中使用的該思路)

// 通過identifer獲得一個唯一的CNContact
- (nullable CNContact *)unifiedContactWithIdentifier:(NSString *)identifier
                                         keysToFetch:(NSArray<id<CNKeyDescriptor>> *)keys
                                               error:(NSError *__nullable *__nullable)error;

遍歷請求類-CNContactFetchRequest

感覺這裡介紹一下CNContactFetchRequest類還是有必要的,畢竟當初在這裡也是浪費了點時間,它是一個遍歷請求的類,我們可以通過初始化該類的例項物件,告訴contactStore我們需要遍歷contact的某些屬性:

//例項化CNContactFetchRequest物件,通過一個遍歷鍵的描述陣列
- (instancetype)initWithKeysToFetch:(NSArray <id<CNKeyDescriptor>>*)keysToFetch NS_DESIGNATED_INITIALIZER;

鍵值描述協議-CNKeyDescriptor

如果我們單純的進入開發文件,我們會發現他是一個空協議,剛開始看到這裡的時候樓主表示很蒙B

//沒有任何的required和optional方法
@protocol CNKeyDescriptor <NSObject, NSSecureCoding, NSCopying>
@end

但很快就發現了下面這個Category

// //Allows contact property keys to be used with keysToFetch.
// 允許contact的屬性鍵作為遍歷的鍵
@interface NSString (Contacts) <CNKeyDescriptor>
@end

如果還是有點疑惑,那麼相信看到下面就不會再有困惑了呢。沒錯,可以直接將下列字串當成CNKeyDescriptor物件寫入陣列

//識別符號
CONTACTS_EXTERN NSString * const CNContactIdentifierKey                      NS_AVAILABLE(10_11, 9_0);
//姓名字首
CONTACTS_EXTERN NSString * const CNContactNamePrefixKey                      NS_AVAILABLE(10_11, 9_0);
//姓名
CONTACTS_EXTERN NSString * const CNContactGivenNameKey                       NS_AVAILABLE(10_11, 9_0);
//中間名
CONTACTS_EXTERN NSString * const CNContactMiddleNameKey                      NS_AVAILABLE(10_11, 9_0);
//姓氏
CONTACTS_EXTERN NSString * const CNContactFamilyNameKey                      NS_AVAILABLE(10_11, 9_0);
//之前的姓氏(ex:國外的女士)
CONTACTS_EXTERN NSString * const CNContactPreviousFamilyNameKey              NS_AVAILABLE(10_11, 9_0);
//姓名字尾
CONTACTS_EXTERN NSString * const CNContactNameSuffixKey                      NS_AVAILABLE(10_11, 9_0);
//暱稱
CONTACTS_EXTERN NSString * const CNContactNicknameKey                        NS_AVAILABLE(10_11, 9_0);
//公司(組織)
CONTACTS_EXTERN NSString * const CNContactOrganizationNameKey                NS_AVAILABLE(10_11, 9_0);
//部門
CONTACTS_EXTERN NSString * const CNContactDepartmentNameKey                  NS_AVAILABLE(10_11, 9_0);
//職位
CONTACTS_EXTERN NSString * const CNContactJobTitleKey                        NS_AVAILABLE(10_11, 9_0);
//名字的拼音或音標
CONTACTS_EXTERN NSString * const CNContactPhoneticGivenNameKey               NS_AVAILABLE(10_11, 9_0);
//中間名的拼音或音標
CONTACTS_EXTERN NSString * const CNContactPhoneticMiddleNameKey              NS_AVAILABLE(10_11, 9_0);
//形式的拼音或音標
CONTACTS_EXTERN NSString * const CNContactPhoneticFamilyNameKey              NS_AVAILABLE(10_11, 9_0);
//公司(組織)的拼音或音標(iOS10 才開始存在的呢)
CONTACTS_EXTERN NSString * const CNContactPhoneticOrganizationNameKey        NS_AVAILABLE(10_12, 10_0);
//生日
CONTACTS_EXTERN NSString * const CNContactBirthdayKey                        NS_AVAILABLE(10_11, 9_0);
//農曆
CONTACTS_EXTERN NSString * const CNContactNonGregorianBirthdayKey            NS_AVAILABLE(10_11, 9_0);
//備註
CONTACTS_EXTERN NSString * const CNContactNoteKey                            NS_AVAILABLE(10_11, 9_0);
//頭像
CONTACTS_EXTERN NSString * const CNContactImageDataKey                       NS_AVAILABLE(10_11, 9_0);
//頭像的縮圖
CONTACTS_EXTERN NSString * const CNContactThumbnailImageDataKey              NS_AVAILABLE(10_11, 9_0);
//頭像是否可用
CONTACTS_EXTERN NSString * const CNContactImageDataAvailableKey              NS_AVAILABLE(10_12, 9_0);
//型別
CONTACTS_EXTERN NSString * const CNContactTypeKey                            NS_AVAILABLE(10_11, 9_0);
//電話號碼
CONTACTS_EXTERN NSString * const CNContactPhoneNumbersKey                    NS_AVAILABLE(10_11, 9_0);
//郵箱地址
CONTACTS_EXTERN NSString * const CNContactEmailAddressesKey                  NS_AVAILABLE(10_11, 9_0);
//住址
CONTACTS_EXTERN NSString * const CNContactPostalAddressesKey                 NS_AVAILABLE(10_11, 9_0);
//其他日期
CONTACTS_EXTERN NSString * const CNContactDatesKey                           NS_AVAILABLE(10_11, 9_0);
//url地址
CONTACTS_EXTERN NSString * const CNContactUrlAddressesKey                    NS_AVAILABLE(10_11, 9_0);
//關聯人
CONTACTS_EXTERN NSString * const CNContactRelationsKey                       NS_AVAILABLE(10_11, 9_0);
//社交
CONTACTS_EXTERN NSString * const CNContactSocialProfilesKey                  NS_AVAILABLE(10_11, 9_0);
//即時通訊
CONTACTS_EXTERN NSString * const CNContactInstantMessageAddressesKey         NS_AVAILABLE(10_11, 9_0);

獲取聯絡人姓名屬性

// RITLContactNameObject獲取姓名屬性的類目方法
-(void)contactObject:(CNContact *)contact
{
    [super contactObject:contact];

    //設定姓名屬性
    self.nickName = contact.nickname;                   //暱稱
    self.givenName = contact.givenName;                 //名字
    self.familyName = contact.familyName;               //姓氏
    self.middleName = contact.middleName;               //中間名
    self.namePrefix = contact.namePrefix;               //名字字首
    self.nameSuffix = contact.nameSuffix;               //名字的字尾
    self.phoneticGivenName = contact.phoneticGivenName; //名字的拼音或音標
    self.phoneticFamilyName = contact.phoneticFamilyName;//姓氏的拼音或音標
    self.phoneticMiddleName = contact.phoneticMiddleName;//中間名的拼音或音標

#ifdef __IPHONE_10_0
    self.phoneticOrganizationName = contact.phoneticOrganizationName;//公司(組織)的拼音或音標
#endif
}

獲取聯絡人的型別

這裡需要判斷一下該屬性是否可用(不只該屬性,所有的屬性都應先判斷一下)不然會丟擲異常.

/**
 *  獲得聯絡人型別資訊
 */
+ (RITLContactType)__contactTypeProperty
{
    if (![self.currentContact isKeyAvailable:CNContactTypeKey])
    {
        return RITLContactTypeUnknown;//沒有可用就是未知
    }

    else if (self.currentContact.contactType == CNContactTypeOrganization)
    {
        return RITLContactTypeOrigination;//如果是組織
    }

    else{
        return RITLContactTypePerson;
    }
}

獲得聯絡人的頭像圖片

/**
 *  獲得聯絡人的頭像圖片
 */
+ (UIImage * __nullable)__contactHeadImagePropery
{
    //縮圖Data
    if ([self.currentContact isKeyAvailable:CNContactThumbnailImageDataKey])
    {
        NSData * thumImageData = self.currentContact.thumbnailImageData;

        return [UIImage imageWithData:thumImageData];
    }
    return nil;
}

獲取聯絡人的電話資訊

/**
 *  獲得電話號碼物件陣列
 */
+ (NSArray <RITLContactPhoneObject *> *)__contactPhoneProperty
{

    if (![self.currentContact isKeyAvailable:CNContactPhoneNumbersKey])
    {
        return @[];
    }

    //外傳陣列
    NSMutableArray <RITLContactPhoneObject *> * phones = [NSMutableArray arrayWithCapacity:self.currentContact.phoneNumbers.count];

    for (CNLabeledValue * phoneValue in self.currentContact.phoneNumbers)
    {
        //初始化PhoneObject物件
        RITLContactPhoneObject * phoneObject = [RITLContactPhoneObject new];

        //setValue
        phoneObject.phoneTitle = phoneValue.label;
        phoneObject.phoneNumber = ((CNPhoneNumber *)phoneValue.value).stringValue;

        [phones addObject:phoneObject];
    }

    return [NSArray arrayWithArray:phones];
}

獲取聯絡人的工作資訊

/**
 *  獲得工作的相關屬性
 */
+ (RITLContactJobObject *)__contactJobProperty
{
    RITLContactJobObject * jobObject = [[ RITLContactJobObject alloc]init];

    if ([self.currentContact isKeyAvailable:CNContactJobTitleKey])
    {
        //setValue
        jobObject.jobTitle = self.currentContact.jobTitle;
        jobObject.departmentName = self.currentContact.departmentName;
        jobObject.organizationName = self.currentContact.organizationName;
    }

    return jobObject;
}

獲取聯絡人的郵件資訊

/**
 *  獲得Email物件的陣列
 */
+ (NSArray <RITLContactEmailObject *> *)__contactEmailProperty
{
    if (![self.currentContact isKeyAvailable:CNContactEmailAddressesKey])
    {
        return @[];
    }

    //外傳陣列
    NSMutableArray <RITLContactEmailObject *> * emails = [NSMutableArray arrayWithCapacity:self.currentContact.emailAddresses.count];

    for (CNLabeledValue * emailValue in self.currentContact.emailAddresses)
    {
        //初始化RITLContactEmailObject物件
        RITLContactEmailObject * emailObject = [[RITLContactEmailObject alloc]init];

        //setValue
        emailObject.emailTitle = emailValue.label;
        emailObject.emailAddress = emailValue.value;

        [emails addObject:emailObject];

    }

    return [NSArray arrayWithArray:emails];
}

獲取聯絡人的地址資訊

/**
 *  獲得Address物件的陣列
 */
+ (NSArray <RITLContactAddressObject *> *)__contactAddressProperty
{
    if (![self.currentContact isKeyAvailable:CNContactPostalAddressesKey]) {

        return @[];

    }

    //外傳陣列
    NSMutableArray <RITLContactAddressObject *> * addresses = [NSMutableArray arrayWithCapacity:self.currentContact.postalAddresses.count];

    for (CNLabeledValue * addressValue in self.currentContact.postalAddresses)
    {
        //初始化地址物件
        RITLContactAddressObject * addressObject = [[RITLContactAddressObject alloc]init];

        //setValues
        addressObject.addressTitle = addressValue.label;

        //setDetailValue
        [addressObject contactObject:addressValue.value];

        //add object
        [addresses addObject:addressObject];
    }

    return [NSArray arrayWithArray:addresses];
}

獲得聯絡人的生日資訊

/**
 *  獲得生日的相關屬性
 */
+ (RITLContactBrithdayObject *)__contactBrithdayProperty
{
    //例項化物件
    RITLContactBrithdayObject * brithdayObject = [[RITLContactBrithdayObject alloc]init];


    if ([self.currentContact isKeyAvailable:CNContactBirthdayKey])
    {
        //set value
        brithdayObject.brithdayDate = [self.currentContact.birthday.calendar dateFromComponents:self.currentContact.birthday];
        brithdayObject.leapMonth = self.currentContact.birthday.isLeapMonth;
    }

    if ([self.currentContact isKeyAvailable:CNContactNonGregorianBirthdayKey])
    {
        brithdayObject.calendar = self.currentContact.nonGregorianBirthday.calendar.calendarIdentifier;
        brithdayObject.era = self.currentContact.nonGregorianBirthday.era;
        brithdayObject.day = self.currentContact.nonGregorianBirthday.day;
        brithdayObject.month = self.currentContact.nonGregorianBirthday.month;
        brithdayObject.year = self.currentContact.nonGregorianBirthday.year;
    }

    //返回物件
    return brithdayObject;
}

獲取聯絡人的即時通訊資訊

/**
 *  獲得即時通訊賬號相關資訊
 */
+ (NSArray <RITLContactInstantMessageObject *> *)__contactMessageProperty
{
    if (![self.currentContact isKeyAvailable:CNContactInstantMessageAddressesKey])
    {
        return @[];
    }

    //存放陣列
    NSMutableArray <RITLContactInstantMessageObject *> * instantMessages = [NSMutableArray arrayWithCapacity:self.currentContact.instantMessageAddresses.count];

    for (CNLabeledValue * instanceAddressValue in self.currentContact.instantMessageAddresses)
    {
        RITLContactInstantMessageObject * instaceObject = [[RITLContactInstantMessageObject alloc]init];

        //set value
        instaceObject.identifier = instanceAddressValue.identifier;
        instaceObject.service = ((CNInstantMessageAddress *)instanceAddressValue.value).service;
        instaceObject.userName = ((CNInstantMessageAddress *)instanceAddressValue.value).username;

        //add
        [instantMessages addObject:instaceObject];
    }

    return [NSArray arrayWithArray:instantMessages];
}

獲得聯絡人的關聯人資訊

/**
 *  獲得聯絡人的關聯人資訊
 */
+ (NSArray <RITLContactRelatedNamesObject *> *)__contactRelatedNamesProperty
{
    if (![self.currentContact isKeyAvailable:CNContactRelationsKey])
    {
        return @[];
    }

    //存放陣列
    NSMutableArray <RITLContactRelatedNamesObject *> * relatedNames = [NSMutableArray arrayWithCapacity:self.currentContact.contactRelations.count];

    for (CNLabeledValue * relationsValue in self.currentContact.contactRelations)
    {
        RITLContactRelatedNamesObject * relatedObject = [[RITLContactRelatedNamesObject alloc]init];

        //set value
        relatedObject.identifier = relationsValue.identifier;
        relatedObject.relatedTitle = relationsValue.label;
        relatedObject.relatedName = ((CNContactRelation *)relationsValue.value).name;

        [relatedNames addObject:relatedObject];

    }

    return [NSArray arrayWithArray:relatedNames];
}

獲取聯絡人的社交簡介資訊

/**
 *  獲得聯絡人的社交簡介資訊
 */
+ (NSArray <RITLContactSocialProfileObject *> *)__contactSocialProfilesProperty
{
    if (![self.currentContact isKeyAvailable:CNContactSocialProfilesKey])
    {
        return @[];
    }

    //外傳陣列
    NSMutableArray <RITLContactSocialProfileObject *> * socialProfiles = [NSMutableArray arrayWithCapacity:self.currentContact.socialProfiles.count];

    for (CNLabeledValue * socialProfileValue in self.currentContact.socialProfiles) {

        RITLContactSocialProfileObject * socialProfileObject = [[RITLContactSocialProfileObject alloc]init];

        //獲得CNSocialProfile物件
        CNSocialProfile * socialProfile = socialProfileValue.value;

        //set value
        socialProfileObject.identifier = socialProfileValue.identifier;
        socialProfileObject.socialProfileTitle = socialProfile.service;
        socialProfileObject.socialProFileAccount = socialProfile.username;
        socialProfileObject.socialProFileUrl = socialProfile.urlString;

        [socialProfiles addObject:socialProfileObject];
    }

    return [NSArray arrayWithArray:socialProfiles];
}

獲取聯絡人的備註資訊

/**
 獲得聯絡人的備註資訊
 */
+ (NSString * __nullable)__contactNoteProperty
{
    if ([self.currentContact isKeyAvailable:CNContactNoteKey])
    {
        return self.currentContact.note;
    }

    return nil;
}

接收外界通訊錄發生變化的方法

這裡不再是直接使用C語言的函式賦址來進行方法註冊,方法更加的ObjC,選用了更多的通知中心。

/**
 新增變化監聽
 */
- (void)__addStoreDidChangeNotification
{
    if (self.notificationDidAdd == false)
    {
        //新增通知
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(__contactDidChange:) name:CNContactStoreDidChangeNotification object:nil];
        self.notificationDidAdd = !self.notificationDidAdd;
    }  
}

下面是執行變化後的方法:

樓主的測試的時候通訊錄變化會連續觸發3次通知方法,後面的延遲3s就是解決連續觸發的問題,也不知道是個人的程式出問題還是Contact框架的bug,如果大家有什麼好辦法或者什麼好的建議,也請告知一下,十分感謝。

/**
 通訊錄發生變化進行的回撥

 @param notication 傳送的通知
 */
- (void)__contactDidChange:(NSNotification *)notication
{
    //重新獲取通訊錄
    if (self.contactDidChange != nil )
    {
        //如果可以進行回撥
        if (self.shouldResponseContactChange == true)
        {
            //重新載入快取
            [[RITLContactCatcheManager sharedInstace]reloadContactIdentifiers:^(NSArray<NSString *> * _Nonnull identifiers) {

                NSArray * contacts = [self __contactHandleWithIdentifiers:identifiers];

                //回撥
                self.contactDidChange([contacts mutableCopy]);
            }];

            self.responseContactChange = false;

            //延遲3s
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                self.responseContactChange = true;

            });
        }
    }
}