Releasing memory of specific UIVIewController in the navigationController stack - iphone

Basically I have an app that has introductory views. Once you reach a certain view, the previous views are no longer accessible at all, so I want to remove them from the stack and free any memory they have consumed. What is the best way to do this? Right now I am doing something like
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
NSArray *allControllersCopy2 = [allViewControllers copy];
for (id object in allControllersCopy2) {
if([allControllersCopy2 indexOfObject:object] == ([allControllersCopy2 count] - 1)){
NSLog(#"IGNORE CURRENT VIEW");
}
else{
[allViewControllers removeObject:object];
[object release];
}
}
self.navigationController.viewControllers = allViewControllers;
[allControllersCopy2 release];
Does this actually release the memory consumed by these views? or does it simply remove the view from the stack array?

When you are ready to push "that certain view", use setViewControllers:animated: to replace the stack with your final ViewController, instead of using pushViewController to add it to the stack.
assuming controller = 'that certain view'...
don't do [self.navigation pushViewController:controller animated:YES], do:
[self.navigationController setViewControllers:[NSArray arrayWithObject:controller] animated:YES];
this will release all the previous View Controllers, their dealloc methods will get called, memory release, etc

Related

Release/dealloc tab bar view controllers

I have assigned the tab bar view controllers using the nib files and when i try to release the tab view controllers as following it's not calling the dealloc functions of any of the view controllers. I am releasing as following:
[appDelegate.tabBarController.view removeFromSuperview];
NSMutableArray * vcs = [NSMutableArray arrayWithArray:[appDelegate.tabBarController viewControllers]];
[[vcs objectAtIndex:2] release]; //tried releasing both ways
[vcs removeObjectAtIndex:2];
[[vcs objectAtIndex:1] release];
[vcs removeObjectAtIndex:1];
[[vcs objectAtIndex:0] release];
[vcs removeObjectAtIndex:0];
[appDelegate.tabBarController setViewControllers:vcs];
Please help me out.
You don't need to call release explicitly on the view controllers.
They're being retained by the array, so removing them from the array should suffice.
And actually if the view controllers weren't being retained somewhere else, you'd probably be "over releasing" and should expect a crash after this code runs...
So look somewhere else for this.
Instruments is your friend.

Return to the first index of UINavigationController?

I'm doing an application which uses a UINavigationController and I'm switching to other UIViewControllers as follows:
if(self.myViewController == nil){
MyViewController *aViewController = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
self.myViewController = aViewController;
[aViewController release];
}
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[delegate.myNavController pushViewController:myViewController animated:YES];
I imagine this is creating a pile of UIViewControllers into the UINavigationController, maybe an array of indexs? I would like to know how to turn back without having to be back one by one.
For example, I'm sailing through a few screens and with a button I would like to return at the first index of navigation. I would also like know how to modify indexes, view, erase and anything pertaining to this issue.
Sorry if I have not explained well.
You've asked two questions.
The first is, how do I get back to my first view controller. As #Thomas Clayson and #ender have answered, you want the popToRootViewControllerAnimated: method of your navigationcontroller object for that.
The second is how to move to a particular index in the view controller stack. The answer to that is, you can set the array of viewControllers explicitly. So you can pull out the current listing of view controllers, modify it, and set it back into the navigationController stack. It'll reset the stack and animate you moving to the top item in the stack.
Thusly:
NSMutableArray *controllers = self.navigationController.viewControllers;
[controllers removeObjectAtIndex:[controllers count] - 1]; //or whatever
[self.navigationController setViewControllers:controllers animated:YES];
NSArray *viewControllers = [[self navigationController] viewControllers];
for (int i = 0; i < [viewContrlls count]; i++){
id obj = [viewControllers objectAtIndex:i];
if ([obj isKindOfClass:[yourViewControllername class]]){
[[self navigationController] popToViewController:obj animated:YES];
return;
}
}
Using this you can come back to any specified viewController.
[self.navigationController popToRootViewControllerAnimated:YES];
Will take you back to the very first view controller (root view controller).
Hope this helps
Use this
NSArray *viewContrlls=[[NSArray alloc] initWithArray:[[self navigationController] viewControllers]];
id obj=[viewContrlls objectAtIndex:1];
[[self navigationController] popToViewController:obj animated:YES];
[viewContrlls release];
You should use popToRootViewControllerAnimated: From UINavigationController class reference:
Pops all the view controllers on the stack except the root view
controller and updates the display.
You can return to the first view with
[self.navigationController popToRootViewControllerAnimated:YES];
That being said, you can also remove a particular view controller, or navigate to a specific index in your view controller if you look at the example.
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray:navigationController.viewControllers];
// You can now manipulate this array with the methods used for NSMutableArray to find out / perform actions on the navigation stack
[allViewControllers removeObjectIdenticalTo: removedViewController];
// You can remove a specific view controller with this.
navigationController.viewControllers = allViewControllers;

pushViewController at didSelectRowAtIndexPath

I have the following code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
if (tvc == nil)
tvc = [[TopicViewController alloc] initWithNibName:#"TopicViewController" bundle:nil];
tvc.title = #"Topic";
tvc.topicId = [[results objectAtIndex:indexPath.row] objectForKey:#"id"];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:tvc animated:YES];
[tvc release];
}
So when I tap on the row it is able to bring this table view. Then I press navigate back and choose a different row, then the app crashes. I tried to see in the console for any error, but can't find any. What is wrong?
I believe your (tvc == nil) is returning NO because you released tvc but didn't set it to nil so next time this method is accessed, you try and push it as a view controller without allocating it again, hence the crash.
You can either remove the if (tvc == nil) check or release tvc and then set it to nil with [tvc release], tvc = nil;.
The other possibility is your results array is being released. Where is it initialised and have you declared a retain property for it? If so you can access it with [self.results objectAtIndex:...] which will guarantee it will stick around until your view controller is deallocated.
#Equinox i think you need to do something like this
tvc.title = [[NSString alloc] initWithFormat:#"Topic"];
tvc.topicId = [[NSString alloc] initWithString:[[results objectAtIndex:indexPath.row] objectForKey:#"id"]];
Edit: i think you are over releasing some objects in your TopicViewController class so your app is getting crash with out any message in the console. what you can do is build and analyze your project to check for any over releasing objects
You're always releasing, even if it's not allocated.
Assuming tvc is a declared property that retains an object, do
self.tvc = [[TopicViewController alloc] initWithNibName:#"TopicViewController" bundle:nil];
//....
self.tvc = nil;
[tvc release];
instead of
tvc = [[TopicViewController alloc] initWithNibName:#"TopicViewController" bundle:nil];
//....
[tvc release];
However, I wouldn't release the view controller and re-allocate it at all, because a memory allocation is expensive. I would just store one tvc object and reuse it, by modifying it according to the selected row.

when to alloc and initialize a view controller

recently I joined two Xcode projects together. To get this thing to work, I had to alloc and initialize my view controller.
self.myViewController = [[MyViewController alloc] init];
But why? In the other project I have the same code. The sole difference is hierarchy of the different views. I added a new view to the top (beginning). So the calling View Controller is not the first view on the stack anymore.
I'm pushing my view in this way on the stack:
[[self navigationController] pushViewController:myViewController animated:YES];
In my NIBs I have added a View Controller object with IB and connected the Outlets.
And I have a memory management question too: If I have a property like myViewController, do I have to release it? The "normal" release is done in the dealloc method. But do I have to use an additional release because of the alloc? I don't think so, but I ask you anyway.
I would need to see more code to answer why you had to alloc your view controller, but I'd say that you always alloc them manually (at least in my experience).
As for the memory management question, if your property is declared as a retain property (#property(retain) UIViewController *myViewController), you are indeed leaking memory, since the retain count after alloc will be 1, and after the retain done by your accessor will be 2. Hence, if you release it only once, you'll end up with a leak.
I usually do this instead:
self.myViewController = [[[MyViewController alloc] init] autorelease];
I found it out: In IB I had to set the nib name on my view controller object. So the alloc and initialization is done by IB?
There is one more option:
(IBAction)loginButton:(UIButton *)sender {
NSLog(#"pressed login");
ICMasterViewController *controller = [[self storyboard] instantiateViewControllerWithIdentifier:#"mainnav"];
[self presentViewController:controller animated:YES completion:nil];
}
On your storyboard you must have UIViewController with name mainnav

UINavigationController and memory management

- (void)launchSearch
{
EventsSearchViewController *searchController = [[EventsSearchViewController alloc] initWithNibName:#"EventsSearchView" bundle:nil];
[self.navigationController pushViewController:searchController animated:YES];
//[searchController release];
}
Notice the [searchController release] is commented out. I've understood that pushing searchController onto the navigation controller retains it, and I should release it from my code. I did just alloc/init it, after all, and if I don't free it, it'll leak.
With that line commented out, navigation works great. With it NOT commented out, I can navigate INTO this view okay, but coming back UP a level crashes with a *** -[CFArray release]: message sent to deallocated instance 0x443a9e0 error.
What's happening here? Is the NavigationController releasing it for me somehow when it goes out of view?
The boilerplate that comes on a UINavigationController template in XCode has the newly-pushed controller getting released. But when I do it, it fails.
---EDIT----
So this morning, I sit down, and it works. No real clue why. Sigh.
Taking what I thought I learned, then, and applying it to another piece of this same controller, I did the following. Yesterday I had this code WITHOUT the release statements, because it didn't work right with them. So this morning I added them to create:
- (IBAction)switchView:(id)sender
{
UISegmentedControl *seg = (UISegmentedControl *)sender;
NSInteger choice = [seg selectedSegmentIndex];
NSArray *array = [mainView subviews];
UIView *oldView = [array objectAtIndex:0];
[oldView removeFromSuperview];
if (choice == 0) {
tableController = [[EventsTableViewController alloc]
initWithNibName:#"EventsTableView" bundle:nil];
[mainView addSubview:tableController.view];
[tableController release];
}
if (choice == 1) {
calendarController = [[EventsCalendarViewController alloc]
initWithNibName:#"EventsCalendarView" bundle:nil];
[mainView addSubview:calendarController.view];
[calendarController release];
}
if (choice == 2) {
mapController = [[EventsMapViewController alloc]
initWithNibName:#"EventsMapView" bundle:nil];
[mainView addSubview:mapController.view];
[mapController release];
}
}
With it set up like this, when I come onto the view, the main portal of my view is filled with the EventsTableViewController's view, I can click to mapView and calendarView, but when I go BACK to tableView, I die because the table delegate methods are being called on a deallocated instance.
So I went and made all of these controllers into synthesized properties, so I can release them in [dealloc]. Which seems to work, but the real question is why adding these views as subviews doesn't retain them, passing ownership to the new view it's a member of, allowing me to release them right there?
Wow, guys. Thanks so much for all your responses--tragically I sent you all on a horrible goosechase.
My NavigationView navigates a NSArray of Event objects (local arts events). My table view drills down to a detail view.
My detail view has in it the following:
-(void)loadEvent:(Event *)event
{
thisEvent = event;
}
And I call that from my table view before pushing the detail view onto the nav stack. thisEvent is a synthesized property of type Event, and so since it's synthesized, I dutifully release'd it in [dealloc].
Many of you already see the problem. Backing up to the table view, when I scroll such that the one I just saw is displayed, it builds the custom table row, and so it goes to get the title property from the Event.... which I just released inside the detail controller. Boom.
I added a retain to that loadEvent: method above and the crashes, they are gone.
NONE of this was really about the views getting retained and released by the navcontroller. It was about accidentally over-releasing the data objects I'm navigating. Part of what had me discover this was, I NSLogged myself in the [dealloc] of each of these view controllers, and I can now see they're behaving exactly as they should.
Thanks! I SO love this site.
I'd guess the fault lies in EventsSearchViewController's init. Is it returning an autoreleased self by mistake ?
Looks like EventsSearchViewController is allocating an array and then over-releasing it, with one of the releases probably in its dealloc.
If you comment out the release, your EventsSearchViewController is never deallocated (it leaks). So, errors that occur as a result of its own dealloc will be masked since that method won't be called. Releasing the controller is the right thing, but you have another bug in the controller itself that only appears at dealloc time.
It could also be that dealloc is releasing an autoreleased array, so you may not have two explicit release calls in your code. But it looks very much like releasing something in dealloc that's causing the problem.