UIViewController Presented from Table View - Not being destroyed when popped - iphone

I have a table view which presents a UIViewController whenever a row is tapped (displays details for that particular row).
The code is as follows :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!detail) {
detail = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
}
PlaceObject *info = [locationInfo objectAtIndex:indexPath.row];
detail.UniqueID = info.UniqueID;
detail.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:detail animated:YES];
self.detail = nil;
[detail release];
}
The problem is that "detail" does not seems to be destroyed when it is popped from the stack (when the user goes back to the table view).
I have a number of IBOutlets and Variable in the "detail" UIViewController class which I release in dealloc as follows :
- (void)dealloc
{
NSLog(#"Deallocing");
[storedURL release];
[storedNumber release];
[storedLocation release];
[nameLabel release];
[postCode release];
[description release];
[openTime release];
[nearestTube release];
[area release];
[image release];
[name release];
[phoneNumber release];
[scroll release];
[picture release];
[addressOne release];
[cost release];
[super dealloc];
}
Can anybody advise why "details" may not be being destroyed when the user returns to the table view ?
EDIT
Ok the above code now works perfectly. The key seemed to be setting detail to nil - not sure why though.

Couple things:
You are alloc/init'ing it but not releasing it. That's a problem.
You are setting it to a property (which is probably set to retain). That's a problem because of (1).
So, to fix this, release the object after you are done with in in the local scope (end of that method). And since it is a property, release it in the dealloc method of the owning class.

alloc/init - retain count: 1
setting with property - retain count: 2
pushing onto nav. controller - retain count: 3
popping off nav. controller - retain count: 2
This is making the assumption that the property has the retain flag but either way, your retain count is never getting to 0

Assume that detail is a retained property. Override the getter to do lazy instantiation.
- (DetailViewController *)detail
{
if (!detail)
{
detail = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
detail.hidesBottomBarWhenPushed = YES;
}
return detail;
}
Additionally be sure that you're sending -release to detail in -dealloc only. Your -tableView:didSelectRowAtIndexPath: now looks like this.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
detail.UniqueID = [locationInfo objectAtIndex:indexPath.row].UniqueID;
[self.navigationController pushViewController:self.detail animated:YES];
}

Related

how to fix iOS leak when flipping between two views

My iPhone app badly leaks when flipping back and forth between a main uiviewcontroller and a help uiviewcontroller .
Here is the source of the main view, followed by source of the help view.
MAIN VIEW - FLIP TO HELP.....................
// Changes from operational view to Help view.
- (IBAction)showHelp:(id)sender
{
// End trial mode:
self.stop_trial_if_started;
self.rename_trial_if_edited;
// Switch to trial help:
help_view_context = 0;
HelpView *controller = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
}
HELP VIEW - INIT.............................
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];
help_scroll.editable = FALSE;
return;
}
HELP - RETURN TO MAIN VIEW.........................
// User clicked the button to return to operational view:
- (IBAction)done:(id)sender {
NSLog(#"help- done");
if( help_view_context == 0 ) {
[self.delegate trial_help_DidFinish:self];
}else{
[self.delegate file_help_DidFinish:self];
}
}
MAIN VIEW - RETURN FROM HELP...............................
// Inits operational view when user changes from Help view back to operational view.
- (void)trial_help_DidFinish:(HelpView *)controller {
NSLog(#"trial_help_DidFinish");
[self dismissModalViewControllerAnimated:YES];
self.init_trial_operation;
}
You are creating a controller with ref count of 1 and a local reference each time showHelp: is called:
HelpView *controller = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
you are losing your reference to it at the end of this method.
You happen to have references to it in done: (self) and *_help_didFinish (controller), but you never release it in either of those locations. Dismissing the controller is fine, but you also have to release it.
(Another option would be to never create a second one, and maintain an iVar to the original.)
You could well be leaking on this line
controller.delegate = self;
What is your property declaration for the delegate. If it's anything other than assign, then you either need to change it (preferred option) or make sure you are releasing it in the dealloc method of HelpView controller.

App crashes at [UIWebView webView:didReceiveTitle:forFrame:]

I am implementing a simple in-app browser. In my home view (UITableViewController), I have something like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WebViewController *webViewController = [[WebViewController alloc] init];
switch (indexPath.row) {
case 0:
webViewController.stringURL = #"http://www.google.com";
break;
case 1:
webViewController.stringURL = #"http://www.bing.com";
break;
default:
webViewController.stringURL = #"http://stackoverflow.com";
break;
}
[self.navigationController pushViewController:webViewController animated:YES];
[webViewController release];
}
The app crashed after I repetitively navigated back and forth between my home view and webViewControllera few times.
Inside WebViewController class, I have nothing but a [UIWebView *webView] and a [UIActivityIndicatorView *activityIndicator]. Both are with attributes nonatomic, retain. Here is the implementation.
#import "WebViewController.h"
#implementation WebViewController
#synthesize webView, activityIndicator, stringURL;
- (void)dealloc
{
[self.webView release];
self.webView.delegate = nil;
[self.activityIndicator release];
[super dealloc];
}
-(void)loadView {
UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
self.view = contentView;
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
webFrame.origin.y = 0.0f;
self.webView = [[UIWebView alloc] initWithFrame:webFrame];
self.webView.backgroundColor = [UIColor blueColor];
self.webView.scalesPageToFit = YES;
self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.webView.delegate = self;
[self.view addSubview: self.webView];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.stringURL]]];
self.activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicator.frame = CGRectMake(0.0, 0.0, 30.0, 30.0);
self.activityIndicator.center = self.view.center;
[self.view addSubview: self.activityIndicator];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadView];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityIndicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[activityIndicator stopAnimating];
}
#end
I just ran my app in Instruments using the Zombies template, which shows -[UIWebView webView:didReceiveTitle:forFrame:] is the Zombie call. But I still can’t figure out what is actually the problem.
(Please download trace if needed)
Any help is greatly appreciated!
[Update]:
As #7KV7 and #David pointed out, there is an obvious bug in my dealloc function. I should call self.webView.delegate=nil; first before I release self.webView. Sorry about that. Unfortunately, after I fix it, the app still crashes in the same way.
If I delete [webViewController release]; from the first code block, the crash actually is gone. But obviously, there will be memory leak.
First of all, remove that call to loadView in viewDidLoad. The framework will the call the method when it doesn't find a view provided in XIB file. Second, your loadView is filled with memory leaks. You are allocating, initializing and retaining an object every time the method is called. So you are taking ownership twice and releasing it only once in the dealloc.
The objects are not being properly deallocated. You should do something like alloc-init-autorelease to solve this. Next thing is the that every time the controller gets loaded, because of your call to loadView, you end up creating two web view objects and two requests. You lose reference to one of them as you reassign. Herein, lies the problem mentioned in the title. You aren't able to reset the delegate of a web view object that has your controller as a delegate. Imagine a request being completed soon after you leave. Here the message will go to a zombie object. This is a pretty good example for why you need to nil out your delegates.
- (void)dealloc
{
self.webView.delegate = nil;
[self.webView release];
[self.activityIndicator release];
[super dealloc];
}
Try this dealloc. You were releasing the webview and then setting the delegate as nil. You should first set the delegate as nil and then release it. Hope this solves the issue.
I think what's happening is that you are going back while the page is still loading so the controller gets deallocated and then the webview finishes loading.
Try calling [webView stopLoading] in your viewDidUnload method to make sure this isn't happening.
Don't know if it's the cause of your problem, but this is definitely wrong:
[self.webView release];
self.webView.delegate = nil;
You cannot (safely) refer to self.webView after you release it!
Instead of pushing webViewController,add its view to self.view .
Dont call [self loadView]; in viewDidLoad.

Modal View Not Working

I am using the following code in my view controller and I want it to present another view controller called "chooserViewController" modaly
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
{
[self presentModalViewController:chooserViewController animated:YES];
}
I am getting a compile error not recognizing "chooserViewController". Am I doing it wrong?
Update:
- (void)add:(id)sender
{
RoutineExerciseChooserViewController *routineExerciseChooserViewController = [[RoutineExerciseChooserViewController alloc] initWithNibName:#"RoutineExerciseChooserViewController" bundle: nil];
[self presentModalViewController:routineExerciseChooserViewController animated:YES];
[routineExerciseChooserViewController release];
}
You need to create chooserViewController:
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated {
ChooserViewController *chooserViewController = [[ChooserViewController alloc] initWithNibName:#"ChooserView" bundle: nil];
[self presentModalViewController:chooserViewController animated:YES];
[chooserViewController release];
}
If you're not loading from a nib, obviously you'll use a different way to create chooserViewController, but you have to do something to ensure it exists, and can then be presented.

The dealloc method is not called in the present modal view contrller

It is in My view controller
-(void)doctorsListAction
{
if(isFirst == YES)
{
[self getDoctorsListController];
[[self navigationController] presentModalViewController:doctorListViewNavigationController animated:YES];
[doctorListViewController release];
}
}
-(void)getDoctorsListController
{
//DoctorListViewController *doctorListViewController=[[[DoctorListViewController alloc]initWithNibName:nil bundle:nil]autorelease];
doctorListViewController=[[DoctorListViewController alloc]init];
doctorListViewNavigationController=[[UINavigationController alloc]initWithRootViewController:doctorListViewController];
doctorListViewController.doctorList=doctorList;
doctorListViewNavigationController.navigationBar.barStyle= UIBarStyleBlackOpaque;
[doctorListViewController release];
}
It is in DoctorListViewContrller
-(void)closeAction
{
printf("\n hai i am in close action*******************************");
//[doctorList release];
//[myTableView release];
//myTableView=nil;
printf("\n myTableView retainCount :%d",[myTableView retainCount]);
[[self navigationController] dismissModalViewControllerAnimated:YES];
}
//this method is not called I don't know why if it not called i will get memory issues
- (void)dealloc
{
printf("\n hai i am in dealloc of Doctor list view contrller");
[doctorList release];
[myTableView release];
myTableView=nil;
[super dealloc];
}
this method is not called I don't know
why if it not called i will get memory
issues
When exactly dealloc gets called (i.e. when the object is deallocated) shouldn't really matter to you. What matters is that you pair up each alloc with a release/autorelease. Which you are likely not doing.
The above code doesn't read very well and looks a bit "Java"-ish. Your "get" method doesn't actually return anything, which looks strange. But you normally wouldn't name a method "get___" anyway.
You're probably leaking memory in your getDoctorsListController method on this line:
doctorListViewNavigationController=[[UINavigationController alloc]initWithRootViewController:doctorListViewController];
Since you didn't define doctorListViewNavigationController in this method, and I assume you posted code that compiles, it is either a member (although not necessarily a property) of your class or a static variable somewhere. Which means it could already be pointing to an object. Which means when you assign a new alloc'ed object to it, the old one is lost (leaked).
Here's how you should refactor it.
- (void)doctorsListAction
{
if (isFirst == YES)
{
[self showDoctorsList];
}
}
- (void)showDoctorsList
{
DoctorListViewController* doctorListViewController = [[DoctorListViewController alloc] initWithNibName:nil bundle:nil];
doctorListViewController.doctorList = doctorList;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:doctorListViewController];
navController.navigationBar.barStyle = UIBarStyleBlackOpaque;
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[doctorListViewController release];
}
There might be a lot of other objects 'behind the scenes' that want to keep the DoctorListViewController around. If you just balance out your retains and releases, you should be ok.
Also in -(void)doctorsListAction, shouldn't [doctorListViewController release]; be [doctorListViewNavigationController release]; instead?

Is dealloc method called in presentModalViewController when dismiss the controller

here the following code is used to view the present modal view controller.
[[self navigationController] presentModalViewController:doctorListViewNavigationController animated:YES];
the close action is in the next view controller(DoctorListViewController). You can understand by seeing the following code I added now cleary.
-(void)doctorsListAction
{
if(isFirst == YES)
{
[self getDoctorsListController];
[[self navigationController] presentModalViewController:doctorListViewNavigationController animated:YES];
}
}
-(void)getDoctorsListController
{
DoctorListViewController *doctorListViewController=[[DoctorListViewController alloc]init];
doctorListViewController.doctorList=doctorList;
doctorListViewNavigationController=[[UINavigationController alloc]initWithRootViewController:doctorListViewController];
doctorListViewNavigationController.navigationBar.barStyle= UIBarStyleBlackOpaque;
[doctorListViewController release];
//code in next DoctorListViewContrller to dismiss the view.
//code for dismiss the ModalViewController.
-(void)closeAction
{
[[self navigationController] dismissModalViewControllerAnimated:YES];
}
My problem is the dealloc method is not called then I am getting memory issue problems like object allocations, leaks..
- (void)dealloc
{
[doctorList release];
[myTableView release];
[super dealloc];
}
Dealloc method is called when object is released same number of times, as it was retained.
When you add doctorListView... (let's call it view) to navigationController (let's call it controller), the controller retains the view. And it was also retained during creation. That's why you should release this view twice: one time with dismissModalView... and one with direct release.
I mean something like this:
[[self navigationController] presentModalViewController:doctorListViewNavigationController animated:YES];
[doctorListViewNavigationController release]; // first time
...
- (void)closeAction {
[[self navigationController] dismissModalViewControllerAnimated:YES];
// second time
}