iOS 13.x获取通讯录信息时的注意事项

之前的项目兼容的版本在iOS9.0以上,所以在项目中使用了CNContacts框架获取用户的通讯录信息,大致的实现如下:

/// 判断字符串是否可用
/// 辅助函数
/// @param str 原始对象
BOOL isAvailableString(NSString *str) {
    if ([str isKindOfClass:[NSNull class]]) {
        return false;
    }
    if (!str || !str.length) {
        return false;
    }
    return true;
}


/// 查看通讯录访问权限
void getAuthrization(void(^_Nonnull result)(BOOL granted)) {
    CNAuthorizationStatus authorizationStatus = [CNContactStore authorizationStatusForEntityType:(CNEntityTypeContacts)];
    if (authorizationStatus == CNAuthorizationStatusNotDetermined) {
        CNContactStore *contactStore = [[CNContactStore alloc] init];
        [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (result) {
                result(granted);
            }
        }];
    } else if (authorizationStatus == CNAuthorizationStatusAuthorized){
        if (result) {
            result(true);
        }
    } else {
        if (result) {
            result(true);
        }
    }
}

/// 获取通讯录信息
/// @param result 结果回调
- (void)getAllContacts:(void(^)(NSArray *))result {
    getAuthrization(^(BOOL granted){
        if (granted) {
            CNContactStore *contactStore = [[CNContactStore alloc] init];
            CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactMiddleNameKey, CNContactNoteKey]];
            NSError *error = nil;
            __block NSMutableArray  *> *allContacts = @[].mutableCopy;
            [contactStore enumerateContactsWithFetchRequest:fetchRequest error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
                NSString *familyName = contact.familyName;
                familyName = isAvailableString(familyName) ? familyName : @"";
                
                NSString *middleName = contact.middleName;
                middleName = isAvailableString(middleName) ? middleName : @"";
                
                NSString *givenName = contact.givenName;
                givenName = isAvailableString(givenName) ? givenName : @"";
                
                NSString *note = contact.note;
                note = isAvailableString(note) ? note : @"";
                
                NSString *fullName = [NSString stringWithFormat:@"%@%@%@", familyName, middleName, givenName];
                if (isAvailableString(fullName)) {
                    [allContacts addObject:@{@"fullName" : fullName, @"note" : note}];
                }
            }];
            if (result) {
                result(allContacts);
            }
        }
    });
}

一直相安无事,正常运行.直到某一天早上突然发现,预警系统相当一部分用户的所有的联系人信息一夜之间全部为空,一脸懵逼.

其实在早前的发布大会上,已经说明了要对要用户通讯录隐私数据的使用权限做限制,尤其是"备注"信息,可能会涉及到用户的个人私密数据,例如银行卡密码,亲密人物关系等,所以在iOS13.x上使用用户通讯录数据时就需要注意.

在iOS 13.x 上获取用户的通讯录数据时,不能获取 "备注" 信息,否则会系统会抛出异常,从而导致获取信息失败.在获取的方法中,修改一下实现来查看一下是否有异常发生:

/// 获取通讯录信息
/// @param result 结果回调
- (void)getAllContacts:(void(^)(NSArray *))result {
    getAuthrization(^(BOOL granted){
        if (granted) {
            CNContactStore *contactStore = [[CNContactStore alloc] init];
            CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactMiddleNameKey, CNContactNoteKey]];
            NSError *error = nil;
            __block NSMutableArray  *> *allContacts = @[].mutableCopy;
           BOOL fetchSuccess = [contactStore enumerateContactsWithFetchRequest:fetchRequest error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
                NSString *familyName = contact.familyName;
                familyName = isAvailableString(familyName) ? familyName : @"";
                
                NSString *middleName = contact.middleName;
                middleName = isAvailableString(middleName) ? middleName : @"";
                
                NSString *givenName = contact.givenName;
                givenName = isAvailableString(givenName) ? givenName : @"";
                
                NSString *note = contact.note;
                note = isAvailableString(note) ? note : @"";
                
                NSString *fullName = [NSString stringWithFormat:@"%@%@%@", familyName, middleName, givenName];
                if (isAvailableString(fullName)) {
                    [allContacts addObject:@{@"fullName" : fullName, @"note" : note}];
                }
            }];
            if (!fetchSuccess || error) {
                NSLog(@"fetchSuccess : %@", fetchSuccess ? @"成功" : @"失败");
                NSLog(@"error : %@", error);
            }
            if (result) {
                result(allContacts);
            }
        }
    });
}

再次运行,输出信息果然是异常的:

 fetchSuccess : 失败
 error : Error Domain=CNErrorDomain Code=102 "(null)" UserInfo={CNKeyPaths=(
    note
), CNInvalidRecords=(
    ""
)}

 然后在CNErrorDomain定义中找到了关于Code=102的说明:

CONTACTS_EXTERN NSString * const CNErrorDomain NS_AVAILABLE(10_11, 9_0);

typedef NS_ENUM(NSInteger, CNErrorCode)
{
    CNErrorCodeCommunicationError = 1,
    CNErrorCodeDataAccessError = 2,
    
    CNErrorCodeAuthorizationDenied = 100,
    CNErrorCodeNoAccessableWritableContainers NS_ENUM_AVAILABLE(10_13_3, 11_3) = 101,
    CNErrorCodeUnauthorizedKeys API_AVAILABLE(macos(10.14), ios(13.0), watchos(6.0)) = 102,
    CNErrorCodeFeatureDisabledByUser API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0)) = 103,

    CNErrorCodeRecordDoesNotExist = 200,
    CNErrorCodeInsertedRecordAlreadyExists = 201,
    CNErrorCodeContainmentCycle = 202,
    CNErrorCodeContainmentScope = 203,
    CNErrorCodeParentRecordDoesNotExist = 204,
    CNErrorCodeRecordIdentifierInvalid = 205,
    CNErrorCodeRecordNotWritable API_AVAILABLE(macos(10.14), ios(13.0), watchos(6.0)) = 206,
    CNErrorCodeParentContainerNotWritable API_AVAILABLE(macos(10.14), ios(13.0), watchos(6.0)) = 207,

    
    CNErrorCodeValidationMultipleErrors = 300,
    CNErrorCodeValidationTypeMismatch = 301,
    CNErrorCodeValidationConfigurationError = 302,

    CNErrorCodePredicateInvalid = 400,
    
    CNErrorCodePolicyViolation = 500,
    
    CNErrorCodeClientIdentifierInvalid = 600,
    CNErrorCodeClientIdentifierDoesNotExist = 601,
    CNErrorCodeClientIdentifierCollision = 602,
    CNErrorCodeChangeHistoryExpired = 603,
    CNErrorCodeChangeHistoryInvalidAnchor = 604,
    
    CNErrorCodeVCardMalformed NS_ENUM_AVAILABLE(10_13, 11_0) = 700,
    CNErrorCodeVCardSummarizationError NS_ENUM_AVAILABLE(10_14, 12_0) = 701,
    
}  NS_ENUM_AVAILABLE(10_11, 9_0);

发现CNErrorCodeUnauthorizedKeys = 102是获取了未授权的属性key.由此真相大白,苹果爸爸不再授权获取note属性了.那么我是不是只要在CNContactFetchRequest初始化时,不加入CNContactNoteKey就可以了,大不了在访问时获取不到数据就完事了?

CNContactStore *contactStore = [[CNContactStore alloc] init];
CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactMiddleNameKey, /*CNContactNoteKey*/]];

再次运行,结果

Ignored Exception: A property was not requested when contact was fetched.
(
	0   CoreFoundation                      0x00000001a2f49c44 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 1256516
	1   libobjc.A.dylib                     0x00000001a2c640c8 objc_exception_throw + 60
	2   CoreFoundation                      0x00000001a2e393ac 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 140204
	3   Contacts                            0x00000001aa321d18 75694259-6C2D-34A1-828B-E0A506C23F35 + 818456
	4   ContactsDemo                        0x0000000100a91528 __33-[ViewController getAllContacts:]_block_invoke.83 + 412
	5   Contacts                            0x00000001aa2c0434 75694259-6C2D-34A1-828B-E0A506C23F35 + 418868
	6   Contacts                            0x00000001aa2f5a08 75694259-6C2D-34A1-828B-E0A506C23F35 + 637448
	7   CoreFoundation                      0x00000001a2f1b7c4 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 1066948
	8   CoreFoundation                      0x00000001a2e19424 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 9252
	9   Contacts                            0x00000001aa2f5914 75694259-6C2D-34A1-828B-E0A506C23F35 + 637204
	10  ContactsFoundation                  0x00000001ac8ac78c CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 178060
	11  ContactsFoundation                  0x00000001ac8e4b24 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 408356
	12  ContactsFoundation                  0x00000001ac8e4b24 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 408356
	13  ContactsFoundation                  0x00000001ac8e4b24 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 408356
	14  ContactsFoundation                  0x00000001ac8e4b24 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 408356
	15  ContactsFoundation                  0x00000001ac8e4b24 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 408356
	16  Contacts                            0x00000001aa2881f8 75694259-6C2D-34A1-828B-E0A506C23F35 + 188920
	17  Contacts                            0x00000001aa2888b8 75694259-6C2D-34A1-828B-E0A506C23F35 + 190648
	18  Contacts                            0x00000001aa289024 75694259-6C2D-34A1-828B-E0A506C23F35 + 192548
	19  CoreFoundation                      0x00000001a2f50724 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 1283876
	20  CoreFoundation                      0x00000001a2e19280 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 8832
	21  Foundation                          0x00000001a34489cc 672CF0CB-4951-3B91-89DF-55E953AEA00F + 2410956
	22  Foundation                          0x00000001a3211088 672CF0CB-4951-3B91-89DF-55E953AEA00F + 86152
	23  Foundation                          0x00000001a3449d04 672CF0CB-4951-3B91-89DF-55E953AEA00F + 2415876
	24  Foundation                          0x00000001a32102d4 672CF0CB-4951-3B91-89DF-55E953AEA00F + 82644
	25  Foundation                          0x00000001a321e0ac 672CF0CB-4951-3B91-89DF-55E953AEA00F + 139436
	26  Foundation                          0x00000001a3450f44 672CF0CB-4951-3B91-89DF-55E953AEA00F + 2445124
	27  Contacts                            0x00000001aa288c78 75694259-6C2D-34A1-828B-E0A506C23F35 + 191608
	28  Contacts                            0x00000001aa288738 75694259-6C2D-34A1-828B-E0A506C23F35 + 190264
	29  Contacts                            0x00000001aa288060 75694259-6C2D-34A1-828B-E0A506C23F35 + 188512
	30  ContactsFoundation                  0x00000001ac8c6780 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 284544
	31  ContactsFoundation                  0x00000001ac8abb08 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 174856
	32  ContactsFoundation                  0x00000001ac8c6780 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 284544
	33  ContactsFoundation                  0x00000001ac8ab800 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 174080
	34  ContactsFoundation                  0x00000001ac8c6780 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 284544
	35  ContactsFoundation                  0x00000001ac8ab800 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 174080
	36  ContactsFoundation                  0x00000001ac8c6780 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 284544
	37  ContactsFoundation                  0x00000001ac8abb08 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 174856
	38  ContactsFoundation                  0x00000001ac8c6780 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 284544
	39  ContactsFoundation                  0x00000001ac8ab45c CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 173148
	40  ContactsFoundation                  0x00000001ac8c6780 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 284544
	41  ContactsFoundation                  0x00000001ac8ac638 CAD1A4FE-E7C1-3980-8391-9E6E7CA88370 + 177720
	42  Contacts                            0x00000001aa2f55d4 75694259-6C2D-34A1-828B-E0A506C23F35 + 636372
	43  Contacts                            0x00000001aa3584d0 75694259-6C2D-34A1-828B-E0A506C23F35 + 1041616
	44  libsystem_trace.dylib               0x00000001a2b27d30 os_activity_apply_f + 88
	45  Contacts                            0x00000001aa358438 75694259-6C2D-34A1-828B-E0A506C23F35 + 1041464
	46  Contacts                            0x00000001aa2f52a4 75694259-6C2D-34A1-828B-E0A506C23F35 + 635556
	47  Contacts                            0x00000001aa2c0320 75694259-6C2D-34A1-828B-E0A506C23F35 + 418592
	48  ContactsDemo                        0x0000000100a91180 __33-[ViewController getAllContacts:]_block_invoke + 480
	49  ContactsDemo                        0x0000000100a90dbc getAuthrization + 340
	50  ContactsDemo                        0x0000000100a90f7c -[ViewController getAllContacts:] + 120
	51  ContactsDemo                        0x0000000100a917f0 -[ViewController tableView:didSelectRowAtIndexPath:] + 104
	52  UIKitCore                           0x00000001a71b04c0 55D60569-DBB7-3BC9-8A97-DCAC95C64D4B + 12588224
	53  UIKitCore                           0x00000001a71afff8 55D60569-DBB7-3BC9-8A97-DCAC95C64D4B + 12587000
	54  UIKitCore                           0x00000001a71b06e0 55D60569-DBB7-3BC9-8A97-DCAC95C64D4B + 12588768
	55  UIKitCore                           0x00000001a6fe9078 55D60569-DBB7-3BC9-8A97-DCAC95C64D4B + 10723448
	56  UIKitCore                           0x00000001a6fd876c 55D60569-DBB7-3BC9-8A97-DCAC95C64D4B + 10655596
	57  UIKitCore                           0x00000001a70099a0 55D60569-DBB7-3BC9-8A97-DCAC95C64D4B + 10856864
	58  CoreFoundation                      0x00000001a2ec467c 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 710268
	59  CoreFoundation                      0x00000001a2ebf31c 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 688924
	60  CoreFoundation                      0x00000001a2ebf8cc 97285ACB-7B21-393A-ABF6-03F1DBB5D2A2 + 690380
	61  CoreFoundation                      0x00000001a2ebf098 CFRunLoopRunSpecific + 480
	62  GraphicsServices                    0x00000001ad029534 GSEventRunModal + 108
	63  UIKitCore                           0x00000001a6fdf7ac UIApplicationMain + 1940
	64  ContactsDemo                        0x0000000100a92cb8 main + 124
	65  libdyld.dylib                       0x00000001a2d3ef30 0DC9A4BA-C3E8-3487-99DB-1B5C86597AF5 + 3888
)

尴尬....就连之前设定的日志

if (!fetchSuccess || error) {
    NSLog(@"fetchSuccess : %@", fetchSuccess ? @"成功" : @"失败");
    NSLog(@"error : %@", error);
    }

都没有打印,但是通过断点,那就说明fetchSuccess返回了成功,同时error为空,这种隐藏的异常是最难发现的.通过设置断点发现获取的遍历方法还是执行了的.为啥会这样呢,我们使用异常捕获来查看一下:

               @try {
                   NSString *familyName = contact.familyName;
                   familyName = isAvailableString(familyName) ? familyName : @"";
                   
                   NSString *middleName = contact.middleName;
                   middleName = isAvailableString(middleName) ? middleName : @"";
                   
                   NSString *givenName = contact.givenName;
                   givenName = isAvailableString(givenName) ? givenName : @"";
                   
                   NSString *note = contact.note;
                   note = isAvailableString(note) ? note : @"";
                   
                   NSString *fullName = [NSString stringWithFormat:@"%@%@%@", familyName, middleName, givenName];
                   if (isAvailableString(fullName)) {
                       [allContacts addObject:@{@"fullName" : fullName, @"note" : note}];
                   }
               } @catch (NSException *exception) {
                   NSLog(@"exception == %@", exception);
                   
               } @finally {
                   
               }

然后发现:

exception == A property was not requested when contact was fetched.
exception == A property was not requested when contact was fetched.
...

所以,既然苹果粑粑不让用,在正常情况下就不要试图去申请,也不要视图去获取,因为你粑粑终究是你粑粑,如果非要不听话,那就要准备好背锅.然后删掉

contact.note

调用,代码正常运行.

最后我们可以在此处添加异常检测代码,以防止以后此处再次发生改动时可以及时找到原因.

- (void)getAllContacts:(void(^)(NSArray *))result {
    getAuthrization(^(BOOL granted){
        if (granted) {
            CNContactStore *contactStore = [[CNContactStore alloc] init];
            CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactMiddleNameKey, /*CNContactNoteKey*/]];
            NSError *error = nil;
            __block NSMutableArray  *> *allContacts = @[].mutableCopy;
            BOOL fetchSuccess = [contactStore enumerateContactsWithFetchRequest:fetchRequest error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
                
                @try {
                    NSString *familyName = contact.familyName;
                    familyName = isAvailableString(familyName) ? familyName : @"";
                    
                    NSString *middleName = contact.middleName;
                    middleName = isAvailableString(middleName) ? middleName : @"";
                    
                    NSString *givenName = contact.givenName;
                    givenName = isAvailableString(givenName) ? givenName : @"";
                    
                    NSString *note = /*contact.note*/@"";
                    note = isAvailableString(note) ? note : @"";
                    
                    NSString *fullName = [NSString stringWithFormat:@"%@%@%@", familyName, middleName, givenName];
                    if (isAvailableString(fullName)) {
                        [allContacts addObject:@{@"fullName" : fullName, @"note" : note}];
                    }
                } @catch (NSException *exception) {
                    NSLog(@"exception == %@", exception);
                    //添加异常检测
                    [WEDExceptionManager sendExcetion:@"ContactEnumerateException" description:exception.description];
                    
                } @finally {
                    
                }
            }];
            if (!fetchSuccess || error) {
                //添加异常检测
                [WEDExceptionManager sendExcetion:@"ContactEnumerateException" description:error ? error.description : @"" ];
            }
            if (result) {
                result(allContacts);
            }
        }
    });
}

 

你可能感兴趣的:(开发小技巧)