Release/dealloc tab bar view controllers - iphone

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.

Related

Releasing memory of specific UIVIewController in the navigationController stack

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

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;

Sending release to an NSArray of UIViewControllers?

If I have an NSArray of UIViewControllers and send release to the array, will that call viewDidUnload or dealloc for each of the UIViewControllers? or neither?
Here's what I'm doing:
- (void) viewDidLoad {
UIViewController* profileController = [[ProfileController alloc] init];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc] initWithObjects: profileController, dietController, exerciseController, progressController, friendsController, nil];
[profileController release];
//other controllers get released same way ....
}
- (void) dealloc {
[viewControllers release];
NSLog("DEALLOC!");
//I know dealloc is being called
//what happens to the view controllers?
}
I put a breakpoint in the viewDidUnload and dealloc methods for each of these view controllers, and they don't get called.
As pgb mentioned, you are confusing the two concepts of memory management and view controller life cycle. viewDidUnload will be called whenever the view controllers view is unloaded, which of course will only happen if the view is loaded. In the absence of displaying any of the view controllers you should not expect to see viewDidUnload called at all.
You very much should expect to see dealloc called though!
Your problem is with the initialisation of the viewControllers array:
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
Assuming that viewControllers is a property that is using the retain attribute, this will leak the array, and thus all the array's contents. The problem is that you have alloc'd a mutable array (thus retain count = 1), then you are assigning it to the viewControllers property which will increment the retain count.
In your dealloc method you (correctly) release the array, but this will merely decrement the retain count to one.
My suggested fix is to add autorelease to the above code:
self.viewControllers = [[[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil] autorelease];
After making this change you should expect to see dealloc being called on the view controllers. You should also expect to see viewDidUnload called as the views of these view controllers are unloaded (for example, as they are popped off of the stack in a navigation-controller based application).
I see two issues here, one for each of the methods not called:
viewDidUnload won't be called if the view associated with the UIViewController wasn't loaded. You are basically mixing two concepts: memory management (and object lifecycle) vs. view controller life cycle. While you can see some parallelism between the two, they are not necessarily related.
Are you, at any point, pushing the viewController so its view is visible? If you are not, then viewDidUnload will certainly not be called (as won't viewDidLoad).
As for dealloc, if it's not called after you release the object (and you think the object should be dealloced from memory) it's because you have a memory leak. In your code, I can easily see the memory leak on the initialization code:
UIViewController* profileController = [[ProfileController alloc] init];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
profileController gets its retainCount bumped on the first line, when you alloc it. Later, when you add it to a NSMutableArray, the array will retain it, bumping its retainCount once again. To balance that, you would need to release twice, but you are only releaseing once, on your dealloc method. To solve this issue, I would change your initialization to:
UIViewController* profileController = [[[ProfileController alloc] init] autorelease];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
which will add the extra release you are missing and balance the retain release calls on your view controllers.
When you release the array, that does not dealloc it, that gives it permission to be dealloced if there are no other retains.
Similarly, if the array is dealloced, each element inside is released, but the objects may be retained elsewhere and hence are not guaranteed to be dealloced immediately.
In general, an object like a view or view controller is retained by the UI logic while it's actively being presented, so releasing your retains on it, either directly or through the array, will not cause it to be dealloced if it's being displayed (or, eg, in the stack of a navigation controller).

iPhone EXC_BAD_ACCESS when calling [super dealloc] on a custom UIViewController

I'm at a loss! It's one of those pesky bugs that happens only under specific conditions, yet I cannot link the conditions and the results directly.
My app has a paged UIScrollView where each page's view comes from a MyViewController, a subclass of UITableViewController. To minimize memory usage I unload those controllers that are not currently visible. Here is my "cleaning" method:
- (void) cleanViewControllers:(BOOL)all {
if (all) {
// called if some major changes occurred and ALL controllers need to be cleared
for (NSInteger i = 0; i < [viewControllers count]; i++)
[viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
}
else if ([viewControllers count] > 2) {
// called if only the nearest, no longer visible controller need to be cleared
NSInteger i = pageControl.currentPage - 2;
if (i > -1) [viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
i = pageControl.currentPage + 2;
if (i < [viewControllers count]) [viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
}
}
It's this line that crashes the app:
viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
viewControllers is an NSMutableArray containing objects of type MyViewController. MyViewController has no custom properties and its dealloc method contains nothing but a [super dealloc] call.
Here is what the debugger shows:
alt text http://a.imageshack.us/img831/3610/screenshot20100806at126.png
The thing is that this does not happen every time the controller is cleared, but only sometimes. Specifically, after certain changes trigger a complete cleaning and re-drawing of the ScrollView, it displays the current page (call it X) fine, but as soon as I scroll far enough to cause cleaning of X, this crash happens. It's driving me nuts!
Another thing, this does not happen in the 4.0 Simulator, nor on an iPad, but happens very consistently on a 1st gen iPod touch running 3.1.3.
Something is being released but a pointer is still dangling. Set NSZombieEnabled to YES and run it again. Here's how:
Product -> Edit Scheme
Select the "Arguments" tab
Add to "Variables to be set in the environment"
Name: NSZombieEnabled
Value: YES
In Xcode 4.1 and above:
Product -> Edit Scheme
Select the "Diagnostics" tab
You have an option to enable zombie objects.
Run the app again. At some point it'll tell you you're accessing an already released object. From that you'll need to figure out who's not retaining or who's over-releasing the object.
Happy Zombie Hunting.
Often this can be caused by allocing something then setting it to something within the ViewController then releasing it, for example:
UIBarButtonItem* timeLabel = [[UIBarButtonItem alloc] initWithTitle:#"time" style:UIBarButtonItemStylePlain target:nil action:nil];
NSArray *items = [NSArray arrayWithObjects: timeLabel, nil];
self.toolbarItems = items;
Now the natural thing to do after this is :
[timeLabel release];
But this will cause EXC_BAD_ACCESS on [super dealloc], presumably because the view controller releases the array AND all the items within it.
View controllers will automatically unload their views on a memory warning by default, so there's no reason to unload the controllers themselves unless they include significant overhead.
Is the view controller's view still in the view hierarchy? You can check with something like [viewController isViewLoaded] && viewController.view.superview. If so, it's probably not safe to remove the view controller.
(Note the isViewLoaded check, since UIViewController.view will load the view if it's not already loaded.)
Instead of replacing the elements in the array you can simply call removeObjectAtIndex: or removeAllObjects: to make sure there is nothing holding reference where it shouldn't.

Retain/release pattern for UIPopoverController, UIActionSheet, and modal view controllers?

I'm somewhat unclear on the object ownership patterns required for the following instances. When my UIViewController presents a popover controller, an action sheet, or another view controller as modal, am I required to hang onto a retained reference to that child controller until it's been dismissed?
In other words, do the following lines of code effectively "transfer" ownership, or not?
[aPopoverController presentPopoverFromBarButtonItem:someButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
[anActionSheet showFromBarButtonItem:someButtonItem animated:NO];
[aViewController presentModalViewController:someOtherViewController animated:YES];
Can someone point me to explicit documentation on this subject?
UIPopoverViewController has a slight different memory management/owning. Present a popover does not retain the memory, so you can't transfer the ownership of your popviewcontroller to the presenting object.
To avoid memory leak, you have to adopt the UIPopoverControllerDelegate and implement the DidDismissPopOver method as follow:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
[popoverController release];
}
This way, you can safe alloc and present a PopOver:
-(void)showSearch:(id)sender {
SearchViewController *searchVC = [[SearchViewController alloc] init];
UIPopoverController *popVC = [[UIPopoverController alloc] initWithContentViewController:searchVC];
popVC.delegate = self;
[popVC setPopoverContentSize:CGSizeMake(320, 100)];
[popVC presentPopoverFromRect:CGRectMake(200, 200, 320, 100) inView:self.view permittedArrowDirections:0 animated:YES];
[searchVC release];
}
Presenting a modal view controller retains the UIViewController. This is actually not clear from the docs. However, I tested it using the following code...
NSLog(#"BEFORE %d", [self.setupViewController retainCount]);
[self.navigationController presentModalViewController:self.setupViewController animated:YES];
NSLog(#"AFTER %d", [self.setupViewController retainCount]);
The self.setupViewController is already retained locally, but presenting it output the following:
2010-05-19 10:07:36.687 LocateMe[27716:207] BEFORE 1
2010-05-19 10:07:36.762 LocateMe[27716:207] AFTER 3
So it is probably being retained in the local modalViewController property, as well as in the view hierarchy. Dismissing it will balance these.
So bottom line is, retain it if you want to control it directly, but you don't have to.
EDIT - Just to be clear, the correct pattern is to always retain an object if you set yourself as its delegate. That's because you should be setting the delegate to nil in your dealloc for safety. Practically though, a modal controller is always going to be dismissed before you dealloc, so it's not an issue. You'll notice Apple also breaks this rule in [UIView setAnimationDelegate:], which actually retains the delegate you set.