一,(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
获取 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对象的区别转换和内存管理权移交