ABRecordCopyValue() EXC_BAD_ACCESS Error while getting kABPersonFirstNameProperty - ios

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!

you deal with CoreFoundation:
sharedSingleton.personGlobal=person;
=>
since it isn't an arc object, you have to retain it
CFRetain(person);
sharedSingleton.personGlobal=person;
AND release it once done
- dealloc {
CFRelease(sharedSingleton.personGlobal);
}

Ignoring the weirdness of a lot of this code, the fundamental issue is that you are not retaining a value that you intend to use beyond the scope it is presented in. Specifically, I am referring to the person variable in section number 1. You don't retain this variable, and so it is free to be released at any time after the scope ends (which it likely does). Therefore, once you get around to calling updateLabel it is simply a dangling pointer. To fix this, you should make it a strong variable.
But wait a minute...that is only for Objective-C objects, so you need to do a little more decorating of the property. You can add __attribute__((NSObject)) to make this type behave as if it were an NSObject and subject to ARC. I can't find documentation about this anymore, but here is a reference from an old Apple Mailing List Thread

Related

Access all UIAlertView object as a global object to control it Universally(to resolve CRASH in alert after deallocating the view contoller)

My requirement is to make my app's third tab as the home screen,and also whenever the user moves the app in background and while again moving the app to foreground it should be the home screen as our third tab instead the app is in any of the places in the app.
This issue is handled(making third tab as home screen instead the app is in any of the places/scenarios in the app).,But i have an issue now which is generated by this resolution.
Problem :- If any alert is displaying in any view and we are making the app background-foreground ,the app comes to the third tab from that screen,and the alert is still there and if we click on the alert button ,then as per the rules of IOS the "self" object of the screen of alert is deallocated(because now we are in the home screen and the alert is here) and app CRASHES !
Tried some resolutions :-
1.In a screen ,I made an global object of UIAlertView and using below line of code in applicationDidBecomeActive method of the Screen...
[_alertToRemoveContact dismissWithClickedButtonIndex:1 animated:NO];
This is working code for this view,but my problem with resolution is that i need to create a global object of alert view in all places of the app which is a very much time consuming task because i am using around 250 alerts in the project.
2.I am killing the app whenever it moves to background ,In this resolution the problem is that my app will not work its downloading functionality in background cause the app is killed.
Need help for the resolution of this issue if any one need more explanations,please leave comments.
My Crash Log....
* -[ContactShowViewController respondsToSelector:]: message sent to deallocated instance 0x1138c4e0*
*Where ContactShowViewController will differ a/c to the screen
Thanks in advance !!!
Let's try singleton:
__strong static AlertUtil* _sharedInstance = nil;
#implementation AlertUtil
{
UIAlertView *alertView;
}
+ (AlertUtil *)sharedInstance
{
#synchronized(self)
{
if (nil == _sharedInstance)
{
_sharedInstance = [[AlertUtil alloc] init];
}
}
return _sharedInstance;
}
- (void)showConfirmAlertWithMessage:(NSString *)msg cancelBtnTitle:(NSString *)btn1 okbtnTitle:(NSString *)btn2 delegate:(id)delegate tag:(int)tag
{
alertView = [[UIAlertView alloc] initWithTitle:nil message:msg delegate:delegate cancelButtonTitle:btn1 otherButtonTitles:btn2, nil];
alertView.tag = tag;
[alertView show];
}
- (void)cancel
{
if (alertView){
[alertView dismissWithClickedButtonIndex:0 animated:NO];
}
}
I have resolved this issue with the help of below solution :-
1.I created an app delegate instance of UIAlertView.
2.I implemented an alert view delegate method "will present alert view...",this method gives me all the alert view objects as a parameter where i have assigned it to my app delegate object of alert view.
3.On application life cycle method "applicationDidEnterBackground" i am using below code ...,which resigns my alert dailog on coming from background to foreground...
if ([AppDelegate shared].alertObserver)
{
//Dismissing alert which was shown before moving to background
[[AppDelegate shared].alertObserver dismissWithClickedButtonIndex:0 animated:NO];
[AppDelegate shared].alertObserver=nil;
}

"[UIAlertView show]" returns immediately without displaying message popup

I'm trying to implement an error popup function for iOS. My current implementation:
void SysErrorAlert(NSString * title, NSString * message, ...)
{
NSString * contents = nil;
va_list args;
va_start(args, message);
contents = [[NSString alloc] initWithFormat:message arguments:args];
va_end(args);
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:title
message:contents
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil];
[alert show];
// tried this but popup still never shows...
//for (;;) { }
}
However, "[alert show]" is returning immediately without ever displaying the popup dialog.
I need the dialog to be displayed on top of the current application screen and block the calling thread until the user clicks one of the buttons. The application will them terminate after the function returns.
The app is running Cocos2d, so maybe the Cocos drawing is interfering with the UIAlertView... But I'm rather new to iOS programming and may be missing something obvious here.
NOTE: I have not tested this on an actual device, only in the simulator. Could it be a limitation/bug of the simulator?
Looks like you have to ask cocos2d for help to get the right parent for the alertview
This older post suggests an outline:
http://www.cocos2d-iphone.org/forums/topic/how-to-popup-a-uialertview-with-cocos2d/

Alert after using sharedmanager

This is my code
audioViewController *voiceRecorder = [audioViewController sharedManager];
[voiceRecorder stopRecording];
NSString *msg = [NSString stringWithFormat:#"Want to logout?"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Info"
message:msg
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
alert.tag = 100;
[alert show];
I am calling sharedManager in one of my view controller. The problem is, my alertview runs before sharedManager method executes, if you check my code, i have called "StopReording" method, but when i run the code, it works after showing alert. Anyone has idea, how do I show alert only after the method returns something.?
You seem to be confusing yourself about method run order and alert presentation order. The methods run in the order specified by your code, they must. What you see on screen is 2 alerts, one (stop) presented first, the the other (logout) presented immediately after.
Generally, you shouldn't show 2 alerts at the same time. Certainly not if they relate to different things.
Present your first alert, then wait for the answer to be received (using the delegate methods). Once you have the users answer, then decide what to do next and present the second alert or continue with some other operation.

How to set a max number of friends using FBFriendPickerViewController?

I've been working with Facebook to make a multiplayer game. I want to allow the player to be able to invite his friends from Facebook to play in a match, so I use FBFriendPickerViewController for this. However, I want to limit the number of selected friends to a minimun of 1 player and a maximun of 4.
The problem is that there's no obvious way to do this, or at least none mentioned in the Developer documents at Facebook. I tried to prevent this inside
- (void)friendPickerViewControllerSelectionDidChange:(FBFriendPickerViewController *)friendPicker
but as the attribute NSArray *selection is readonly, it can't be done. I had also thought of warning the user after he had selected the friends and clicked the 'Done'button, but it's kind of lame allowing him to choose 100 friends and after that warning him about the 4 players max limit.
Does anyone have any idea how to do this? Or will I have to implement a full FBFriendPickerViewController from scratch?
Thanks! :D
One work around could be showing a label/message on the picker letting the user know they can pick up to 4 friends. Then after picking four you dismiss the view controller? Then you could add code like this:
- (void)friendPickerViewControllerSelectionDidChange:
(FBFriendPickerViewController *)friendPicker
{
if ([friendPicker.selection count] > 3) {
UIAlertView *alertView =
[[UIAlertView alloc] initWithTitle:#""
message:#"Max number of friends selected."
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self dismissModalViewControllerAnimated:YES];
}
You may be looking for a better experience, for example to give users a chance to edit from their maximum list. In that case you can get the source code from GitHub tableView:didSelectRowAtIndexPath: and tableView:didDeselectRowAtIndexPath: delegate methods in the FBGraphObjectTableSelection class. Looks like you would likely add a new "maxSelection" property and key off that.
Previous SDK was HTML based and was hosted on facebook but SDK 3.x is native iOS code with open source.
You can modify Facebook SDK. It is licensed under Apache license and add limit for friends you allow to invite.
just removing the friend picker isn't the solution! but this is!
i got it , after struggling for quite a few times.
solution was simpler than i imagined.
you friendPickerController is a tableView so we can set userInteractionEnabled property to NO.
- (void)friendPickerViewControllerSelectionDidChange:
(FBFriendPickerViewController *)friendPicker
{
if ([friendPicker.selection count] <=3)
{
self.friendPickerController.tableView.userInteractionEnabled=YES;
}
if ([friendPicker.selection count] >=3)
{
UIAlertView *maxFriendsAlert =
[[UIAlertView alloc] initWithTitle:#"Max number of friends selected."
message:#"no more friends can be selected,"
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:#"Buy more friends",nil];
[maxFriendsAlert show];
maxFriendsAlert.tag=1;
// disable friends selection
self.friendPickerController.tableView.userInteractionEnabled=NO;
}
Replying a bit late, but I was just looking for a solution to this issue, and I went with something that I found to be a bit cleaner than the other solutions listed:
- (void)friendPickerViewControllerSelectionDidChange:(FBFriendPickerViewController *)friendPicker {
if ([friendPicker.selection count] > 3) {
friendPicker.doneButton.enabled = NO;
[[[UIAlertView alloc] initWithTitle:#"Too many selections"
message:#"You may only select up to 3 friends."
delegate:nil
cancelButtonTitle:#"Ok" otherButtonTitles:nil] show];
} else {
friendPicker.doneButton.enabled = YES;
}
}
I inform the user that they have exceeded the max (via UIAlertView), then I disable the Done button. When the count comes back down to a valid number, I re-enable the Done button.

UIAlertView runModal

Followup to Where is NSAlert.h in the iOS SDK?
Is there any way to get NSAlert runModal like behavior from a UIAlertView? Or from a UIActionSheet?
I'm planning on using only in debug builds so I'm not concerned with how it looks or if it uses undocumented functionality.
Edit:
NSAlert is part of the OS X SDK and is similar to MessageBox in Win32. It allows you to synchronously prompt the user for something. Here's an example:
NSAlert * myAlert=[[NSAlert alloc] init];
[myAlert setMessgeText:#"This is my alert"];
[myAlert addButtonWithTitle:#"button 1"];
[myAlert addButtonWithTitle:#"button 2"];
switch ([myAlert runModal]) {
case NSAlertFirstButtonReturn:
//handle first button
break;
case NSAlertSecondButtonReturn:
//handle second button
break;
}
runModal is a synchronous function, it shows the alert and waits for user response. Internally it is running a limited version of the message loop, but as far as the rest of my application is concerned, the world has stopped; no messages, no events, nothing.
Internally it is running a limited version of the message loop, but as far as the rest of my application is concerned, the world has stopped
Just do exactly what you described: throw up the alert, then run the event loop till the alert view gets dismissed. This code works:
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"O rlly?" message:nil delegate:nil
cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
NSRunLoop *rl = [NSRunLoop currentRunLoop];
NSDate *d;
while ([alert isVisible]) {
d = [[NSDate alloc] init];
[rl runUntilDate:d];
[d release];
}
[alert release];
You'll have to write your own if you want this behavior. Careful, if you block the main queue for too long, your app will be watchdog'd.
UIAlertView gives you modal behavior, and will end up working the same way your custom class will. You might consider using a block-based wrapper that wraps up UIAlertView and allows you to setup blocks for the button action callbacks.

Resources