【问题收集】iOS 13 Crash 处理

一,(AddressBook.framework)编辑通讯录 Crash

环境:
release 环境(pn1 release 或者 dn1 release)

场景:
编辑联系人信息

Crash 信息:

   2019-07-23 17:36:45.836520+0800 DingtoneBeta[2050:123112] *** Terminating app due to uncaught exception 'CNPropertyNotFetchedException', reason: 'Contact 0x13f2132e0 is missing some of the required key descriptors: (
    ""
    )'

代码如下:

- (void)editSystemContact {
    [[ServiceMgr sharedServiceMgr].GAITrack sendEventWithCategory:kUserOperatorUsageCategory
                                                       withAction:@"EditSystemContactFrom_ContactInfo"
                                                        withLabel:nil
                                                        withValue:nil];

    if (![[DTNetWorkAlertViewCtrl sharedInstance] isLoginDoneAndShowAlertView]) {
        return;
    }

    ABAddressBookRef addressBook = nil;
    if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
        addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
    } else {
        [NUtils showAlertUnableToAccessContactWithText:NSLocalizedString(@"Dingtone is unable to access contact information from the address book.To share a contact with your friend, Dingtone needs to access the address book.Please enable Dingtone access address book from Settings=>Privacy.", @"")];
        return;
    }

    ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID) self.contactInfo.localId);
    if (!person) {
        CFRelease(addressBook);
        return;
    }

    self.isJumpToEditSystemContact = YES;

    ABPersonViewController *personVc = [[ABPersonViewController alloc] init];
    personVc.displayedPerson = person;
    personVc.personViewDelegate = self;
    personVc.allowsEditing = YES;
    personVc.allowsActions = NO;
    personVc.shouldShowLinkedPeople = NO;
    personVc.addressBook = addressBook;
    self.personVc = personVc;
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:personVc];
    personVc.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Back", nil)
                                                                                 style:UIBarButtonItemStylePlain
                                                                                target:self
                                                                                action:@selector(editSystemPageDidClickBack)];
    [[ServiceMgr getBaseViewController] presentViewController:nav animated:YES completion:nil];

    CFRelease(addressBook);
}

分析流程:

在 iOS 13 上必定会 Crash,目前没有找到处理方法,只能通过 Contacts.framework 处理。

思路:
由于项目中使用的是 AddressBook.framework,通过 ABRecordID 来指定用户进行编辑,因此只能通过 ABRecordRef 对象获取 CN 对象的 identifier

typedef int32_t ABRecordID AB_DEPRECATED("use CN object's identifier");

通过调试可以看到 person 对象是一个 ABSPerson 类型,并且私有属性 _cnImpl 是一个 CNContact 对象,通过 CNContact 对象可以获取到 identifier

image.png

获取 identifier:
1,__bridge 获取 Objective-C 对象
2,通过 kvc 获取私有属性

CNContact *contact = [(__bridge NSObject *)person valueForKey:@"_cnImpl"];
NSString *identifier = contact.identifier;

最终解决代码:

- (void)editSystemContact {
    ABAddressBookRef addressBook = nil;
    if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
        addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
    } else {
        [NUtils showAlertUnableToAccessContactWithText:NSLocalizedString(@"Dingtone is unable to access contact information from the address book.To share a contact with your friend, Dingtone needs to access the address book.Please enable Dingtone access address book from Settings=>Privacy.", @"")];
        return;
    }
    
    ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID) self.contactInfo.localId);
    if (!person) {
        CFRelease(addressBook);
        return;
    }
    
    if (@available(iOS 13, *)) {
        // 获取 identifier
        CNContact *contact = [(__bridge NSObject *)person valueForKey:@"_cnImpl"];
        NSString *identifier = contact.identifier;
        NSLog(@"contact = %@, identifier = %@", contact, identifier);
        [[LJContactManager sharedInstance] editSystemContactWithIdentifier:identifier
                                                                controller:[ServiceMgr getBaseViewController]
                                                                 backTitle:NSLocalizedString(@"Back", nil)
                                                             editBackBlock:^{
                                                                 [[LJContactManager sharedInstance] setContactChangeHandler:nil];
        }];
        CFRelease(addressBook);
    } else {
        ABPersonViewController *personVc = [[ABPersonViewController alloc] init];
        personVc.displayedPerson = person;
        personVc.personViewDelegate = self;
        personVc.allowsEditing = YES;
        personVc.allowsActions = NO;
        personVc.shouldShowLinkedPeople = NO;
        personVc.addressBook = addressBook;
        self.personVc = personVc;
        UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:personVc];
        personVc.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Back", nil)
                                                                                     style:UIBarButtonItemStylePlain
                                                                                    target:self
                                                                                    action:@selector(editSystemPageDidClickBack)];
        [[ServiceMgr getBaseViewController] presentViewController:nav animated:YES completion:nil];
        
        CFRelease(addressBook);
    }
}

- (void)editSystemContactWithIdentifier:(NSString *)identifier
                             controller:(UIViewController *)controller
                              backTitle:(NSString *)backTitle
                          editBackBlock:(void(^)(void))editBackBlock {
    NSArray *keysToFetch = @[CNContactIdentifierKey,
                             CNContactGivenNameKey,
                             CNContactFamilyNameKey,
                             CNContactPhoneNumbersKey,
                             CNContactImageDataAvailableKey,
                             CNContactImageDataKey,
                             CNContactViewController.descriptorForRequiredKeys];
    NSError *error = nil;
    CNContactStore *store = [[CNContactStore alloc] init];
    CNContact *contact = [store unifiedContactWithIdentifier:identifier
                                                 keysToFetch:keysToFetch
                                                       error:&error];

    if (contact == nil) {
        NSAssert(NO, @"获取联系人错误");
        return;
    }
    
    self.editBackBlock = editBackBlock;
    
    self.editContactVc = [CNContactViewController viewControllerForContact:contact];
    
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.editContactVc];
    self.editContactVc.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:backTitle
                                                                                           style:UIBarButtonItemStylePlain
                                                                                          target:self
                                                                                          action:@selector(editBackAction)];
    
    [controller presentViewController:nav animated:YES completion:nil];
}

参考资料

  • Core Foundation内存管理
  • ARC 时代的内存管理
  • 【iOS沉思录】Foundation对象与Core Foundation对象的区别转换和内存管理权移交

你可能感兴趣的:(【问题收集】iOS 13 Crash 处理)