Here is the code I have:
Phone SDK undestanding cocoa object live cycle:
- (void) DismissWelcomeMessage: (UIAlertView *) view
{
[view dismissWithClickedButtonIndex:0 animated:YES];
}
- (void) ShowWelcomeMessage
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Blah" message:#"Blah Blah" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[self performSelector:#selector (DismissWelcomeMessage:) withObject: alert afterDelay: WELCOME_MESSAGE_DELAY];
[alert release];
}
ShowWelcomeMessage is called first.
Why DissmissWelcomeMessage works fine and does not crash even though alert object is released?
Is because Dismiss function uses copy of the object passed on stack as a parameter when function? But even then would not it be just a copy of the pointer pointed to the now deallocated object?
Or [alert release] just decriment reference counting and does not really do the same as
delete in C++?
performSelector retains the object, thus your release doesn't cause its retain count to go to zero.
See NSObject docs
This method retains the receiver and the anArgument parameter until after the selector is performed.
It's possible that performSelector is retaining the object passed in, which is why it's still valid when DismissWelcomeMessage is called.
Related
When I click the UIAletView, I receive the following error.
alertView:clickedButtonAtIndex:]: message sent to deallocated instance 0x84c7010
This is the code I have used.
UIAlertView *testAlert = [[ UIAlertView alloc]initWithTitle:messageTitle message:messageBody delegate:self cancelButtonTitle:messageClose otherButtonTitles:messageTryAgain, nil];
testAlert.tag = 2;
[testAlert show];
[testAlert release];
And I have the delegate method
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
}
When I click the UIAlertView , even before the control reaches the delegate method, the app crashes. What could be the reason. What am I doing wrong?
This is "one hack of a solution".
Hopefully it helps you understand that your delegate is the memory issue. The delegete (in this case self) is deallocated somehow before the testAlert is dismissed
// retain self to avoid crash you were experiencing earlier
UIAlertView *testAlert = [[ UIAlertView alloc]initWithTitle:messageTitle message:messageBody delegate:[self retain] cancelButtonTitle:messageClose otherButtonTitles:messageTryAgain, nil];
testAlert.tag = 2;
[testAlert show];
[testAlert release];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
// release self because you've gotten past the crash
[self release];
}
This is in no way an elegant solution and should encourage you to debug your app further to find out why self is being deallocated prematurely
Just wondering, could you show us your .h file?
If I had to hazard a guess, you've forgotten to set your class to respond to UIAlertViews as a delegate
You might be missing something like this:
#interface MyClass : UIViewController <UIAlertViewDelegate>
If ARC enable UIAlertView Object retain and no need to release, it's automatically release your object.
When writing code, there are many situations that must be treated as runtime errors : an alloc/init returns nil, a resource is not found, a [someClass canDoThis] returns NO for an absolutely-needed feature where YES would be the natural answer, ...
For all these situations, I have written an exitWithMessage routine (that displays an alert box), and each class has a kill method that frees allocated memory.
So... When in an init method, you have these kind of exceptions, I supposed you could do :
[self kill];
[OneClass exitWithFatalErrorMessage];
return nil;
- (void) exitWithFatalErrorMessage:(NSString*)message
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:NSLocalizedStringFromTable(#"Error" #"ErrorMessages", #"") message:message delegate:self cancelButtonTitle:NSLocalizedStringFromTable(#"Understood", #"ErrorMessages", #"") otherButtonTitles: nil];
[alert show];
[alert release];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// stop the normal running of the app, there is a situation that would prevent it
}
- (void)kill
{
self.member = nil;
self.member2 = nil;
...
}
But this does not work... My alert is not displayed (the exitWithMessage works fine when used anywhere else than into an init method.
How would you handle those cases ? Is this piece of coding a fine way to do ?
If yes, why does my alert do not display (I'm into a view controller initWithCoder method for the example) ?
Are you actually calling the exitWithFatalErrorMessage method, because in your code you call exitWithMessage instead, try changing it to this:
[OneClass exitWithFatalErrorMessage:#"Message"];
would you please look at that piece of code:
/* This app is a game, the user can click an "abort" button anytime,
* and he/she is therefore asked for confirmation ("really abort game?")
*/
- (IBAction)btnAbortClicked:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] init];
[alert setMessage:#"Really abort game?"];
[alert setDelegate:self];
[alert addButtonWithTitle:#"Yes"];
[alert addButtonWithTitle:#"No"];
[alert show];
[alert release];
}
/* Delegate method (I don't like it, I wish I had modal blocking windows) */
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0)
[self quitGame];
}
/* pop the view controller */
- (void)quitGame {
[self.navigationController popToRootViewControllerAnimated:YES];
}
The problem is simple - but apparently not enough for me to solve. The UIViewController gets popped, but doesn't get deallocated. And the problem is strictly related to the UIAlertView, because if I just call quitGame from btnAbortClicked:, the view controller is popped and immediately deallocated.
Instead, it seems some mysterious entity retains it.
Can you help me? Thanks in advance.
Well, I think that you're still inside the alertView when clickedButtonAtIndex is called. I'd suggest moving to alertView:disDismissWithButtonIndex instead, so that you're called after the alertview disappears.
I have several places where I need to display an alert and handle the dismiss operation in the same way, namely take them to the same view controller. Instead of duplicating the alert specific code in all those places, I created a separate class like below:
AlertUtility.h:
#interface AlertUtility : NSObject {
}
- (void) displayAlert;
#end
AlertUtility.m:
#implementation AlertUtility {
- (void) displayAlert {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:... message:...
delegate:self cancelButtonTitle:... otherButtonTitles:nil] autorelease];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Create another view controller and display its views
[self release] // Release current object because I'm not releasing it where I create it
}
}
Where I need to use this alert (i.e. in MyViewController), I have the following:
MyViewController.m:
AlertUtility *utility = [[AlertUtility alloc] init];
[utility displayAlert];
As you can see, I am not releasing the utility object here (which I should since I own it), but rather in the didDismissWithButtonIndex method of the AlertUtility class. I am clearly missing something here.
I've tried autoreleasing the utility object but then by the time the didDismissWithButtonIndex method is 'called' on the utility object, that object was already released (due to the autorelease).
I've tried making the displayAlert method static (and calling it with [AlertUtility displayAlert];), but then the didDismissWithButtonIndex is never called.
Should I just release the current object from within the didDismissWithButtonIndex method, like I do now, or is there a way to release it in MyViewController instead (without creating a AlertUtility property for the current class)?
Thanks!
EDIT
Should I use the Singleton pattern instead maybe?
Calling [self release] is a big no-no because no object has information about what other object refer to it. The retention system is intended to make sure that if ObjA needs ObjB, that ObjB will remain in memory until ObjA no longer needs it. If ObjB releases itself, the entire system breaks down.
In this case, the UIAlert itself retains the AlertUtility object as it's delegate so you can release the AlertUtility as soon as your done with it just as you would a UIAlert.
So,this is fine:
AlertUtility *utility = [[AlertUtility alloc] init];
[utility displayAlert];
[utility release]; // released here, but still alive
It's not a problem if the AlertUtility object is still alive past the release. A release doesn't necessarily kill an object. It merely says that the object sending the release message has no further need of the released object. Each retaining object just has the responsibility to balance all retains (including implicit ones like init) with a release. Past that the retaining object has no interest and no responsibility for whether the system keeps released object alive or not.
After all, any single object may be retained by multiple retaining objects each with no knowledge of the other. In this case the AlertUtility is retained once by the ViewController with init. Then it is retained again by the UIAlert as a delegate. (Edit: UIAlert assigns it delegate)When ViewController releases the AlertUtility object, it will remain alive as long as the UIAlert needs it and vice versa.
Since the UIAlert object is retained by the UIWindow when it is displayed this means that the AlertUtility object will remain alive until after the UIAlert is dismissed. (Edit: this is valid only for the view, not the AlertUtility delegate)
You would not normally have a UIAlert delegate whose existence is solely dependent on that of the UIAlertitself. Usually, the UIAlert delegate is the view controller that evokes the alert. That way, you don't have to worry about the delegate dying before it completes the task associated with the alert.
You can use this instead:
void displayAlert(NSString *title, NSString *message)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
}
Or just:
#define MY_ALERT(TITLE, MESSAGE) \
[[[[UIAlertView alloc] initWithTitle:TITLE \
message:MESSAGE \
delegate:nil \
cancelButtonTitle:#"OK" \
otherButtonTitles:nil] autorelease] show]
And then write:
MY_ALERT(nil, #"Press, OK!");
Your pattern works, although I agree releasing it "elsewhere" is ugly and I try to avoid this. You could make the following approach:
// AlertUtility.h:
#interface AlertUtility : NSObject {
}
- (void) displayAlert;
#end
// AlertUtility.m:
#implementation AlertUtility {
- (void) displayAlert {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:... message:...
delegate:self cancelButtonTitle:... otherButtonTitles:nil] autorelease];
[alert show];
[self retain]; // explicitly retain this (self) object
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Create another view controller and display its views
[self release] // Release current object because you retained it earlier here
}
}
And later:
// MyViewController.m:
AlertUtility *utility = [[AlertUtility alloc] init];
[utility displayAlert];
[utility release]; // released here, but still alive
Edit: removed the release on the alert, as it was autoreleased.
My alertview appears twice and requires 2 user clicks to dismiss.
- (void) showAlert: (NSString *) message
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"You chose"
message: message
delegate: self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"ok",nil];
av.tag = SLIDER_ALERT;
[av show];
}
I am then releasing the alertview in the delegate method "alertView: (UIAlertView *) alertView clickedButtonAtIndex: (int) index"
I suspect the problem is that I have built my own view hierarchy programmaticaly. I have one parent view for the viewcontroller. Under that i have 2 other views (parentView -> subview1 and subview2). I've tried to call [self.view addSubview: av] but that does not work. Neither does bringToFrontSubView:
Any help would be much appreciated
Peyman
The Alert code is fine (other than the release, mentioned in the comments).
[av show] is all that's required to show a view. You don't add UIAlertViews as subviews.
Call it after a delay of 0.1 sec [self performSelector:#selector(showAlert:) withObject:nil afterDelay:0.10];
Hope this will solve your problem.
With using autorelease the Alert View seems to be twice or 3 times. And for iOS4 it needs to be autoreleased otherwise it will crash.