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

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.

Related

self.navigationController pushViewController not adding controller to stack

I am on a viewController called vcA and I do this:
[self.navigationController pushViewController:vcB animated:YES];
and it works. vcB is pushed. Inside vcB's viewDidAppear I do this:
NSArray *controllers = self.navigationController.viewControllers;
and controllers contains just one object, vcA !!!! (what?)
Why is vcB being added to the controllers array? Is there anything that can prevent that from happening?
thanks.
I banged my head on the wall for a week to solve this problem involving pushing a view controller on the navigation controller stack.
The problem was this: I was on a navigation controlled based app and I had 3 view controllers, vA, vB and vC.
I was pushing vB from vA using this:
[self.navigationController pushViewController:vB animated:YES];
it worked, but, when I tried to push vC from vB, the self.navigationController property of vB was nil and vB was not on the navigation controller stack. What? Yes, I pushed vB on the navigation stack and vB was not being added to it, but even so, vB was showing correctly.
I discovered that inside vB's viewDidLoad, self.navigationController was not nil and that vB was on the navigation controller stack, but as soon as vB's viewDidLoad ended, vB was removed from the navigation controller stack and its navigationController property was set to nil. At that time, vB was a kind of ghost control, outside the navigation controller stack.
I don't need to mention that from vB I was unable to get back to vA or to push vC.
How that happen?
simple, I was pushing vB from inside a block... something like:
void (^ block1)() = ^(){
[self.navigationController pushViewController:vB animated:YES];
};
What was happening because a UIKit function (the push thing) was being executed in a block that was probably running on a thread that was not the main thread.
The resolution to that was to wrap the push in a dispatch to the main thread, using this:
void (^ block1)() = ^(){
dispatch_async(dispatch_get_main_queue(),
^{
[self.navigationController pushViewController:vB animated:YES];
});
};
The app had another minor problem with a wrong outlet connected to a viewController, but this was the main problem. This is the kind of problem hard to spot, for being subtle. I discovered the problem when I added another button to the navigation bar and that button did not appear. So, I suspect that something involving UIKit was happening, or not happening in that case. The big problem here is that this code was not crashing, no error message, nothing, just working bad, but working.
I believe this has to do with the timing of your checking. During the pushViewController method, the next view controller's viewWillAppear: method (and others, such as the one you're checking) are in the process of getting called. I believe after all those method are called, the view controller is considered on the navigation stack AFTER the animation is done completing.

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.

pushViewController only works with animated:YES

I have found a strange behaviour when trying to push a viewcontroller onto the stack. The following works fine:
[self.navigationController pushViewController:myViewController animated:YES];
but if I change it to animated:NO it no longer works, doesn't seem to push at all. I was performing this in a viewWillAppear but I have also tried it in viewDidAppear but with no luck.
Any ideas what could be causing this?
Thanks
The problem is most probably not the call itself, but the placement of the call. Try putting the same action on a UIButton and it should 100% work. I've noticed that putting view controller manipulation routines like presentModal... and pushViewController... don't sometimes work in the viewWill* viewDid* methods. Or try making the calls from those functions with a performSelector:withObject:afterDelay after a short delay and see if that works.
Edit: there are a couple of ways of doing what you want to do. You can directly modify the navigation stack of a navigation controller, so when you are in view N+1, you can replace view N on the stack (by building a new navigation stack array and setting it in to the navigation controller), then pop, and you'll get the effect of "popping back to a different view controller". You can also issue multiple pops and pushes from the view controller you want to leave, but you have to be careful:
// pop back 2 controllers on the stack to the setup screen
//
// locally store the navigation controller since
// self.navigationController will be nil once we are popped
//
UINavigationController *navController = self.navigationController;
// retain ourselves so that the controller will still exist once it's popped off
//
[[self retain] autorelease];
// Pop back 2 controllers to the setup screen
//
[navController popViewControllerAnimated:NO];
[navController popViewControllerAnimated:YES];

How do I destroy a view that is added by pushViewController?

After adding a view by pushViewController method, there will be a back button in the navigation bar to pop the view off the stack. However, it seems that iOS won't destroy the view after popping it off the stack so when will it be destroyed? Can we destroy it manually when popping out the view?
Generally the pattern is like this:
- (void)pushSomeViewControllerOnStack
{
SomeViewController* someViewController = [[SomeViewController alloc] initWithNibName:#"SomeView" bundle:nil];
[self.navigationController pushViewController:someViewController animated:YES];
[someViewController release];
}
In other words, the navigation controller will do its own retain of the view controller, which means you also need to release it yourself, since there's an init. The navigation controller will also take care of releasing this controller when appropriate.
You should implement the viewDidUnload and dealloc methods within your UIViewController subclasses.
When a UINavigationController pops a view controller off its stack the code within those methods will be executed.
You should read the View Controller Programming Guide for iOS: Navigation Controllers documentation from Apple's iOS Developer Library as well as the class reference documentation for the UINavigationController and UIViewController classes so that you will better understand the view controller life cycle and what to expect when various application events occur.

How to jump to a specific view?

I have 3 views (xib'd) the third view opens a modal view (also xib'd).
My goal is to dispose the modal view and jump on the view #1.
I used the following code but it does nothing.
self.statusView = [[StatusViewController alloc] initWithNibName:#"StatusViewController" bundle:nil];
[self.navigationController popToViewController:self.statusView animated:YES];
[self.navigationController popToViewController:
I also tried the following, same result.
[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
I'm going crazy...
statusView has an accessor regularly synthetized and it represents the view that I want to jump to.
It is not entirely clear how your views are set up with respect to each other, based on what you've said so far.
I'm guessing you have a navigation controller, and 3 view controllers that are displayed on the navigation stack.
If that's the case, and you want to pop back by two screens at once (from #3 to #1, skipping #2), then you need a pointer to the view controller for #1 (not the view itself). It looks as if the first popViewController: method call in your question is sending in a view.
Sample code to pop to the first view controller:
UINavigationController* navController = self.navigationController;
UIViewController* controller = [navController.viewControllers objectAtIndex:0];
[navController popToViewController:controller animated:YES];
If you've tried this, and it doesn't work, a few things might be going wrong:
Maybe self.navigationController isn't actually the right object.
Maybe the view you expect isn't actually view #0 on the navigation stack.
Maybe the navigation controller you're working with isn't currently visible.
Here are some further steps you can take to test these hypotheses:
When you first allocate the navigation controller you want to work with, call NSLog(#"Nav controller is at %p", navController); and in this code add a call to NSLog(#"Now my navController is at %p", navController); and check that the addresses match.
If the nav controller is the right one, print out the current navigation stack; something like this (which assumes each view controller has a different class name):
for (UIViewController* viewController in navController.viewControllers) {
NSLog(#"%s", class_getName([viewController class]));
}
Do something visual to the navigation controller you think is visible to make sure it actually is. For example [navController.visibleViewController.view addSubview:aColorFulView]; where aColorFulView is some visually obvious UIView.
-popToViewController lets you pop controllers OFF the stack. What you want to do is push new viewControllers ONTO the stack, so use:
[self.navigationController pushViewController: self.statusView animated: YES];