Click on UIAlertView crashes app if view is dismissed
Asked Answered
S

6

5

A UIAlertView is displayed if an error occurs. But in the meantime the view on which the UIAlertView were called has been dismissed (and therefore released). If the user clicks on OK the app crashes because a message to a released instance is sent. This will cause your app crashing:

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"test" message:@"test" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
alertView = nil;
[self.navigationController popViewControllerAnimated:YES];

I thought the UIAlertView is an independent unit. But it seems it isn't. Is there a way how I could avoid the app crashing (except not dismissing the view)?

Sadoff answered 14/10, 2010 at 23:4 Comment(0)
F
10

The delegate is called when the UIAlertView is dismissed, so in your case:

delegate:self

Delegates are not retained, like an object added to an array, or a subview would be. So in your case, when you call:

[self.navigationController popViewControllerAnimated:YES];

self is most likely being released, and when the the user dismisses the alert, self is called, but has been dealloc'd so it no longer exists.

An easy way to check this is to put a logger statement, like NSLog(@"I'm gone"); in self's dealloc method, if it's ran, then you know your self isn't around anymore, and any messages sent to it will cause a crash.

Flatcar answered 14/10, 2010 at 23:24 Comment(3)
Nice explanation. But what can I do against this?Sadoff
You have two choices, 1) pick a different delegate, or 2) have self retained by some other object (i.e. from other object [the-reference-to-what-were-calling-self retain]) Without knowing what you are doing in the delegate methods it's hard to say which is best. If you aren't using delegate methods, the have nil be the delegateFlatcar
I propose a different choice in my answer below: simply unset the delegate when the view is deallocated.Lyris
L
5

Make the UIAlertView a retained property of your view controller so that you can refer to it in your dealloc, then set the alert view's delegate to nil when the view controller is deallocated.

Be sure to properly release the retained alert view once it's been dismissed and on dealloc.

For instance:

@interface MyViewController : UIViewController <UIAlertViewDelegate> {
    UIAlertView *alertView;
}

@property (nonatomic, retain) UIAlertView *alertView;



@implementation MyViewController

@synthesize alertView;

- (void)showAlert {
    if (alertView) {
        // if for some reason the code can trigger more alertviews before
        // the user has dismissed prior alerts, make sure we only let one of
        // them actually keep us as a delegate just to keep things simple
        // and manageable. if you really need to be able to issue multiple
        // alert views and be able to handle their delegate callbacks,
        // you'll have to keep them in an array!
        [alertView setDelegate:nil];
        self.alertView = nil;
    }
    self.alertView = [[[UIAlertView alloc]
                       initWithTitle:@"Error"
                       message:@"Something went wrong"
                       delegate:self
                       cancelButtonTitle:@"Cancel"
                       otherButtonTitles:@"Retry",nil] autorelease];
    [alertView show];
}

- (void)alertView:(UIAlertView *)alertViewParam didDismissWithButtonIndex:(NSInteger)buttonIndex {
    self.alertView = nil; // release it
    // do something...
}

- (void)dealloc {
    [alertView setDelegate:nil]; // this prevents the crash in the event that the alertview is still showing.
    self.alertView = nil; // release it
    [super dealloc];
}

The downside here is that you will not be able to handle the alert view's callback when the user dismisses it. However, since your controller is already gone/released, presumably you don't need to. If you do, you have to set the alert view's delegate to something that will persist.

Lyris answered 22/3, 2012 at 6:9 Comment(2)
Awesome. Works great. As an addition to your suggestion, I added [alertView dismissWithClickedButtonIndex: 0 animated: YES]; to the first line of the dealloc so that the alertView is programmatically removed.Hah
Hello, above code is working fine, when you shows alert in same view controller. But when you go to another page and come back on this page and again try to show alert, alert view display, but its delegates methods are not calling. It is not dismissing automatically. When click on Cancel button, then it dismiss. [alertView dismissWithClickedButtonIndex: 0 animated: YES]; this line is not working when you are waiting for some task to complete and then dismiss.Paraphernalia
P
1

If the UIAlertView object is to be usable from anywhere in the app, not just on the current view, then retain it inside something that is available from anywhere in the app, either some persistant root view controller under the entire possible view stack, or the app delegate.

Added:

This top level object can also retain the alert view's delegate until after it's done being needed (after alert view dismissal).

Penhall answered 14/10, 2010 at 23:24 Comment(3)
The more important question is who retains the alert view delegate? That needs to be persistant.Penhall
Retained by the UIWindow that show puts it in. Yes, the alert view delegate should still be around when the alert view dismisses or be set to nil when the delegate is released.Etka
I also thought about this. But this a little too complicated for this special case I think. This only happens if the user makes an action twice, where one of this action fails because of the little time for completion. In no other case I would need this.Sadoff
J
1

(People might wonder I am late by years in answering this question,but it might help someone else)

I guess your problem lies some where in popping the view controller,you are displaying the alert view and at the same time trying to navigate the user back to a view.I would recommend you to follow a hierarchal approach here i.e.:

First of all declare your alert view as a global object,i.e.

@property(nonatomic,retain) UIAlertView *sampleAlert;

Now write your alert view display code where ever desired,say for instance:

-(IBAction)buttonClicked:(id)sender
{
  self.sampleAlert = [[UIAlertView alloc] initWithTitle:@"test" message:@"test" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
  [sampleAlert show];
  [sampleAlert release];
}

Finally try to navigate the user to the desired view when the "Ok" button is pressed,i.e. you need to make use of alertView didDismissWithButtonIndex method,i.e.

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex 
{
   if(alertView == sampleAlert)
   {
     [self.navigationController popViewControllerAnimated:YES];
   }
}

Please note that if you have alert view with multiple buttons,you also need to check for button index for distinguishing actions,i.e. check using

if(alertView == sampleAlert && buttonIndex == 0)
{
  //Do your stuff
}
else
{
  //Do something else
}

This will definitely avoid application crash,thanks :)

Justificatory answered 6/9, 2013 at 8:16 Comment(0)
D
1

Easier way that worked for me is to hold all the alert views in a Array and when the parent view is deallocated enumerate alertViews array and assign the delegate to nil. This will ensure that the touch event is ignored and app will function.

// ARC world

@property (strong, nonatomic) NSMutableArray *alertViews;

- (void)dealloc
{
    [self.alertViews makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil];
}
Doorbell answered 3/10, 2013 at 22:15 Comment(0)
E
-2

Make sure you are implementing the UIAlertViewDelegate protocol. If you don't care about when the alert is dismissed just init with delegate as nil.

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"test" message:@"test" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
Etka answered 14/10, 2010 at 23:16 Comment(2)
I have implemented the UIAlertViewDelegate protocol extra for this alert. It seems there is no other way than providing only a "OK" button and set the delegate to nil. I'll give it a try.Sadoff
This is not a correct answer. SooDesuNe's answer is correct. You need to either not set a delegate, or make sure to UNSET it if the delegate will be released.Carrion

© 2022 - 2024 — McMap. All rights reserved.