ViewWillDisappear versus dealloc - iphone

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.

Related

Storyboards and memory management

I decided to use storyboards in a project I have been doing. When the app launches it does the right thing of awakeFromNib and then viewDidLoad, but when the app has finished segueing to another view, it doesn't call viewDidUnload and, I think, neither does dealloc. I have used Apple's Instruments and doesn't show any memory leaking.
Just to note, I am using custom segues and testing this by inserting NSLogs into the methods. Has anyone else come across this?
Just want to update: dealloc actually is called but not viewDidUnload.
The viewDidUnload method is solely for the purposes of didReceiveMemoryWarning (i.e. when the view is being removed to recover some memory, but the view controller is not). If you want to see viewDidUnload while running in the Simulator, push or presentViewController to a secondary view, then generate a memory warning from the Simulator's menus. I quote from the UIViewController Class Reference:
When a low-memory condition occurs and the current view controller’s views are not needed, the system may opt to remove those views from memory. [The viewDidUnload method] is called after the view controller’s view has been released and is your chance to perform any final cleanup. If your view controller stores separate references to the view or its subviews, you should use this method to release those references. You can also use this method to remove references to any objects that you created to support the view but that are no longer needed now that the view is gone. You should not use this method to release user data or any other information that cannot be easily recreated.
At the time this method is called, the view property is nil.
viewDidUnload is called when the view is actually unloaded. If you want to clean up your resources when the view is not displayed put that in viewDidDisappear.
If you want to see what is happening with viewDidUnload, run your app in the simulator and from the menubar choose Hardware | Simulate Memory Warning.
Under memory pressure, views that are not on screen are removed and that is when the viewDidUnload method is sent.

UIViewController not releasing subviews when dealloc (using ARC)

I have what seems like a weird (non?) issue with UIViewController. It appears that the controller is not releasing its subviews when it is dealloc'd. I placed NSLog messages in all of the subview's dealloc method as well as the view controller. The view controller dealloc gets called but the subview's do not. However, if I then push another instance of that view controller on to the navigation stack, it appears that all of the subviews of the previous instance are then released (I get a bunch of NSLog messages in the console letting me know). I've check and I have no separate reference to the custom view controller in the presenting view controller (the one that's doing the pushing).
One small (maybe) detail: The custom view controller does receive a block it stores and then executes before popping. However, I did send nil to it and I get the same behavior. Plus, the presenting view controller does dealloc when popped of the stack, so no retain cycle.
Also, I did try explicitly releasing each view in the dealloc method of the custom view controller. Same behavior.
Is it possible the navigation controller would be holding on to it? It doesn't seem to do this to any of my other view controllers.
My problem is that this does represent a memory leak (of all those subviews); though the leak doesn't stack, it's still a leak.
Ok, this is embarrassing. I did find the problem in another class (called ViewDef) I was inadvertently using as a collection class. It was a quick and dirty way of keeping track of my subviews when I was first figuring out some animations (months ago). ViewDef stored frame/font/color/etc info retrieved from a database, so it was convenient to also store the views when figuring out animations (between orientations). These ViewDefs were being store by my model and passed around, so of course the views were also being retained (and replaced later by another view controller). Anyway, I forgot to insert a warning in my code to fix this later.
Moral of the story: If you plan on doing something stupid, at least document your stupidity so you don't have to broadcast it over the internet later.
you could try setting the subviews to nil in the viewDidUnload method maybe that will help
One thing to try is to make sure all your subviews delegates are set to nil.

Deallocation when iPhone is turned back on from sleep and app is still open

There are a few instances where my app crashes when turned back on from sleep and the app is still open. It happens if a UITableViews is open when the iPhone is put into sleep; the table is being filled from an XML being parsed. I understand why it is crashing; in my viewDidUnload method I am releasing the array that is filling the table. How to handle this I am not sure; yes I could simply not release it in viewDidUnload, but then it would never leave memory if you returned to the main menu.
Any help would be appreciated!
The method viewDidUnload is not the right place to release your data if at all. I quote Apple's documentation, which desribes it better than I could:
This method is called as a counterpart
to the viewDidLoad method. It is
called during low-memory conditions
when the view controller needs to
release its view and any objects
associated with that view to free up
memory. Because view controllers often
store references to views and other
view-related objects, you should use
this method to relinquish ownership in
those objects so that the memory for
them can be reclaimed. You should do
this only for objects that you can
easily recreate later, either in your
viewDidLoad method or from other parts
of your application. You should not
use this method to release user data
or any other information that cannot
be easily recreated.
ViewDidUnload is used only to release view related objects. A view controller can release its view because its not shown, still your instance of that controller exists and so does your model.

iPhone: Reusing UIViewControllers in order to save memory

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).

iPhone Development - Simulate Memory Warning

Background:
I have a tab bar application. Each tab contains navigation controller allowing the user to transition from one view to the other showing a drill down information of the data (each view is being handled by a view controller and each view controller class has didReceiveMemoryWarning method). Lists are populated by pulling the data from web services.
Problem:
When i use "Hardware > Simulate Memory Warning" option of iPhone Simulator, the didReceiveMemoryWarning method is called for ALL my view controllers - even the one which the user is viewing. I don't want to clear any content which is being used by the active view controller. How can I achieve that?
Which method should have the implementation to reload the data after the data was released because of memory warning? (I see that the view controller classes that contain a table view call viewDidLoad method when user comes back to that view, but if the view contains (say UIWebView) then viewDidLoad method is not called. Why is that?)
Edited (Friday 30 January 2009 - 03:10 PM)
(Note: I'm using Interface builder for creating views, and loadView method is commented out.)
So, when a view controller receives a memory warning message, these are the steps that are carried out:
Following method is called:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
As a result of call to [super didReceiveMemoryWarning], [self setView:nil] gets automatically called?
If any resources should be cleared, then setView method should be overwritten to clear local resources.
[self setView:nil] is not called if the view is currently active (By default). Right? - I'm really curious which method takes this decision and how?
Can you please confirm. Plus, I was getting an error following this approach but adding myObject = nil after releasing myObject in dealloc method of controller class fixed the issue. Thanks.
This is an old question, but I don't see a proper answer, so here goes:
When a memory warning is received, -didReceiveMemoryWarning gets called in ALL view controllers, whether they are the "current" one or not. The view controllers are simply listening for the memory warning event broadcast.
If the view controller's view isn't being used at the time of the memory warning, the controller will unload it by setting the property to nil. How does it know if the the view is used? By the view's -superview property. If view.superview is nil, the view isn't part of any tree and can be unloaded safely.
Once that happens, the controller's -viewDidUnload gets called. This is the correct place to unload any outlets, and anything that will get re-created in -viewDidLoad.
So what is -didReceiveMemoryWarning for? Your controller might have objects that don't get instanced until accessed. For example, you could have a controller that sometimes needs a big chunk of data from a file, but not always. You could have a property set for it like this:
- (NSData*)bigChunkOfData {
// Get data from our instance variable _data, read from disk if necessary
if (_data == nil) {
_data = [[NSData alloc] initWithContentsOfFile:#"/path/to/data"];
}
return _data;
}
This will read the data from disk this first time, then keep it in an instance variable. Since the _data variable is created on demand, it's safe for us to unloaded it in low-memory situations: it'll just get created again next time we need it.
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[_data release];
_data = nil; // <-- Very important: don't leave strong references dangling.
}
I do my clean up like this:
-(void)setView:(UIView*)view
{
[super setView:view];
if(view == nil)
{
// Our view has been cleared, therefore we should clean up everything
// we are not currently using
....
setView:nil is called by UIViewController in response to a memory warning, if that view is not currently visible - which is basically what you want to know.
EDITED
In answer to the follow ups:
Correct.
That's what I do, and it works for me.
Correct. The implementation of didReceiveMemoryWarning in UIViewController is what does this. If you don't override didReceiveMemoryWarning, then the base class implementation in UIViewController will be called - if you do override it, obviously you should call:
[super didReceiveMemoryWarning]
To ensure that I dont have to handle this for every single viewcontroller I write.. I have just made a Xcode ViewController template which provides guidelines on which objects to release and when..
more explanation here http://iphone2020.wordpress.com/2010/05/30/efficient-memory-handling-in-uiviewcontroller-part-1/
Hope it finds useful.
In regard to the view management and memory warnings:
UIKit doesn’t only allow navigation back from a view controller, but also allows navigation to other view controllers from existing ones.
In such a case, a new UIViewController will be allocated, and then loaded into view.
The old view controller will go off-screen and becomes inactive, but still owns many objects – some in custom properties and variables and others in the view property/hierarchy.
And so does the new visible view controller, in regard to its view objects.
Due to the limited amount of memory of mobile devices, owning the two sets of objects – one in the off-screen view controller and another in the on-screen view controller – might be too much to handle.
If UIKit deems it necessary, it can reclaim some of the off-screen view controller’s memory, which is not shown anyway; UIKit knows which view controller is on-screen and which is off-screen, as after all, it is the one managing them (when you call presentModalViewController:animated: or dismissModalViewControllerAnimated:).
So, every time it feels pressured, UIKit generates a memory warning, which unloads and releases your off-screen view from the view hierarchy, then call your custom viewDidUnload method for you to do the same for your properties and variables.
UIKit releases self.view automatically, allowing us then to manually release our variables and properties in our viewDidUnload code.
It does so for all off-screen view controllers.
When the system is running out of memory, it fires a didReceiveMemoryWarning.
Off-screen views will be reclaimed and released upon memory warning, but your on-screen view will not get released – it is visible and needed.
In case your class owns a lot of memory, such as caches, images, or the like, didReceiveMemoryWarning is where you should purge them, even if they are on-screen; otherwise, your app might be terminated for glutting system resources.
You need to override this method to make sure you clean up your memory; just remember you call [super didReceiveMemoryWarning];.
An even more elaborate explanation is available here: http://myok12.wordpress.com/2010/11/30/custom-uiviewcontrollers-their-views-and-their-memory-management/
Fortunately, the simulator has a handy function that allows you to put low-memory situations to the test. Put some NSLog() statements in both viewDidLoad and didReceiveMemoryWarning, like this:

- (void)viewDidLoad {
NSLog(#"viewDidLoad");
...
}
- (void)didReceiveMemoryWarning {
NSLog(#"didReceiveMemoryWarning");
}