The Mysterious Case of the Retained UIViewController
When dealing with user interface elements and navigation controllers in iOS development, it’s not uncommon to encounter unexpected behavior. In this case, we’re exploring a peculiar issue where a UIViewController
fails to get deallocated after being popped from a navigation controller. We’ll delve into the world of memory management, retain counts, and the specific context of UIAlertView
s to uncover the root cause of this problem.
Understanding Memory Management in iOS
Before diving into the specifics of our issue, let’s take a quick tour of how memory management works in iOS:
- Automatic Reference Counting (ARC): In recent versions of Xcode, Automatic Reference Counting is enabled by default. This means that the compiler automatically manages the memory usage of your objects, ensuring they’re deallocated when no longer needed.
- Manual Memory Management: Before ARC, developers used manual memory management techniques, such as
retain
andrelease
, to control memory allocation.
When a view controller is popped from the navigation stack, it’s not immediately deallocated. Instead, it remains in memory until all references to it are released. This can lead to unexpected behavior, especially when working with complex UI elements like UIAlertView
s.
The Role of UIAlertView in Memory Management
UIAlertView
is a non-modal alert view that displays a message to the user and provides two buttons for response. When you present an UIAlertView
, it retains the current view controller as its delegate. This can cause issues when trying to pop the view controller from the navigation stack.
In our example, we see this problem in action:
- (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];
}
Here, we create an UIAlertView
instance and set ourselves as its delegate. This means the view controller is retained by the UIAlertView
, preventing it from being deallocated when popped from the navigation stack.
The Solution: Using(alertView:disMISSWithButtonIndex)
To resolve this issue, we can use the alertView:didDismissWithError:
method instead of alertView:clickedButtonAtIndex:
. However, there’s a catch – the alertView:didDismissWithError:
method is not called when the user clicks either button.
A better approach is to use alertView:disMISSWithButtonIndex:
and handle it within your view controller:
- (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];
}
However, this will not solve our problem as alertView:disMISSWithButtonIndex:
is called when the alert view is dismissed. So instead, we should use it to dismiss the view controller:
- (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];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0)
{
[self.quitGame];
}
}
- (IBAction)btnAbortClicked:(id)sender {
self.alertView = [[UIAlertView alloc] init];
[self.alertView setMessage:@"Really abort game?"];
[self.alertView setDelegate:self];
[self.alertView addButtonWithTitle:@"Yes"];
[self.alertView addButtonWithTitle:@"No"];
[self.alertView show];
}
- (void)alertView:(UIAlertView *)alertView dismisWithButtonIndex:(NSInteger)buttonIndex {
if(buttonIndex == 0)
[self quitGame];
else
[self.navigationController popToRootViewControllerAnimated:YES];
}
In our btnAbortClicked
method we set the alertView to nil, so that the view controller gets deallocated immediately after being popped from the navigation stack.
- (IBAction)btnAbortClicked:(id)sender {
self.alertView = nil;
self.alertView = [[UIAlertView alloc] init];
[self.alertView setMessage:@"Really abort game?"];
[self.alertView setDelegate:self];
[self.alertView addButtonWithTitle:@"Yes"];
[self.alertView addButtonWithTitle:@"No"];
[self.alertView show];
}
Last modified on 2025-02-08