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

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.

Related

Does a UIView not require its UIViewController?

Playing around in Instruments, I noticed something I don't understand. I create a couple of UIViewControllers and add their views to another UIView:
CustomVC *vc = [[CustomVC alloc] initWithCustomInitializer:someParameter];
[mainView addSubview:vc.view];
By logging the memory address of the CustomVC in its init and dealloc, I see that vc is deallocated almost immediately, though the view remains on screen and everything works fine.
Does the UIView not necessarily need its controller? Or is something else going on that I'm misinterpreting?
A UIView doesn't/shouldn't retain its parent (the UIViewController), so if you drop your reference to the UIViewController and only keep the reference to the UIView, nothing keeps a strong reference to it and it will be released.
The weak reference in the UIView to its controller will be automatically set to nil.
Whether the UIView needs its "lost" controller to do its work is another story though, and entirely depending on the UIView.
UIView does not need ViewController by any means. What is happening is that you are probably not retaining pointer to VC. Pointer to view is retained by mainView.
Furthermore regardless wether you are using ARC or not. If you want to keep object in memory you need to have a strong(ARC) or retain property to it. Not to confuse you. It doesn't necessarily need to be your custom subclass. Lets say you are using UINavigationController. You can create a controller in app delegate for instance and push it to UINavigation controller. You dont need to keep a pointer to it as long as something does. This is very fundamental. You need to be clear on memory management I dont think I will be able to explain it here adequately. I would highly recommend you to check Stanford course on iOS development.
http://itun.es/ru/_zEGD
There should be only one UIViewController at a time. Adding a view that belongs to a viewcontroller is bad practice.
To answer your question, because of the above mentioned constraint (or design decision by Apple), the view is probably now retained by the other view controller, and released from vc.

viewDidUnload sent to deallocated instance after memory warning

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

Should IBOutletCollections be set to nil in UIViewController viewDidUnload?

My UIViewController class has an IBOutletCollection containing several IBOutlet objects. I am aware of the need to set retained outlets to nil in viewDidUnload, but do I also need to set the IBOutletCollection to nil? Or should it be released instead in dealloc? Or left alone altogether?
You need to set your outlets to nil in viewDidUnload in order to make sure that as much memory as possible can be released by the view controller when it receives a memory warning. In response to the memory warning, the view controller releases its view in order to free the memory the view (and all its subviews) is using. If you fail to release those outlets that you are retaining/holding a strong reference to, the subviews referenced by those outlets will not be destroyed and their memory won't be freed.
So yes, you should also set the property of an outlet collection to nil in viewDidUnload.
This requirement is independent of the responsibility to release all your retained ivars/properties in dealloc.

Memory management in a custom UIViewController

I'm new with iOS development, but I'm familiar with the memory management basics in Obj-C. However, Apple's docs about custom UIViewControllers confused me a bit.
So, I have a PagingController that stores an array of EntryViewController objects. (They're all UIViewControllers.) These objects are loaded dynamically and, of course, released when appropriate. The view in PagingController is created in -loadView and stored in self.view, just as the documentation said.
Whenever I load an EntryViewController (as ctrl), I call [self.view addSubview:ctrl.view];. I also need to call ctrl.parent = self; since the target OS is 3.0 and I don't want to modify a private variable (_parentViewController). (parent is defined as #property(assign) PagingController *parent;)
Here are my questions:
Do I have to release self.view myself, or is UIViewController taking care of that?
Do I have to retain parent (the property in EntryViewController)? I assume it's pointless, since all the child view controllers are being released when the parent is unloaded.
Do I have to call [self.view removeFromSuperview] in the -dealloc method of EntryViewController? The docs said, I have to manage all of the subviews, so I'm not sure if this gets called automatically.
Or am I just confused about how to create a custom view controller in the first place?
UIViewController will take care of that
You shouldn't retain parent, set the property to assign, otherwise you're gonna have a retain cycle
The view controller will take care its view, you only need to release the other subviews you are retaining (removeFromSuperview isn't necessary)

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

Resources