3

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.

Manuel
  • 14,274
  • 6
  • 57
  • 130
  • I don't think that you need to set the dataSource to nil in the deinit method though. –  Sep 16 '17 at 02:33
  • @Alex I think you do, because the property `dataSource` is just another delegate with a different name. E.g. it implements the delegate function `cellForItemAt` for `UICollectionView`. – Manuel Sep 16 '17 at 16:58

1 Answers1

3

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.

Jonah
  • 17,918
  • 1
  • 43
  • 70
  • 1
    And should I do it in the `deinit` method or where is the best place? – Manuel Sep 16 '17 at 02:21
  • 1
    `deinit` is a safe and reasonable place for this since it will be called reliably. – Jonah Sep 16 '17 at 02:38
  • Instead of setting the delegate to nil, can I set create a weak reference to `self` with `weak var weakSelf = self` and set it as delegate to achieve the same effect? Would be less code. – Manuel Sep 16 '17 at 03:09
  • I don't believe so; your reference to `self` would be weak but the delegate properties would still have an `unsafe_unretained` reference to `self`, not to your weak reference to `self`. – Jonah Sep 16 '17 at 03:11
  • 1
    Even if I set `tableView.delegate = weakSelf` it would be an `unsafe_unretained` reference? – Manuel Sep 16 '17 at 03:13
  • I believe that if you set the delegate to weakSelf it might work @Manuel, I haven't tried it yet so I guess we should wait for others that might know the answer, but it sounds interesting :) –  Sep 16 '17 at 04:21
  • 2
    This is becoming an interesting new question but here we go. https://www.mikeash.com/pyblog/friday-qa-2015-12-11-swift-weak-references.html has a great tour of how weak references work. `weak var weakSelf = self; v.delegate = weakSelf` is no different than `v.delegate = self; weak var weakSelf = self`. In both cases `v.delegate` just stores a pointer to your `self` object. The zeroing (or not) behavior of that reference is determined by the object holding the reference, not the pointer you passed to it or the zeroing behavior of the reference you got that pointer from. – Jonah Sep 16 '17 at 05:37
  • 1
    this is quite opinionated but I only set delegates to nil if I need it. I dont like to to it as a general rule :P – Daij-Djan Sep 16 '17 at 06:00
  • @Jonah Got it, the `weak` keyword defines the reference of the variable's class instance to another instance. That's why `self` and `weakSelf` are the same pointer. The reference of `tableView.delegate` would still be `unsafe_unretained`. If anyone wants to read up on this: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html – Manuel Sep 16 '17 at 14:28