I'm just learning how to code for the iPhone using Objective-C, and I don't quite understand why a view controller needs to be released when its in use?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
Thanks in advance
The navigation controller retained the detail view controller already, therefore, the retain count is 2 at that point. The release will make the retain count 1.
The dl;dr of what I have below is this: take care of your local memory management, don't worry about needing to manage the memory of API classes. They manage their own memory.
Some things to remember with Objective-C memory management:
The cocoa touch classes are responsible for retaining things you give it. Add a subview to a view? The view retains it on its own. Pushing a view controller onto a navigation controller? The navigation controller retains the view controller until it is popped off the stack.
Conversely, each time you call alloc, copy, new and/or retain there must be a corresponding release or autorelease. In the code above, you called alloc to make your view controller, giving it a retain count of 1 that you are responsible for releasing at some point.
If you use properties that are objects with dot notation and the property is a copy or retain property (usually what you want), don't do this:
self.property = [[WhateverObject alloc] init];
instead, do this:
self.property = [[[WhateverObject alloc] init] autorelease;
Related
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.
How to release view controller created like this:
VCClass *vc = [[VCClass alloc] initWithNibName:#"VCClass" bundle:nil];
[self.view addSubview:vc.view];
so the view appear, UIViewController is allocated. Now I want to releas it from within VCClass. I call inside VCClass:
[self.view removeFromSuperView];
my question is, where should I release "vc" object attached to removed view. Is there a good way to notify viewcontroller that is can be released while view is released ?
addSubview does a +1 to the retain count, and it's usually a good practice to release as soon as you don't need it, and you're handing it to another pointer. It's like a glass ball, it is passed hand by hand, and if no one is holding, it falls to the ground and breaks.
Example:
UIView *sampleView = [[UIView alloc] init]; // Retain count: 1
[self.view addSubview:sampleView]; // Retain count: 2
[self.view release]; // Retain count: 1
When the removeFromSubview: is called, the object will be released:
[sampleView removeFromSuperView]; // Retain count: 0
That's for memory management.
Answering your question, a safer way to do what you want to do (loading just a part of an ViewController from a nib (I'm assuming you're using a nib, because you used #"VCClass" in the initWithNibName:), is to use it as following:
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"VCClass" owner:self options:nil];
UIView *view = (UIView*)[nib objectAtIndex:0];
This works by loading the NibName into memory, and then stealing the first element (if you only have a UIView inside, then it will pick that, as the top-most element). This is done similarly for UITableViewCells when loading them from nib files. Nib Files are autoreleased, and it makes more sense, since you apparently just care about the view itself, not the controller.
After you remove it, add a call to
[self autorelease];
Views don't know about their view controllers except as a weak reference to a delegate. This is to avoid a circular reference, among other reasons. VCs often have a life outside their views - hence, the viewDidLoad and viewDidUnload messages. For example, throughout the lifetime of a tab-bar application, the VCs for each tab might go through many different view instances while never being deallocated. Therefore, you should avoid having the view release its own view controller.
Often, the class that allocated the VC should be the one to release it. In the code you provided, you have:
VCClass *vc = [[VCClass alloc] initWithNibName:#"VCClass" bundle:nil];
[self.view addSubview:vc.view];
The controller class that the above code is in is probably the place best suited to releasing the VC. You might need to devise a delegate call just for this purpose.
[self.view removeFromSuperView]; should release your said view from the memory. Though be warned that this will not be true if your view has been retained by any other object that is its retain count is more than 1. Also look at the second answer on this thread.
Does UIView's removeFromSuperView method remove the UIView from memory
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.
i have an array used to populate the UITableView.
The question is where i should to allocate memory for it. I have two options in viewDidLoad or viewWillAppear, but i don't know where is more efficient from the point of view of memory management.
Thanks
Edit:
I create the view doing the following:
NextViewController *nextController = [[ReservationsViewController alloc] initWithNibName:#"NextViewController" bundle:nil];
nextController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:nextController animated:YES];
[nextController release];
But i don't want to allocate memory every time is shown, only the first time the view is loaded in order to be right from the point of view of memory-management.
If you only want it to be allocated once then you should alloc it either in the init method or in awakeFromNib depending on how your view controller is created.
viewWillAppear will likely be called multiple times and viewDidLoad is not guaranteed to be called only once.
If the array is a property just release it in the dealloc like so:
- (void)dealloc {
[someArray release];
[super dealloc];
}
If for some reason you switch to creating a local array in the viewDidLoad or viewWillAppear method make sure you release it as soon as your done with it.
If you allocate memory in the viewDidLoad method it will be done only when the view is loaded to memory. If you do this in the viewWillAppear you will be doing this every time the view becomes visible.
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.