I have a navigation controller-based application with 5 view controller inside. When I push a view controller I allocate some memory and when I go back with popViewController my delloc() method is correctly called. I'm sure that the dealloc is called in the right way for every view controller I push and pop.
Nevertheless when I run the application with Instruments (Start with performance tools -> Object allocations, Leaks) there is a strange behavior for me.
When a view controller is pop the memory usage does not decrease, to be exact it does not decrease as expected:
when I start the application it use 950 KB, then I push the first view controller and the memory usage increase up to 1,56MB, finally I pop the view controller and the memory usage is now 1,55MB.
Is this behavior right?? I'm sure that every dealloc method is correctly called whenever I pop a view and the Leaks instrument does not show any memory leak.
I guess that the operating system is "retaining" in some way the view so that to second time I push the same view controller the loading process is much more fast...
Could someone confirm that this behavior is right?
Thanks
See this Screenshot from Instruments
This is as expected. The memory handling rules of "you are only accountable for objects you did alloc, copy etc. on" applies here as well.
When you push stuff on to the navigationController I assume you do it like this:
MyController *myCon = [[MyController alloc] init];
[self.navigationController pushViewController:myCon animated:YES];
[myCon release]; //You have alloc and release.
The navigationController is often handling a hierarchy where a user drills down a data set, and up again. By holding onto your controllers when memory is plenty is the navigationControllers way of saving having to instantiate the controller again 5 sec. later when the user taps "back". You can see this because dealloc never gets called, but viewWillAppear and viewDidAppear are called when you back up.
If memory is lacking the navigationController will start to release controllers on its stack.
But! make sure that going back and forward does not result in the viewControllers being instantiated again and again, this will make the memory footprint grow and there is a leak.
The navigationController should notice that it already has the viewController in its stack and simply display it.
You should be able to move through all the views and if they "fit" in memory, the app should never increase its memory footprint from here on out.
Related
I'm working on an app where different view controllers get pushed and dismissed via dismissModalViewControllerAnimated.
I'm having some memory issues with the app just crashing after a while. Looking at the Leaks instrument, I see that my overall allocations keeps going up and up. Even after the viewcontroller is dismissed, memory does not go down.
Are there any obvious reasons for this? What is the simplest and easiest way to find out why my app is crashing? Thanks
POSSIBLE SOLUTIONS
I went through some trial and error as well as googling and made a few changes:
1) A delegate relationship may have been retaining the viewController, so I changed the object's delegate property to weak.
2) NSTimer's should be invalidated before dismissing viewController.
3) UIView animations may interfere with dealloc being called? You can use [view.layer removeAllAnimations] to end them before popping your viewController.
If your memory is not go down after the dismissModalViewControllerAnimated .. it means you are creating the Global Object of the ViewController And after dismissing you are not setting the Object = nil;
If you set nil then your memory goes down automatically.
I have a simple app with a tabbar navigation interface.
It has three view (A, B and C) and a modal view. Each view has its own view controller and nib . They are all designed and connected in the interface builder.
I'd like to release views which are not visible.
Tried release and nil them when another view appears such as
[[[self.navigationController.viewControllers objectAtIndex:0] view] release];
[[self.navigationController.viewControllers objectAtIndex:0] view] = nil;
etc.
It doesn't cause any issues but when I run instruments it does not make any difference. I don't see any drop in memory usage
I would appreciate your help
as #Daryl Teo wrote you should release and recreate in viewWillDis/Appear and (thats why I write this answer) you have a method called didReceiveMemoryWarning, use it!
You can simply log out whenever it gets called and test it with the Simulator included memory warning test function.
Simply open a tab, open another tab and call that test function. Your debug console should print out the log. If not you should double check if you have released all objects maybe someone is over-retained (which again should be released in viewWillDisappear).
The drop in memory usage might not be significant, depending on what the released viewController holds on to. I sugest you out a NSLog into the 'dealloc' of the viewController to see if it really gets dealloced or if there is some other object still holding on to it. Remember that release won't free the memory, it will only do so (by calling dealloc) if the objects retain count reached 0.
You don't want to do this. Let the TabBarController handle your view controllers for you. (It will already retain your viewController internally so whatever you do will only make retain count go out of sync)
You may be able to make it more memory efficient if you release objects in viewWillDisappear. Then rebuild the data again in viewWillAppear.
For the UIVIewController which is released and pushed on the navigation stack, memory allocated to it gets deallocated when view is popped.
But what about viewcontroller which is rootview controller i.e the first member on navigation stack which in turn bound to tab bar?
How to trace memory leak in it??
If it's never removed during the course of the app running, you don't need to worry about it leaking its member variables, because they'll always be required while the app is open.
When the app closes, the OS cleans up all the memory allocated to the app, even if it has leaked, so there's nothing to worry about.
For testing purpose I pushed the viewController from some dummy viewController...
I put an NSlog in my dealloc method of my view controller. It does not get consistently called. I do notice that ViewWillDisappear does get called always. Would it be ok to move all my tidy upcode here? Setting stuff to Nil and release calls.
Anybody got some advice on why dealloc is not getting called? I know it says in docs it may not get called, but if you have a very simple App it gets called always. So something i do must be affecting the dealloc.
This is the code that calls my ViewController than isnt always calling my dealloc.
-(IBAction) playComputerTapped:(id)sender
{
PlayGameViewController *pgvc = [[PlayGameViewController alloc]
initWithNibName:#"PlayGameViewController" bundle:[NSBundle mainBundle]];
pgvc.gameMode = 1;
[self presentModalViewController:pgvc animated:YES];
[pgvc release];
}
The above code takes me from the mailmenu ViewController into the game.
Below is the code to leave the gameViewController and take me back to the menu.
[self.parentViewController dismissModalViewControllerAnimated:YES];
Thanks
-Code
Don't you mean viewDidUnload instead of viewWillDisappear ?
viewWillDisappear is called when the view controller is disappearing. This usually happens when the view controller is being popped out, or other view controller is pushed to the stack. Purpose of viewWillDisappear is to stop active actions - for example stop animations, hide some elements or similar.
viewDidUnload is probably what you meant as this one is called when view controller's view is unloaded. This should never happen for a view controller that is currently visible, only for controllers that are somewhere in the navigation stack (part of UITabBarController or UINavigationController), but not currently visible. Purpose of viewDidUnload is to release any UI elements that are part of the view and that the view controller retained as well.
To understand it's important to realize the reasons why a view for such controller would want to be unloaded. Reason is memory consumption. Views consume considerable amount of memory, even when they are not currently visible. Views are usually very easy to reconstruct - simply by calling the code that constructed them in the first place. That's especially very easy if you are using Interface Builder. So these views are best candidates to be freed to gain more memory.
When system doesn't have enough memory it starts calling didReceiveMemoryWarning method of view controllers which are not currently visible. If you have created your controllers from template in Xcode, this method simply calls super's (UIViewControllers's) implementation [super didReceiveMemoryWarning]. That default implementation will release self.view, which in turn should deallocate it together with all its subviews.
Now let's say that your view-controller needs access to some of the subviews to manipulate it in some way. For example you can have a UILabel element there and you want to change its content accordingly. To access this element you create a member variable (IBOutlet) and connect it to the element. Now your view-controller owns that label, so its retain-count is increased. When controller's view is released, so is the label, but because your view-controller still retains the label, it will not be deallocated. Therefore you should release the label in viewDidUnload method.
I've seen applications that were creating views programmatically (in loadView method), but loading was done in such dirty way that it was not possible to reconstruct the view after it was once deallocated. Therefore each time system was out of memory, it had called didReceiveMemoryWarning which in turn deallocated the view, but after navigating back to that view-controller application had crashed. A fast "bugfix" was to remove calling [super didReceiveMemoryWarning] in view-controllers. Well, system didn't get the memory and some strange effects occurred, but at least the application didn't crash immediately.
Now the third one - dealloc. This is called when object is not owned by anyone and its memory is going to be freed. Here you need to release all objects that you have retained. For view-controllers those are usually references to model classes.
I want to describe one more possible scenario. Let's say you have a view-controller displaying a chat with another person. Let's say it's very fancy chat, with emoticons and buddy-icons being animated. Let's say that each chat-entry is displayed as a cell of UITableView.
When your buddy sends you a message, you want to append a new cell into table-view by reloading it. Therefore your view-controller has an outlet to the table-view.
In viewWillDisappear you should stop the animations of emoticons and icons.
In viewDidUnload you should release the table-view.
In dealloc you want to release chat's history (probably NSArray of all messages sent and received during this conversation).
Now if you navigate away from your chat, viewWillDisappear gets called and you stop the animations.
When system is low on memory, and your view-controller is not visible, didReceiveMemoryWarning gets called and the view is released. Your viewDidUnload gets called and you release UITableView so that it can be really deallocated.
When you navigate back to the chat, loadView gets called again and your view is constructed again, viewDidLoad is called after that. Your model (representation of chat conversation) is still there, so the table-view's datasource has all the data as before, so table-view will display exactly the same thing as before the view was deallocated. After all viewWill/DidAppear is called where you start the animations.
When you finish chatting with your friend, you release the view controller, and it gets deallocated - dealloc is called, you release an array containing the chat messages, and cleanup everything else.
I hope it make things a little clearer.
Well that depends.
If you need to reduce the memory footprint of your app, unloading stuff while the view is not visible via viewWillDisappear is a good way to go. However you'll need to re-initalize everything once the view will be shown again, depending on its content this may produce considerable overhead - maybe even without the need.
For a small app (in terms of memory usage), using dealloc to unload your stuff is fine. It will be called only if the objects retain count drops to zero and is the last method that will be run before the object is destroyed. When using autorelease, that may not be the case right away and of course the object could be retained by some object other than the parentviewcontroller, preventing it from being destroyed and thus dealloc from being called.
You might want to check out the Memory Management Programming Guide, it'll explain things more detailed.
what are best pratices to reuse UIViewControllers? In many apps (including Apple's own examples: e.g. SQLiteBooks), UIViewControllers are allocated and initialized everytime, a UIViewController is pushed to the stack. This increases the use of memory with every new controller, because the objects stay in memory and aren't used again.
How to make it better?
This increases the use of memory with
every new controller, because the
objects stays in the memory and aren't
used again.
It should be released when the stack is popped though, as long as you have not got something else holding on to it. Check your dealloc methods are getting called.
Also if it is pushed to the stack, then you need to keep it around at least until it is popped (which automatically happens if you follow the standard patterns). So it is used again.
So following the standard pattern should already keep your memory usage as small as you can get away with.
This is what I do when creating a new viewcontroller and the memory is released when the view is removed from the window
MyViewController *mvc = [[[MyViewController alloc] initWithNibName:#"MyView" bundle:nil] autorelease];
[[self navigationController] pushViewController:mvc animated:YES];
Do you actually have a memory issue that you are trying to address or is this a case of premature optimization? I would say that unless there is a specific resource issue then the best practice would be to follow the standard view controller patterns.
Put a breakpoint in your view controller's dealloc function, and make sure it is called when you remove the view controller from the window. The memory shouldn't keep building up. If you're properly creating and autoreleasing your controllers (as LostInTransit shows above), the memory for each controller should be released when it is removed.
If you see that dealloc is not getting called, it means that somewhere in the app a reference to the view controller still exists.
Don't forget that a View Controller is not your view.
Views held by a view controller can unload, so view controllers themselves are very lightweight. If you want to keep the footprint really light you could nullify any other data the controller has allocated in viewDidUnload (mostly called when there's a memory warning - it's a 3.0 only thing though).
As noted mostly view controllers will be deallocated when you leave them (hit back) so there aren't generally that any hanging around anyway. But sometimes I find it handy to leave a reference around if I want to re-open that view in the same state the user left it (does not work between app launches).