I am upgrading my Application written a year ago for iOS 6 to iOS 7/8 and I am getting this EXC_BAD_ACCESS error which never occurred in my old version.
In my application I am trying to fetch certain contact information like first name, last name, phone numbers, photo. Application flow is as follow:
1) Click on a button, presents address book.
2) Select any contact.
3.1) If contact has only one phone number, update the label.
3.2) If contact has multiple phone number, represent them in action sheet and whatever number user selects update that number to UILabel.
Now, if a contact has a single phone number application works fine without crash. i.e. 1-->2-->3.1 path. But if a contact has multiple phone and as soon as one contact number is selected from action sheet it crashes at this line.
CFTypeRef firstNameCF = (__bridge CFTypeRef)(CFBridgingRelease(ABRecordCopyValue(sharedSingleton.personGlobal, kABPersonFirstNameProperty)));
Detail Code
1) Select a contact
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
sharedSingleton.personGlobal = nil;
sharedSingleton.personGlobal=person; // ====> Save a ABRecordRef object globally.
//^^^ Could this be a culprit? I tried to make it private variable also at first.
[self displayAndVerifyPerson]; // No 2 below.
[self dismissViewControllerAnimated:YES completion:^{
}];
return NO;
}
2) Will check how many phone nos person has got. 0/1/>1.
If 0 show no phone no error.
If 1 phone update label by calling updateLabel.
If >1 represent action sheet for user to select number. And on clickedButtonIndex call updateLabel.
-(void)displayAndVerifyPerson
{
ABMultiValueRef phoneNumbers = ABRecordCopyValue(sharedSingleton.personGlobal,kABPersonPhoneProperty); //ABRecordRef which globally saved.
globalContact=nil; //NSString to store selected number. Works fine.
//self.personGlobal=person;
NSArray *phoneNumberArray = (__bridge_transfer NSArray *)ABMultiValueCopyArrayOfAllValues(phoneNumbers);
CFRelease(phoneNumbers);
if (ABMultiValueGetCount(phoneNumbers) > 0){ //Check if a contact has any number
NSLog(@" Number--> %@",phoneNumberArray); //Prints numbers correct whether no of contacts are 0/1/>1.
if ([phoneNumberArray count]==1){ //If exactly one contact number no problem.
globalContact = [phoneNumberArray objectAtIndex:0];
NSLog(@"--> %@",globalContact);
[self updateLabel]; // No 3 Below.
}
// We have multiple numbers so select any one.
else{
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"Select Number"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:nil];
actionSheet.delegate=self;
actionSheet.tag=0;
for(int i=0;i<[phoneNumberArray count];i++){
[actionSheet addButtonWithTitle:[phoneNumberArray objectAtIndex:i]];
}
[actionSheet addButtonWithTitle:@"Cancel"];
actionSheet.destructiveButtonIndex = actionSheet.numberOfButtons - 1;
actionSheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent;
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
if ([window.subviews containsObject:self.view])
[actionSheet showInView:self.view];
else
[actionSheet showInView:window];
}
}
else{ //No contact found. Display alert.
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error"
message:@"No contact numebr found."
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[av show];
return;
}
}
3) Fetch first name, Last name, Image from ABRecordRef Object.
-(void)updateLabel{
// ----------------- Get First Name From Global ABRecordRef personGlobal---------------------
CFTypeRef firstNameCF = (__bridge CFTypeRef)(CFBridgingRelease(ABRecordCopyValue(sharedSingleton.personGlobal, kABPersonFirstNameProperty)));
^^^^^^^^^^^^^^^^^^^^^^
Crashes only when `updateLabel` called from Actionsheet delegate `clickedButtonAtIndex`
NSString *fName = (NSString *)CFBridgingRelease(firstNameCF);
if ([fName length]==0){
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error"
message:@"Contact name not found."
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[av show];
return;
}
self.lblFirstName.text = fName; //Set label with first Name.
self.lblHomePhone.text = self.globalContact;//Set number label.
}
4) Actionsheet Delegate
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *buttonTitle=[actionSheet buttonTitleAtIndex:buttonIndex];
if(actionSheet.tag==0){
//Printing multiple phone numbers which works and prints perfect.
NSLog(@"Lets see what you got: ===> %@",buttonTitle);
if([buttonTitle isEqualToString:@"Cancel"])
return;
globalContact=buttonTitle; // Save contact to NSString for later use.
[self updateLabel]; // No. 3.
}
}
Extra Notes:
1) Questions I looked for solution(Just 3 of many).
i) ABRecordCopyValue() EXC_BAD_ACCESS Error
ii) EXC_BAD_ACCESS when adding contacts from Addressbook?
iii) kABPersonFirstNameProperty… trowing EXC_BAD_ACCESS
2) Sample project on dropbox if someone is generous/curious enough and wants to run and check.
3) My doubts regarding this error:
The same code works for a current App (Written for iOS 6) which is on App Store but crashes for iOS 7.
Could be due to Memory management of Core Foundation. I tried to release Core Foundation object wherever I used as ARC does not take care of them. But if that is a case then it should also crash while contact has only one phone number.
THREAD ISSUE? Since application only crashed shen contact has more than one phone number, I believe action sheet delegate method
clickedButtonAtIndex
running on background thread and something is going wrong? (Just a random guess!)I have tried to make my question easy and informative at my best. Any suggestion, comment or solution will be appreciated as I have been trying to get rid of this issue for last 3 days. Thanks!