One of my UIViewController has several child view controllers. They are built inside the interface builder, by dragging an NSObject to the "Objects" section at left, then put in my own SpecialViewController as "Custom Class". With this setup, upon viewDidLoad I'll have my views and the controller ready. The workflow is suggested by the following screenshot:
And in my implementation, I have:
#interface ParentController : UIViewController
{
SpecialViewController *svc;
}
#property (nonatomic, retain) IBOutlet SpecialViewController *svc;
As I understand that during didReceiveMemoryWarning I should release my own resources. Then set IBOutlets to nil during viewDidUnload.
I get crashes when simulating low memory in the simulator, with debugger pausing at didReceiveMemoryWarning of SpecialViewController (whose body is just [super didReceiveMemoryWarning];), with error EXC_BAD_ACCESS (code=1, address=0xe0000008). At this time, the parent controller isn't visible, so it can be safely released.
Parent controller also contains only [super didReceiveMemoryWarning]; in didReceiveMemoryWarning. I've tried niling IBOutlets in both classes. It didn't help.
Any idea why this happened?
I'm targeting iOS 4 & 5 with ARC. SpecialViewController is a subclass of UITableViewController.
Through tracing, I found that ParentController didReceiveMemoryWarning is called before SpecialViewController.
It seems like you have a view controller with in a view controller here. Is there any particular reason that you have chosen to create the class like this? In my experience each UIViewController should be a separate subclass. Based on the fact that your error arises in didReceiveMemoryWarning, I believe that the issue is elsewhere. Can you share your initialization code for this View Controller?
If you are attempting something like UIViewController Containment, you should probably check out the WWDC topic that covers this process.
Update iOS > 6.0:
Views are no longer purged under low-memory conditions and so this method is never called [1].
View controller's didReceiveMemoryWarning default implementation will release their view; your responsibility is only to release any other views created in viewDidLoad or loadView, as well as any strong references to IBOutlet subviews.
You have a strong reference to another view controller, which will not be recreated when the view becomes visible again. You should not use the didReceiveMemoryWarning to release your the sec instance variable; instead, rely on the SpecialViewController's didReceiveMemoryWarning implementation to take care of releasing it's view.
In practice, I use didReceiveMemoryWarning to release my main view (self.view) and use viewDidUnload to release anything created in viewDidLoad. I find the balance of the names of the methods and their uses intuitive. If you create something in viewDidLoad, release it in viewDidUnload.
Related
I've looked through lots of posts, my books and Apple Developer and gleaned most of the understanding I need on use of these. I would be really grateful if some kind person could confirm that I've got it right (or correct me) and also answer the two questions.
Many thanks,
Chris.
Order of Messages
Generally, the messages will appear in the following order:
didReceiveMemoryWarning
viewDidUnload (which can be caused by 1) - obviously only applies to View Controller Classes.
dealloc
didReceiveMemoryWarning
Called when the system is low on memory.
By default, view controllers are registered for memory warning notifications and within the template method, the call to [super didReceiveMemoryWarning] releases the view if it doesn't have a superview, which is a way of checking whether the view is visible or not. It releases the view by setting its property to nil.
Action - Release anything you do not need, likely to be undoing what you might have set up in viewDidLoad. Do not release UI elements as these should be released by viewDidUnload.
Question1 - It seems that this will be called even if the View is visible, so its difficult to see what you could safely release. It would be really helpful to understand this and some examples of what could be released.
viewDidUnload
Called whenever a non visible View Controller's View property is set to nil, either manually or most commonly through didReceiveMemoryWarning.
The viewDidUnload method is there so that you can:
- clean up anything else you would like, to save extra memory or
- if you've retained some IBOutlets, to help free up memory that wouldn't otherwise be released by the view being unloaded.
Action - generally any IBOutlets you release in dealloc, should also be released (and references set to nil) in this method. Note that if the properties are set to retain, then setting them to nil will also release them.
dealloc
Called when the view controller object is de-allocated, which it will be when the retain count drops to zero.
Action - release all objects that have been retained by the class, including but not limited to all properties with a retain or copy.
Popping View Controllers and Memory
Question 2 - Does popping a view remove it from memory?
Some corrections and suggestions:
didReceiveMemoryWarning practices
As you said, the controller's default implementation of didReceiveMemoryWarning releases its view if it is 'safe to do so'. While it's not clear from Apple's documents what 'safe to do so' means, it is generally recognized as it has no superview (thus there is no way that the view is currently visible), and its loadView method can rebuild the entire view without problems.
The best practice when you override didReceiveMemoryWarning is, not to try releasing any view objects at all. Just release your custom data, if it is no longer necessary. Regarding views, just let the superclass's implementation deal with them.
Sometimes, however, the necessity of the data may depend on the state of your view. In most cases, those custom data is set in viewDidLoad method. In these cases, 'safe to release custom data' means that you know that loadView and viewDidLoad will be invoked before the view controller uses the custom data again.
Therefore, in your didReceiveMemoryWarning, call the superclass implementation first, and if its view is unloaded, then release the custom data because you know that loadView and viewDidLoad will be invoked again for sure. For example,
- (void)didReceiveMemoryWarning {
/* This is the view controller's method */
[super didReceiveMemoryWarning];
if (![self isViewLoaded]) {
/* release your custom data which will be rebuilt in loadView or viewDidLoad */
}
}
Be careful not to use self.view == nil, because self.view assumes that the view is needed for someone and will immediately load the view again.
viewDidUnload method
viewDidUnload is called when the view controller unloaded the view due to a memory warning. For example, if you remove the view from the superview and set the view property of the controller to nil, viewDidUnload method will not be invoked. A subtle point is that even if the view of a view controller is already released and set to nil by the time the controller receives didReceiveMemoryWarning, so actually there is no view to unload for the controller, viewDidUnload will be invoked if you call the superclass's implementation of didReceiveMemoryWarning.
That's why it's not a good practice to manually set the view property of a view controller to nil. If you do, you may better send a viewDidUnload message as well. I guess your understanding of viewDidUnload is more desirable, but apparently it's not the current behavior.
Popping view controllers
If you mean 'removing from the superview' by 'popping', it does decrease the retain count of the view, but not necessarily deallocate it.
If you mean popping out from a UINavigationController, it actually decrease the retain count of the view controller itself. If the view controller is not retained by another object, it will be deallocated, desirably with its view. As I explained, viewDidUnload will not be invoked this time.
Others...
Technically, the retain count may not go down to zero. The object is more likely to be just deallocated without setting the count to zero beforehand.
Just to make sure, the view controller itself is normally not deallocated by default behaviors due to the memory warning.
didReceiveMemoryWarning
...
Action - Release anything you do not need, likely to be undoing what you might have set up in viewDidLoad.
This is wrong. Anything that you recreate in viewDidLoad should be released (and set to nil) in viewDidUnload. As you mention below, didReceiveMemoryWarning is also called when the view is visible. In didReceiveMemoryWarning, you should release stuff like caches or other view controllers you are holding on to that can be recreated lazily the next time they are required (i.e., by implementing their getter manually).
viewDidUnload
...
Action - generally any IBOutlets you release in dealloc, should also be released (and references set to nil) in this method. Note that if the properties are set to retain, then setting them to nil will also release them.
Correct. Generally, everything you create in viewDidLoad and all IBOutlets that are declared as retain should be released and set to nil here.
dealloc
...
Action - release all objects that have been retained by the class, including but not limited to all properties with a retain or copy.
Correct. It's worth noting that this includes all objects you handle in viewDidUnload because the latter is not implicitly called in the dealloc process (AFAIK, not entirely sure). That's why it is essential to set all releases objects to nil in viewDidUnload because otherwise you risk releasing something twice (first in viewDidUnload, then again in dealloc; if you set the pointer to nil, the release call in dealloc will have no effect).
Popping View Controllers and Memory
Question 2 - Does popping a view remove it from memory?
Not necessarily. That is an implementation detail that you should not be concerned about. Whatever the current practice is, Apple could change it in the next release.
Just to update this thread to make it iOS6-relevant:
viewDidUnload and viewWillUnload were deprecated in iOS6. These methods are never called.
For this and other deprecated methods, see: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/DeprecationAppendix/AppendixADeprecatedAPI.html
Form iOS 6 onwards, how we can check whether the view loaded again. Since "viewDidUnload" is deprecated. Are you sure "loadView" and "viewDidload" will call if the view is getting removed after "didReceiveMemoryWarning" warning.
I use a .xib to layout my viewController and have 10 IBOutlet objects that IB uses, none of them have #properties assigned to them (no #property(retain) IBOutlet...). When I release the viewController none of those objects are being released, I have to manually release them in the dealloc of the viewController.
This doesn't seem like normal behavior, I thought that those objects would be released by the system since IB is whats assigning them, what could I be doing wrong?
Apple's Memory Management Programming Guide tells you exactly how to properly release IBOutlets.
In short, you are supposed to release them in dealloc, but you also want to release them in viewDidUnload.
I tend to release my stuff in -dealloc, and now iPhone OS 3.0 introduced this funny -viewDidUnload method, where they say:
// Release any retained subviews of
the main view. // e.g. self.myOutlet
= nil;
So -viewDidUnload seems to get called when the view of the view controller has been kicked off from memory. And if I have subviews attached to the main view of the view controller, I have to release that stuff only HERE, but not in -dealloc as well?
That's confusing. Also, what if -dealloc causes the view to be unloaded (released)? Then again, it will call -viewDidUnload?
I do realize the difference, that -viewDidUnload is just for the case where the view itself gets killed, but the view controller stays in memory. And -dealloc is for the case where the whole thing goes to trash.
Maybe someone can clear up the confusion.
The intent here is to "balance out" your subview management. Anything that you create in viewDidLoad should be released in viewDidUnload. This makes it easier to keep track of what should be released where. In most cases, your dealloc method is a mirror-image of your init method, and your viewDidUnload will be a mirror image of your viewDidLoad method.
As you pointed out, the viewDid... methods are to be used when the view itself is loaded and unloaded. This permits a usage pattern in which the view controller remains loaded in memory, but the view itself can be loaded and unloaded as required:
init
viewDidLoad
viewDidUnload
viewDidLoad
viewDidUnload
...
dealloc
Of course, it doesn't hurt to release things in your dealloc method as well, as long as you set them to nil when you release them in viewDidUnload.
The following quote from the Memory Management section of Apple's UIViewController documentation, describes it in more detail:
...in iPhone OS 3.0 and later, the viewDidUnload method may be a more appropriate place for most needs.
When a low-memory warning occurs, the UIViewController class purges its views if it knows it can reload or recreate them again later. If this happens, it also calls the viewDidUnload method to give your code a chance to relinquish ownership of any objects that are associated with your view hierarchy, including objects loaded with the nib file, objects created in your viewDidLoad method, and objects created lazily at runtime and added to the view hierarchy. Typically, if your view controller contains outlets (properties or raw variables that contain the IBOutlet keyword), you should use the viewDidUnload method to relinquish ownership of those outlets or any other view-related data that you no longer need.
As you say viewDidUnload will be called if self.view=nil, this generally occurs if you get memory warning. In this method you must release any subview of the mainview which can easily be created by .xib or loadView method. You should release any data object if you create them in viewDidload or loadView etc. because these methods will be called again to present view to the user, those data can be recreated easily.
When you get a memory warning usually the viewcontroller will unload it's view but itself will not be dealloc.
All that can be re-created easily should be unloaded, but not the model of the view.
I have an app which consists multiple tabs managed by the class derived from UITabBarController (the only reason I subclassed UITabBarConteroller is to handle shake event for all views). 3 views are from subclassed UIViewContentroller class, one view is UINavigationController which shows a table.
In every single controller I have, including my subclassed one, I have this:
(void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
App works great. My question is how is default implementation of UITabBarController handles automatic view unloading on low memory conditions.
Only controller derrived from UINavigationController unload automatically (I see viewDidUnload called). viewDidUnload is NOT called for any other view controllers managed by UITabBarController.
Why?
Thanks
viewDidUnload gets called when your view controller's view is removed and deallocated, and this happens after the VC receives didReceiveMemoryWarning. Check for that method being called first. If it's not being called, something else is going on, but if it is being called, then perhaps your view is over retained or not being removed properly.
What is the -(void)viewDidUnload is good for?
Could I not just relase everything in -dealloc? If the view did unload, wouldn't -dealloc be called anyway?
In addition to what has already been indicated, I wanted to elaborate more about logic behind -viewDidUnload.
One of the most important reasons for implementing it is that UIViewController subclasses commonly also contain owning references to various subviews in the view hierarchy. These properties could have been set through IBOutlets when loading from a nib, or programmatically inside -loadView, for instance.
The additional ownership of subviews by the UIViewController means that even when its view is removed from the view hierarchy and released to save memory, through which the subviews are also released by the view, they will not actually be deallocated because the UIViewController itself still contains its own outstanding retaining references to those objects as well. Releasing the UIViewController additional ownership of these objects ensures they will be deallocated as well to free memory.
The objects that you release here are usually recreated and set again when the UIViewController view is re-loaded, either from a Nib or through an implementation of -loadView.
Also note that the UIViewController view property is nil by the time this method is called.
As the documentation says:
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.
In the same situation dealloc is not called. This method is only available in OS3 and above. Dealing with the same situation in iPhone OS 2.x was a real pain!
Update July 2015: It should be noted that viewDidUnload was deprecated in iOS 6 because "Views are no longer purged under low-memory conditions and so this method is never called." So, the modern advice is not to worry about it and use dealloc.
This is because you will typically set the #property as "(nonatomic, retain)" and as such the setter that is created for you releases the current object and then retains the argument i.e.
self.property = nil;
...does something along the lines of:
[property release];
property = [nil retain];
Therefore you are killing two birds with one stone: memory management (releasing the existing object) and assigning the pointer to nil (since sending any message to a nil pointer will return nil).
Hope that helps.
Remember that viewDidUnload is a method in the view controller, not in the view. The view's dealloc method will get called when the view unloads, but the view controller's dealloc method may not be called until later.
If you get a low memory warning and your view isn't showing, which will happen for instance about any time you use a UIImagePickerController to let the user take a picture, your view will get unloaded and will need to get reloaded after that.
Conclusion:
View Controllers have a view property. Typically a nib or piece of code adds other views to this view. This happens often inside a -viewDidLoad method, like this:
- (void)viewDidLoad {
[super viewDidLoad];
[self createManyViewsAndAddThemToSelfDotView];
}
in addition, a nib file may create a button and append it to the view controller's view.
On iPhone OS 2.2, when -didReceiveMemoryWarning was invoked from the system, you had to release something to free up memory. You could release the whole view controller's view if that made sense. Or just big memory-consuming contents in it.
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
Now, in the new OS 3.0, there is an -viewDidUnload method, which will be invoked from the system when the view has been unloaded because of low memory (please correct me: when exactly does this get called?)
-viewDidUnload is used to release all objects that were owned both by the view controller itself and the view. The reason: If a view controller holds references to childs of the view, i.e. a button, the referenced child views will not get released, because their retain count is >= 1. After they are released in -viewDidUnload, they can get freed up from memory.
Apple deprecated viewWillUnload, now you shoud use didReceiveMemoryWarning or dealloc to release your objetcs.
In iOS 6, the viewWillUnload and viewDidUnload methods of
UIViewController are now deprecated. If you were using these methods
to release data, use the didReceiveMemoryWarning method instead. You
can also use this method to release references to the view
controller’s view if it is not being used. You would need to test that
the view is not in a window before doing this.
If the view controller is popped from the navigation controller stack and is not retained anywhere else, it will be deallocated, and dealloc will be called instead of viewDidUnload. You should release the views created in loadView in dealloc, but it is not necessary to set the variables to nil, because soon after dealloc is called the variables will no longer exist.
You can release any subviews you hold on to, for example that UIImageView you retained in your loadView method, or better yet the image that was on that UIImageView.