I have a UIViewController that needs to make use of the UINavigationControllerDelegate, specifically the willShowViewController method.
I am setting <UINavigationControllerDelegate> in my implementation, then I am setting the delegate to self in viewDidLoad (self.navigationController.delegate = self;). Then I implement the willShowViewController method and it works fine, however when the view controller is popped off the stack, there is a memory leak and my app is crashing. I've tried doing self.navigationController.delegate = nil; in both viewDidUnload and dealloc but it doesn't help matters.
What is the correct way I can implement this delegate for use in just one of my viewcontrollers?
viewDidUnload will not necessarily ever be called (it's mostly for handling low memory conditions) and by the time dealloc is called, the view controller is probably no longer contained in the navigation controller, so self.navigationController would be nil.
I'd suggest setting the delegate to nil in your viewWillDisappear: implementation (and setting it in viewWillAppear: instead of viewDidLoad).
Btw, you're seeing the exact opposite of a memory leak here. A memory leak would be memory that cannot be reached anymore and will never be freed. Here you have memory that has already been freed (your view controller), but is still referenced by a (dangling) pointer, leading to the crash. A real leak would usually not result directly in a crash.
You should either keep a weak (non-retaining) reference to the navigation controller or reset its delegate when it becomes clear the navigation controller is going to pop your controller. The truth is, in dealloc self.navigationController is already nil, and viewDidUnload is not sent when your controller gets popped.
you should setting the delegate is self in your [viewDidAppear:] implementation and setting the delegate is nil in your [viewWillDisappear:] implementation.
Tips: you shouldn't set the delegate is nil in dealloc implementation, because when the dealloc is called, the viewController is popped from the navigationcontroller stack so self.navigationController must be nil.
Related
I have a navigation controller with 4 VCs pushed into it. I have a singleton class with a delegate property that is set to the VC which is on top of the stack. I am setting this delegate to nil in the dealloc method of each VC. I am setting the delegate in the viewdidappear method of the rootVC.
When I pop back to root VC from the 4th VC, the sequence of calling the dealloc methods (of all the VCs in stack) and viewdidappear method is following:
"FirstVC dealloc called"
"SecondVC dealloc called"
"viewdidappear of root VC is called"
"ThirdVC dealloc called"
Now, the issue I am facing is that the delegate gets set to nil even though I am setting it to self in the root VC's viewdidappear method (which is visible from the control flow too). How can I prevent this situation? I want the viewdidappear method to get called once all the VCs are really deallocated.
Thanks,
Obaid
Since you can't predict the order of method calls unless Apple publishes some guarantee of what they are, perhaps you could program the singleton to be defensive by creating a method such as:
- (void)removeDelegate:(UIViewController *)oldDelegate;
If the delegate matches the specified old delegate, set it to nil.
dealloc is called automatically once a object is no longer needed. When you pop the ThirdVC, since the delegate property is still retaining it, dealloc doesn't get called. Then, when your rootVC's viewDidAppear gets called, it sets the rootVC as the delegate. At this momment, your thirdVC is no longer needed, which triggers the dealloc.
One thing you could do is set the delegate property to nil not on dealloc, but on the viewWillDisappear method of each ViewController, since this method will surely get called before the next ViewController appears.
Also would anybody tell me what's the difference between viewDidUnload and dealloc?
viewDidUnload : 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. More
dealloc: Deallocates the memory occupied by the receiver, An object’s dealloc method is invoked indirectly through the release NSObject protocol method. More
Yes, it could be unloaded if a memory warning occurs.
As for the difference between viewDidUnload and dealloc - the former is called when your view is unloaded, typically because of a low memory situation. The latter is called when your object's retain count reaches zero (i.e. it is completely released from memory)
UIViewControllers are never unloaded. The UIViews they own can be.
So, if you question is if a UIView can be unloaded although its controller has been pushed on to the navigation controller, the answer is yes. Only the currently displayed UIView will not be unloaded (if you don't prevent the unloading mechanism from working).
Furthermore, viewDidUnload is a message that is sent to a UIViewController when the view it manages has been unloaded, which usually is not the same as deallocating the view. In fact, when a view is actually deallocated, it is unloaded for sure, but viewDidUnload is not sent.
A UIViewController that has been pushed onto a nav controller stack, and hasn't yet been popped, will not be dealloc'd. However, its view property may be unloaded -- in particular, if a low memory condition occurs AND the view for that view controller isn't currently visible (something is covering it, like a modal dialog, or another VC pushed on top of it), the view may be unloaded by the system.
viewDidUnload is a bad name for what the method does. It is called when a low memory condition caused the view to be unloaded -- i.e. it is not the 'opposite' method to viewDidLoad, which I think you might reasonably expect.
More info:
When should I release objects in -(void)viewDidUnload rather than in -dealloc?
Also important is that viewDidLoad will be called again after viewDidUnload. If you perform setup in viewDidLoad, you should deal with it in such a way that a second call to it will not cause memory leaks.
Either tear everything down in viewDidUnload (polite, if they are memory intensive), or check for their existence when you set them up and don't do it twice. (And, of course, totally tear them down before or during the dealloc method.)
You are not guaranteed that viewDidUnload will be invoked before dealloc is.
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 put NSLog(#"%#::%#", [[self class] description], NSStringFromSelector(_cmd)); in both viewDidLoad and viewDidUnload of a view controller.
In the log, I found viewDidLoad is called a lot more than viewDidUnload when the app moves to and from different .nibs.
Why?
The viewDidLoad and viewDidUnload is not corresponding to each other.
The viewDidUnload will only be called when you receive a memory warning. The system then will automatically call your viewDidUnload.
In the normal case, when you push a MyViewController and pop it out. The life cycle will happens like this:
init
viewDidLoad
release
That means, whenever you init and push/present a view, a viewDidLoad will be called. But when you pop the view, the release will be called in normal case and viewDidUnload will be called in memory warning case.
This is quite implicit and Apple doesn't state it clearly on the Guide. Here is some reference: Load and Unload cycle
I imagine that in the cases where -viewDidUnload wasn't called, the view controller was released.
viewDidLoad: controller loads view
viewDidUnload: memory warning, controller unloads view
viewDidLoad: controller loads view again
-: controller gets released, doesn't explicitly unload the view
You and up with 2 -viewDidLoad calls and 1 `-viewDidUnload' call.
Maybe also put a NSLog into the -dealloc method and see if the number of -dealloc and -viewDidUnload calls combined matches the number of -viewDidLoad calls.
when a new view loads, the old view can still be loaded in the background.
you are searching for viewWillAppear as conterpart i think.
views only unload in case of a memorywarning.
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.