Remove permanently UIViewController from the stack - objective-c

Here is how I remove UIViewControllers which never will be used again in the app.
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray: self.navigationController.viewControllers];
[allViewControllers removeObjectAtIndex:0];
[allViewControllers removeObjectAtIndex:1];
Is this the right way to remove view from the stack ?
While running the app with Instruments I notice that the memory is not freed when the app enter the stage where the code above is executed. What is wrong here ?

Creating a new NSMutableArray of the viewControllers and then removing from the new NSMutableArray accomplishes nothing. The viewControllers still are retain by the navigationController.

By assigning your view controllers into a new mutable array you increased their retain count by 1, removing them decreases their retain count by 1 so the net effect is zero. What you want to do is replace the view controllers that are part of the navigation controller after removing them, there by making the navigation controller release your old instances.
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[allViewControllers removeObjectAtIndex:0];
[allViewControllers removeObjectAtIndex:1];
[self.navigationController setViewControllers:allViewControllers animated:YES];

you are not actually freeing up the viewControllers. you are just increasing their retain count by +1 and then decreasing by -1.
actually nothing is getting affected by the code you have written to free your viewControllers.
For a NavigationController, you call popToRootViewController to release all other view controllers except the base/root viewcontroller for NavigationController.

Related

releasing UIViewController subclasses does not actually free up memory

I have a UINavigationController object (named LoginNav) which consists of ViewController1 & ViewController2, my iPad app starts by loading a UISplitViewController subclass (named mainSplitViewController) and then presenting LoginNav modally on top of it (this is by the way done in didFinishLaunchingWithOptions method of AppDelegate like this:
[self.mainSplitViewController presentModalViewController:LoginNav animated:YES];).
Once ViewController1 is shown, I tap a UIButton in it to push ViewController2, when I finish working in ViewController2 I tap a UIButton in it to call [self.navigationController dismissModalViewControllerAnimated:YES]; to dismiss LoginNav with both of its view controllers and show mainSplitViewController's contents.
There is a dealloc method in both ViewController1 & ViewController2 with NSLog statement in each one, once loginNav is dismissed, the NSLogs never get fired, but doing [self.navigationController.viewControllers objectAtIndex:0] release]; & [self.navigationController.viewControllers objectAtIndex:1] release]; right after [self.navigationController dismissModalViewControllerAnimated:YES]; fires both NSLogs.
I commented out the above two release statements, then I launched the Allocations instrument, and launched the app again and pushed ViewController2 then dismissed loginNav as described above, and looked at Live Bytes column (All Allocations value ) it was 6.9 MB right after the dismissal of loginNav, then I did this step again but in this case using the two release statements, I got exactly a 6.9 MB value on Live Bytes column.
Two Questions:
1) why do not the dealloc methods of ViewController1 & ViewController2 never get fired after the dismissal of the navigation controller LoginNav that holds them ? and is it correct to do the above two release statements to release these view controllers ?
2) why releasing ViewController1 & ViewController2 does not free up memory ?
p.s. there is no single variable (or IBOutlet) being held in memory in both ViewController1 & ViewController2, everything is released in both of them.
These kinds of issues are nearly impossible to troubleshoot without seeing all your code. When you manage memory manually, there are multiple areas that you can go wrong. For example the following code will leak:
- (void)didSelectSomethingInViewControllerOne
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[self.navigationController pushViewController:vc2 animated:YES];
}
In this case you have allocated the object and thus have ownership of it. Then the nav controller takes ownership of it. When you pop the controller from the navigation stack, the navigation controller relinquishes ownership of it, but you never did, so it still has a retain count of 1 and won't get deallocated.
Relinquishing ownership of the controllers later in your code (like after dismissing the modal view) is a bad idea. It makes it difficult to analyze ownership when your releases are all over the place. As soon as the navigation controller has ownership you can release the object you allocated, as you do not intend to use it in the future:
- (void)didSelectSomethingInViewControllerOne
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[self.navigationController pushViewController:vc2 animated:YES];
[vc2 release];
}
The situation above can have nothing to do with your problem. Your problem may reside in many different areas. Which is why troubleshooting memory management problems is difficult. Without seeing source code.
Consider transitioning your project to ARC:
http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
Another thing you might have fallen foul of is a retained property (such as a delegate) in your LoginNav.

How to 'pop' to a view contoller not on the stack

I have a navigation controller (XCode4 with Storyboards and ARC) where a segue is connected to each individual ViewController (7 of them). Normally, I tap a row in the Nav contoller which takes me to the correct scene. However, there are times that I want to go from scene "A" to scene "C" using segues, then from "C" to "B", which has NOT been placed on the stack by going through the Nav Controller.
Is this somehow possible (to go from scene "C" to scene "B")?
UPDATE: this is the code to put controller on the stack:
EnterDataViewController *edvc = [[EnterDataViewController alloc]init];
NSMutableArray *ma = self.navigationController.viewControllers;
[ma insertObject:edvc atIndex:1];
self.navigationController.viewControllers = ma;
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex: 1] animated:YES];
Insert the new view controller below the top one in the hierarchy, with animation off. Then, pop. :)
The stack is an array stored in the UINavigationController's viewControllers property. Use it to create a new (mutable) array where you could just insertObject:atIndex:1 and assign this back to the UINavigationController.viewControllers.
Inserting new controllers to the self.navigationController.viewControllers doesn't always work as expected, so I'd recommend to fetch controllers that you need from the array, build a new array from scratch, and then assign this array to navigation controller.
UIViewController *mainScreenController = [[self.navigationController viewControllers]objectAtIndex:0];
SecondScreenController *secondScreenController = [[SecondScreenController alloc]init];
NSMutableArray *controllers = [[NSMutableArray alloc]initWithObjects:mainScreenController, secondScreenController, nil];
[self.navigationController setViewControllers:controllers animated:YES];

Does releasing a view controller releases all its properties?

I have a BaseViewController, which is a subclass of UITabBarController, and set up my views in this view controller.
-(void)setUpViews
{
FirstController *mainViewController = [[FirstController alloc] initAssignment:delegate.currentAssignment withMutableArray:myArray];
UINavigationController *firstNavController = [[UINavigationController alloc] initWithRootViewController:mainViewController];
SecondController *secondViewController = [[SecondController alloc] initWithNibName:#"SecondController" bundle:nil];
UINavigationController *secondNavController = [[UINavigationController alloc] initWithRootViewController:secondViewController];
self.viewControllers = [NSArray arrayWithObjects:firstNavController, secondNavController,nil];
firstNavController.tabBarItem.image = [UIImage imageNamed:#"blablabla.png"];
firstNavController.tabBarItem.title = #"Stream";
secondViewController.tabBarItem.image = [UIImage imageNamed:#"blabla.png"];
secondViewController.tabBarItem.title = #"Favourite";
}
Now I have another view controller, I call it ViewHandlerController, a subclass of BaseViewController. in my viewDidLoad in this viewHandler, i invoke setUpViews which is declared in BaseViewController. in the first screen of my app, when a Login button is pressed, I instantiate my ViewHandlerController, and presented my tabcontroller succesfully with nav controllers by using.
[[[UIApplication sharedApplication].windows objectAtIndex:0] addSubview:viewControllerHandler.view];
Inside my app. there is a logout button. I am using NSNotificationCenter to call my logoutMethod which is declared in my first screen. My question is, in this logoutMethod, how can I release the previously allocated objects to avoid memory pressure since the user can log in again (logIn - logOut -logIn)? since I'm using ARC, is setting my ViewController to NIL will do all the clean up?
EDIT: is removing my ViewControllerHandler from superview and setting it to nil helps releasing its subviews too?
Cheers
Well, answer for your question (not ARC) – no, basicaly view controller doesn't releases his properties when release. But you should nil your properties in viewDidUnload and (or) dealloc methods.
If you use ARC, you should notice that some actions can retain your controller, and it can never be deleted in some cases. Watch for methods, which takes object for delegate, they may not using weak references
Have a look at this Apple article about memory management.
You can just use autorelease in alloc methods or for (UIView *view in [self.view subviews]) {view release}; in dealloc.
In fact release is opposite operation to retain. When you do retain, you increase by 1 count of object instances in allocated memory. It happens when you call alloc, copy, new, mutableCopy. If you are using ARC you can't release objects, memory management is not your problem already.

How correctly release UITabController

This may sound a newbie question ... but however I'm new to iOS development.
I've created UITabController object programmatically like this.
mTabBarController = [[UITabBarController alloc] init];
...
mTabBarController.viewControllers = [NSArray arrayWithArray:tabBarItems];
[tabBarItems release];
And releasing mTabBarController in dealloc like this.
- (void)dealloc {
[mTabBarController release];
}
Now my question : will I get a memory leak ? When I assign value t viewController the ref count of tabBarItems is still 1. When I release mTabBarController does it also release all its viewcontrollers ?
Yes, the tab controller owns an array of view controllers (and everything in an array is retained). You're not creating a leak as long as you properly release or autorelease the items you're adding to the tabBarItems array.
It really helps to think of object relationships as ownerships.
UITabBarController should never be placed as the child of another ViewController, so you will always have to release it in dealloc. If your TabBarController's view is the childview of your application's window, it's ok not to release it in dealloc since the only time dealloc will ever get called is when your program is closing, in which case your controller will be released anyways. However, some people like to release it in dealloc anyways just to keep their code consistent. What you're doing is fine.

Why is this object being deallocated?

I'm developing an iPhone app, I'm trying to push a view into the navigation controller, which I've done many times before, however, I'm having some issues with this particular app. I have a table view, and when the user selects one row the new view is pushed into the controller:
DataWrapper *row=[[self.rows objectAtIndex:[indexPath section]] objectAtIndex:[indexPath row]];
DataViewController *nextController=[[DataViewController alloc] initWithNibName:#"Data" bundle:[NSBundle mainBundle]];
[nextController setInfo:row];
[nextController setRow:[indexPath row]];
[nextController setParent:self];
[self.navigationController pushViewController:nextController animated:YES];
[nextController release];
and it goes fine, until the user taps the back button, I get an exception, and used NSZombieEnabled and get this:
-[DataViewController respondsToSelector:]: message sent to deallocated instance 0x4637a00
So i tried to remove the [nextController release] and in fact it worked, but WHY???? I allocated nextController, so I'm supposed to release it, right?? I don't feel right releasing this app if there's something like this, I feel like it's going to fail. Please let me know your thoughts.
Your nextController isn't being retained by navigation controller. If you release it then because there is only one init/release pair, the object is deallocated. Later when the navigationController attempts to send messages to it, you get the error you see.
This is also why remove [nextController release] fixes the problem.
You are right in that if you allocated, you should free it. But the caveat is only after your application is done with it, not before.
Some objects will stay allocated for nearly the lifetime of the application, so don't feel too bad.
I would guess that [self.navigationController] is returning nil, because if it weren't nil, it would be retaining your object. Since your object is not getting retained, it would appear that there is no object that's trying to retain it, indicating that the navigationController property is empty.
Is it possible that your navigation controller is somehow being deallocated, resulting in the view controllers also getting released? You could maybe test it by retaining the nav controller just before pushing nextController.
For debugging purposes, I would override -dealloc in your DataViewController class, and set a breakpoint on it.
but it seems to work for something like:
DetailViewController *controller = [[DetailViewController alloc] initWithNibName:#"SomeView" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController: controller animated:NO];
[controller release];
so, why is it not working for pushViewController ?