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.
Related
I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)
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 some memory issue with my iPhone application and I have no clue what is happening.
So, I observed that the memory usage of the application is rising continuously when going from an UIViewController to another. I have used the "Mark Heap" tool from the "Allocations" instrument and it seams that the only objects that are not deallocated are my UIViewControllers.
To be more specific I have let take my two UIViewControllers. The first one is named PuzzleViewController and the second one is named Options. When the app starts, the PuzzleViewController appears. I mark a heap here, to set a baseline, and after this I press the "Options" button which will present the Options UIViewController. I go back to the first one and I mark a heap again. After repeating these steps over and over again (like 20 times or so :D) I observe that after every Heapshot I have about 22 objects remaining alive. Two of those objects are instances of my UIViewControllers.
I really don't have any clue what is happening.
Here is how I switch to the Options UIViewController:
- (IBAction) onOptionsButton: (id) sender
{
Options *viewController = [[Options alloc] init];
[self presentModalViewController:viewController animated:YES];
[viewController release];
}
And here is how I go back to the PuzzleViewController:
- (IBAction) onMainMenu:(id) sender
{
PuzzleViewController *modalView = [[PuzzleViewController alloc] init];
[self presentModalViewController:modalView animated:YES];
[modalView release];
}
My viewDidUnload functions are called properly, but no dealloc function is ever called.
Thank you,
Andrei
You should call dismissModalViewController, not presentModalViewController again, nor recreate your PuzzleViewController.
Dismissing a Modal View Controller
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.