Memory issue when Pushing and Popping UINavigationController in Objective C - iphone

I have two view controllers VC1 and VC2. i try to push VC2 by creating object of VC2 and pushing it on button action.
VC2 *vcOject = [[VC2 alloc]init];
[self.navigationController pushViewController:vcOject animated:YES];
[vcObject release];
in VC2 i am popping it in button action..
[self.navigationController popViewControllerAnimated:YES];
now i am in VC1 and again Pushing VC2 for display. but this time i am getting EXC_BAD_ACCESS error. but when i commented [vcObject release]. it works fine. but when can i release memory. how memory is handling in navigation controller.

Simple answer: You should be using ARC.
If for whatever reason you can't, your view controller is deallocated once it's popped off the stack, and you should alloc/init a new one every time you want to push it on to the stack. Or if you don't want to do that - say, if the view in VC2 is very heavy - get rid of your [vcObject release] and keep a reference to vcObject in VC1, and keep pushing the same one on to the stack. Make sure you don't mix these two, though; if you create a new object every time you must release it after pushing it on to the stack, and if you are keeping a reference to push the same VC2 on the stack each time, you must not release it until VC1's dealloc. Keep in mind that you must release it in VC1's dealloc method, or else the memory will be leaked.
But, really. You should be using ARC.

If you don't use [vcObject release]. it will cause memory leaking to your application.
so, This is the way to handle memory management with push/pop viewController without using ARC
VC2 *vcOject = [[VC2 alloc] init];
[[self.navigationController pushViewController:vcOject animated:YES] autorelease];

Related

ARC: When to set a viewController to nil

I'm still struggling a bit with the idea of ARC. Let's suppose I have two very complex viewControllers A and B that each have a lot of pictures in them which are retained by each view. For argument's sake, let's suppose the first ViewController (A) retains images which take up 75 MB in RAM. The other one (B) takes up 75 MB as well.
In my App Delegate I set up my NavigationController like so:
ViewControllerA *vcA = [[ViewControllerA alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:vcA];
[navController.navigationBar setHidden:YES];
[[self window] setRootViewController:navController];
When I switch from A to B, I do it like so in ViewControllerA.m:
ViewControllerB *vcB = [[ViewControllerB alloc] init];
[[self navigationController] pushViewController:vcB animated:YES];
When I switch back, I do it like so in ViewControllerB.m:
[[self navigationController] popToRootViewControllerAnimated:YES];
Now my big question is if I still have ViewController A in my memory when I'm in ViewController B? In this case, when does the compiler release a ViewController? Could I or should I release (i.e. set it to nil) one ViewController when it is not in use?
I'm sorry if the answer is clear or if I'm totally missing the point. So any answers and explanations would be highly appreciated.
Yes, you still have ViewControllerA (you can see this with Instruments next time). That has nothing to do with ARC, it's the same without it. Let me explain:
You create a UINavigationController and put UIViewController A as the root, A is retained (in ARC it's a strong property or something like that), as you can see UINavigationController needs it right?
Now you push UIViewController B, B and A exist on memory, you UINavigationController still needs UIViewController A, it's just not showing and the view can be unloaded, if the system needs memory, but it won't release A. When you pop UIViewController B, it is released, and if there aren't references for it (again, I assume this is how ARC works) it is dealloced.
Now your question is, when is the rootViewController dealloced? Well, UINavigationController always has a root! So, while you have a UINavigationController you have a rootViewController.
Let me know in the comments if you need further explaining.
I can't help you with ARC, cause I never used it (and I don't know if I really want).
But I can tell you one thing :
When you push your ViewControllers, they all are in the navigation stack. And untill they are in the stack, they remain in memory.
Without using ARC, if I autorelease eatch viewController I push, it will be released exactly when I would pop it from the stack.
If someone know more about ARC (and when it release allocated object) I would be glad to have more info.
Thanks
Your view controller A will be retained by navController so it won't be released. Even you set vcA to nil it will not be released because navController is retaining it.
The problem is that your controller retained lots resources (images) that takes lots memory. To solve this, you can allocate the resources on viewDidLoad and free them at viewDidUnload
for example
// in your view controller
- (void)viewDidLoad {
[super viewDidLoad];
self.image = // read image to memory
}
- (void)viewDidUnload {
[super viewDidUnload];
self.image = nil; // release the image to free memory
}
Then after view controller B is pushed to navController, view controller A will get notified by UIKit that it is not the displaying controller and it will unload the view if necessary in order to free memory for other class to use.

ARC UINavigationController stack not getting deallocated when presented as modal view controller

First: I ported my App to ARC and everything seemed to work. But now I discovered a problem: I have a UINavigationController that is presented modally with some UIViewControllers on its stack. But when I dismiss the modal view controller, the view controllers from the stack don't seem to be deallocated. Here is what I do:
UIViewController* root = [[UIViewController alloc] init];
UINavigationController* navi = [[UINavigationController alloc] initWithRootViewController:root];
[self presentModalViewController:navi animated:TRUE];
Then from the root I push some more view controllers, but that doesn't really matter. The fact is when I later call
[self dismissModalViewControllerAnimated:TRUE];
root doesn't get deallocated. Of course in my code root is a subclass of UIViewController, and I track dealloc and viewDidUnload, but nothing gets called.
Any ideas?
What's inside your navigation controller? It could be that something else (perhaps a view controller inside your navigation controller) is the culprit, which is leading up the chain meaning the navigation controller doesn't get released.
Either way, the code you posted is correct, so if your navigation controller isn't being released after calling dismissModalViewController it would suggest that something else still has an active reference to it or one of its dependencies. I know that doesn't answer your question, but you will probably have to hunt around.
Since you aren't showing actual code, it's hard to tell what is going on with your root view controller.
But, with ARC, if you have a strong pointer to an object, it won't get released. I suspect that you are holding on to this controller after adding it to your navigation controller.
But, without seeing your code, I can't tell.

When to release/autorelease VC's that will be pushed

I have come across previous problems which occur when I release a viewController after pushing it via the UINavigationController, so I now autorelease every viewController that will be pushed. But I often see code where the developer releases the viewController after pushing it.
My question is, when is the correct time to release/autorelease a UIViewController when pushed onto the stack?
Thanks
push
release
The navigation controller retains the view controller when you push it.
I would suggest any of your previous problems were nothing to do with the above process, but elsewhere.
ViewController *yourViewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[self.navigationController pushViewController:yourViewController animated:YES];
[yourViewController release];
Whether you autorelease the view controller before the push or release the view controller after the push makes little difference. The release after the push is a bit more efficient since it would cause the view controller to be released sooner (freeing up memory faster) but the autorelease will also release it soon, but a bit later.

Should pushViewController release oldController when a new one is pushed?

From what I understand pushViewController should release old viewController when a new one is pushed?
Here I just creates two different viewControllers and pushes them.
UINavigationController *navController = [[UINavigationController alloc] init];
[self.window addSubview:navController.view];
smallLayout = [[SmallViewController alloc] init];
[navController pushViewController:smallLayout animated: NO];
[smallLayout release];
largeLayout = [[LargeViewController alloc] init];
[navController pushViewController:largeLayout animated: NO];
[largeLayout release];
In the SmallViewController dealloc is never getting called and when I'm checking retain count it's still 1. I'm checking retain count long after the run loop is done and I also know that retain count isn't something you should trust.
No it should not....
The navigation controller maintains a navigation stack of all the view controllers pushed on to it... so when you go back or pop the current view controller, the previous controller is still present.
The navigation controller will release a view controller after it is popped.
The view controllers don't get released when you push a new controller onto the navigation stack. The navigation controller stays holding onto them so that it has the correct item to display when you pop the current controller off of it. If it was releasing it, then the nav controller wouldn't have anything to go back to.
If you're looking to try to optimize memory, implement -(void)viewDidUnload. It gets called when ever the controller's view gets unloaded which may happen when you push the new controller. I say may happen since it is called during low-memory conditions. So if you have plenty of free memory it won't get called. In the simulator you can force it by simulating a memory warning. Make sure that anything you destroy in it can be, and is, recreated in -viewDidLoad.
You alloc once, you release once. You are already doing it in your code. So AFAIK your code is fine. Here dealloc of smallLayout won't get called because UINavigationController keeps a stack of all viewControllers pushed into it, hence retaining it. UINavigationController manages the release of these viewControllers, when it is no longer needed.

release viewcontroller after presenting modally

I was watching CS193P Stanford course on Itunes, and in one of the lectures a demo was given and
There it was said you could present the viewcontroller modally and then release it. Roughly like this (I know this isn't perfect but I'm on my PC atm)
[self.view presentcontentmodally:myVC]
[myVC release];
However this seems to produce problems. If I put a NSLog(#"%d", [myVC retainCount]) between those two lines then it returns 2 implying it is ok to release. However when I dismiss the myVC the app crashes. Nothing in the NSlog and the debugger won't show where it stopped.
But I used malloc-history or something that some blog said would help. And found that it was the myVC.
So should I be releasing myVC?
(also when the modalVC has been dissmissed should the app's memory usuage go back to before the modalVC was presented?)
Yes, you should release your view controller after passing it to a modal navigation controller. Just make sure you are not passing in a previously retained view controller unless you plan to manage its release manually.
For example, follow the lifespan of _myViewController here:
MyViewController *_myViewController = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
UINavigationController *_modalNavigationController = [[UINavigationController alloc] initWithRootViewController:_myViewController];
[_myViewController release], _myViewController = nil;
[[self navigationController] presentModalViewController:_modalNavigationController animated:YES];
[_modalNavigationController release], _modalNavigationController = nil;
The modal navigation controller will increment the retain count of _myViewController, essentially taking ownership of it.
Once the modal navigation controller is dismissed and you are back to your original navigation controller, the modal navigation controller will receive a release message and in turn release its root view controller (_myViewController).
The retain count of this view controller will hit zero and the memory is then reclaimed.
I have just checked through a couple of my apps, and I am releasing my modal view controllers after each presentation, without problems. Which makes me think that you don't yet understand the Cocoa memory management model. Here's a sample:
TweetController *tweetController = [[TweetController alloc] init];
tweetController.content = content;
tweetController.delegate = self;
[self presentModalViewController:tweetController animated:YES];
[tweetController release];
Note that this controller was created with alloc/init, and wasn't previously released or autoreleased.
In addition, please don't rely on retain count checking; a retain could be from a previous autoreleased, which will go away very soon causing the sort of error you have been seeing.