Search code examples
iosswiftdelegatesautomatic-ref-countingnszombies

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.


Solution

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