viewDidUnload sent to deallocated instance after memory warning - ios

I faced a very interesting situation and do not know how to resolve it. I'll outline first the architecture of my program. I have a UITableViewController derived class that also implements a my delegate protocol. The cells in this table view are custom cells, and each of them have a strong (assign) type property to the delegate (the table view controller). The delegate handle some UI action.
To reproduce the crash, I load the table view, and then navigate away from it. Normally here the table view would be dealloced but in my case the cells still hold the strong reference to it, so it remains in the memory. The problem is that I get a crash when a memory warning arrives to the device after this. I deducted that the following happens:
the table view controller receives a memory warning
the it releases all their (reusable) cells
in the dealloc of the cells I nil the delegate property, thus they send a release to the table view controller.
when the last cell nils its property, the ref count of the table view reaches zero, so it will dealloc itself
after freeing the cells, the default implementation of didReceiveMemoryWarning of the table view continues, but already on a dealloced, zombie object
sometimes later it calls viewDidUnload on the zombie and it crashes the app.
How can I resolve this situation?
PS: obviously I don't use ARC

When you're setting a delegate, you should assign it, not retain it (and so the deallocation of the cells should not send a release message to the table view controller).
Delegating objects do not (and should not) retain their delegates.
However, clients of delegating objects (applications, usually) are
responsible for ensuring that their delegates are around to receive
delegation messages. To do this, they may have to retain the delegate
in memory-managed code. This precaution applies equally to data
sources, notification observers, and targets of action messages. Note
that in a garbage-collection environment, the reference to the
delegate is strong because the retain-cycle problem does not apply.
From Concepts in Objective-C Programming

Related

Do system object delegates in ARC need to be set to nil?

An app crashes sometimes with error objc_object::release().
The Apple Developer Technical Support mentioned this:
Remember that you should always do something like _tableView.delegate
= nil; in your -dealloc methods, even if you are using ARC. For compatibility reasons system objects use unsafe_unretained
references to implement delegation, instead of the preferred modern
replacement weak.
Does that mean that I have to set the delegates of system objects to nil when the view controller is about to be released?
class MyViewController: UIViewController {
deinit {
tableView.delegate = nil
tableView.dataSource = nil
}
}
I always assumed UITableView and similar standard objects are using weak references to their delegates?
Update:
It seems that the example by the Technical Support was outdated as UITableView has already been updated to a weak delegate. However not all delegates have been updated, e.g. the AVAudioPlayer.delegate is still unowned(unsafe). It seems that Apple is gradually updating delegates to be weak.
So as to whether a delegate has been set to nil manually can simply be determined by inspecting the delegate declaration in Xcode. If it is weak, don't bother.
Yes you should set these delegates to nil.
As suggested by the name, unsafe_unretained references do not retain your view controller so there's no retain cycle or memory leak here. However, unlike weak, these references will not be set to nil automatically when your view controller is deallocated. In most cases this is not a problem as your view controller will outlive its views, or at least be deallocated at the same time.
Unfortunately there are a few cases where UIKit may have also temporarily retained the view. This can allow the view to outlive the view controller and attempt to call delegate methods on the deallocated object resulting in a crash.
The easiest way I know of to see this in action is to dismiss and deallocate a view controller which is a delegate of a scroll view (or one of its subclasses like UITableView) while the scroll is still scrolling (e.g. from a strong swipe gesture over a long list of items). The scroll view will then attempt to call delegate methods (like scrollViewDidScroll) on the deallocated controller.

Memory Management using viewDidUnload function

I am pretty new to IOS programming and I am working on my first app that I want to submit to app store.
That's why I made a mistake initializing all objects in viewDidLoad() function. I am using viewDidUnload() function to free the memory once the user exits from the view controller. Even though, x-code memory checking tool shows that the objects has not been deleted (the RAM usage does not decrease).
Does anyone know how to fix that or I will have to rewrite the whole code?
DeprecationAppendix
viewDidUnload
Called when the controller’s view is released from memory. (Deprecated in iOS 6.0. Views are no longer purged under low-memory conditions and so this method is never called.)
- (void)viewDidUnload
Discussion
In iOS 5 and earlier, when a low-memory condition occurred and the current view controller’s views were not needed, the system could opt to call this method after the view controller’s view had been released. This method was your chance to perform any final cleanup. If your view controller stored separate references to the view or its subviews, you could use this method to release those references. You could 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 would not use this method to release user data or any other information that cannot be easily recreated.
In iOS 6 and later, clearing references to views and other objects in your view controller is unnecessary.
At the time this method is called, the view property is nil.
Memory Management
Memory is a critical resource in iOS, and view controllers provide built-in support for reducing their memory footprint at critical times. The UIViewController class provides some automatic handling of low-memory conditions through its didReceiveMemoryWarning method, which releases unneeded memory.
Prior to iOS 6, when a low-memory warning occurred, the UIViewController class purged its views if it knew it could reload or recreate them again later. If this happens, it also calls the viewWillUnload and viewDidUnload methods to give your code a chance to relinquish ownership of any objects that are associated with your view hierarchy, including objects loaded from the nib file, objects created in your viewDidLoad method, and objects created lazily at runtime and added to the view hierarchy. On iOS 6, views are never purged and these methods are never called. If your view controller needs to perform specific tasks when memory is low, it should override the didReceiveMemoryWarning method.
viewDidUnload is deprecated function. (Link 1) It is hard to use and not called >= iOS 6.
The view controllers are automatically freed when they are not used. But if there is any reference to the view controllers, they will still reside in the memory. You should check all the strong reference to the view controller.
iOS programming has ARC now, so you dont need to worry about memory problem if you use ARC. ARC tutorial
viewDidUnload was deprecated in iOS 6. It's no longer being called. If you're using ARC, don't worry about memory management, as everything is taken care of for you. If you aren't using ARC, put your stuff in dealloc

Allocating UIViewControllers in viewDidLoad. Was this a mistake?

A colleague and I have been designing our code so that our view controller objects are being allocated within the viewDidLoad method of our controller objects. We did this a lot and the object graph seemed to come out just fine. After a while we found the need to add another layer above our current view controller hierarchy that would own, allocate, and release our previous parent view controller.
This got me thinking about low memory warnings and view did load. Before, our views were always visible and thus wouldn't be released if a low memory warning occurred, but now there is a chance of that happening. If it does, it seems like when the views are reloaded, they will allocated new memory for new view controllers.
Will this cause widespread memory leaks? Or will the fact that we used retained declared properties save us, since the old controllers will be released automatically? Are there styles or conventions I should consider? Thanks for the help!
If you do create your view controllers in viewDidLoad, you should release them in viewDidUnload. True for pretty much any object, not just view controllers.
But I would not be surprised if you say "Wut!?" If your view controllers need to keep their state through memory warnings, then you don't want to release them. But it will be no better if you replace them with new ones that have fresh state.
It probably doesn't make sense to create view controllers in viewDidLoad. Create them in initWithNibName:bundle: (and release them in dealloc). The design pattern is that view controllers stay around, their views can come and go. If your subordinate view controllers have anything memory-intensive, release just that object in their viewDidUnload. Then on a memory warning, you will still free up a lot of memory, but all your view controllers will stay around in a low-memory usage state, retaining only a few flags, lists of indexes, etc., that they will need to restore their views when requested.
If you are explicitly releasing each view controller that is not needed any more you will not cause any memory leaks. Usually when you present a view controller the object that is doing the presenting will retain the controller for you, so you can release it if you don't need access to it anymore. But if you are using a UINavigationController and you are pushing new view controller's then you should be implementing the viewDidUnload method. This is straight from Apple's documentation:
After it is loaded into memory, a view controller’s view remains in memory until a low-memory condition occurs or the view controller itself is deallocated. In the case of a low-memory condition, the default UIViewController behavior is to release the view object stored in the view property if that view is not currently being used. However, if your custom view controller class stores outlets or pointers to any views in the view hierarchy, you must also release those references when the top-level view object is released. Failure to do so prevents those objects from being removed from memory right away and could potentially cause memory leaks later if you subsequently overwrite any pointers to them.
There are two places where your view controller should always clean up any references to view objects:
The dealloc method
The viewDidUnload method
If you use a declared property to store a reference to your view, and that property uses retain semantics, assigning a nil value to it is enough to release the view. Properties are by far the preferred way to manage your view objects because of their convenience. If you do not use properties, you must send a release message to any view that you explicitly retained before setting the corresponding pointer value to nil

In iPad DetailView situation, viewDidUnload not being called

I set up a detail view, do I have several Nib files that get loaded depending on what item is selected in the root view controller's table.
What I've discovered is that for the Nibs' classes, viewDidUnload is never called, whereas viewWillDisappear is called and dealloc of course is also called.
Anybody know why this would be?
Thanks.
I believe viewDidUnload isn't normally called when a view disappears from view. The reason for this is because dealloc will typically take care of all the memory dumping, so it doesn't need to call viewDidUnload first.
I think an example would help identify when viewDidUnload is called. Let's say you have a UINavigationController and you've pushed on a new view. This new view is very heavy on memory usage, so the app tries to shore up some resources. It does this by seeing if any Views are loaded that aren't currently on the screen. If so, it calls viewDidUnload where ideally you remove things that you built in loadView or viewDidLoad. Then when you go back to that view, it calls loadView or viewDidLoad again to re-build what it dumped off in viewDidUnload.
But if it doesn't need to free up memory to show your detail view, it won't call it in the normal processing of it. That's why viewWillDisappear is called (and dealloc) but never viewDidUnload.
From Apple's documentation:
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.
UIViewController Class Reference

What's the best and safest practice for releasing outlets?

The iOS documentation says that your view controllers should release (and nil, since that's good practice and actually a must for 2.x compatibility) any retained outlets in -dealloc.
The documentation also says that you should do the same for your outlets in -viewDidUnload if you want to allow them being cleaned up when the view is not visible and a memory warning was issued.
I wonder about two things:
Does this mean that I should duplicate all my outlet releasing code? Put it all in -dealloc and in -viewDidUnload? That's a real pain to maintain and really easy to forget!
Am I guaranteed that all my outlets will be re-populated when the view is reloaded after a -viewDidUnload, including my outlets that reference non-view objects in my nib?
The ideal answer answers both questions above and if there is a distinction between which properties should optimally be released in -dealloc VS -viewDidUnload, clarifies this distinction in detail.
-viewDidUnload is strictly used for releasing IBOutlets with retain properties.
The reason for this has to do with the fact that UIViewController has a view property which it retains. That view property itself retains references to all of its subviews. These subviews are exactly what you are retaining inside these outlet properties. The problem lies in that these subviews have an "extra" retain on them.
The goal of -viewDidUnload is to clear up unnecessary memory usage. When -viewDidUnload is called, the view property has already been released, which releases the top level UIView along with all its subviews. Since we have retained some of these subviews however, they linger in memory, and we want to release them since they will no longer be used. New copies of these subviews will be created when (if) the view is reloaded. The properties are also set to nil, strictly so we don't have pointers pointing to deallocated memory.
In -dealloc all retained properties and instance variables should be released. In the case where the -viewDidUnload just executed, you will be sending a harmless [nil release]; to the IBOutlet retained properties you just set to nil.
To add to the excellent answers:
With the Apple LLVM 3.0 compiler (and ARC enabled), the need for -dealloc disappears (or at least, the need to release your outlets in it), leaving just -viewDidUnload to deal with. As a result, no more duplicate code.
Nick answered the part 1 of the question perfectly so I skip to part 2 right away.
You are guaranteed, indeed! When your view gets reloaded from a nib file, everything on that nib file gets reloaded. There is not method to partially load a nib file's contents so if that nib gets reloaded, everything inside will get reloaded, too. But everything loaded from a nib has an autorelease on it. So you have to retain them. If you have Outlets which retains these freshly loaded classes and you connected those Outlets as outlets on nib, you are good to go. On a view controller example controller itself is the file's owner of that nib, view is the root element on nib, view being retained by file's owner outlet "view", all other child elements retained by view itself. If you have multiple root elements or elements that root view does not retain then you have to be sure there is outlets for this elements to retain them if nib loading by system automatically, if you are loading nib by a method then you must be aware all those classes are autoreleased when they arrived to your code.
Simply views are classes too and when a nib file gets loaded, everything inside it gets loaded, not some part of it. It is your responsibility to manage outlets.

Resources