Is it necessary to set delegates to nil in deinit?

In the Objective-C era, it was recommended to set the delegates of members to nil in dealloc. The equivalent in Swift would be:

func loadView() {
  // ...
  self.scrollView.delegate = self
}

deinit {
  self.scrollView.delegate = nil
}

The idea here is that scrollView might live on after this object has been destroyed, and someone might try to invoke a method on its delegate, even though the delegate is now invalid.

Is this still necessary in modern Swift? A weak var in Swift should take care of all this, but is it possible to tell if there are any classes in older, Objective-C frameworks where this would still be necessary?

This is only necessary if the delegate isn't properly a weak reference, just like Obj-C. Virtually all of them are nowadays, and linters usually enforce such as a default rule, but there are a few strong delegates out there (like URLSession's) which are usually called out in documentation with a suggestion on how to handle breaking the cycle. In your example, UIScrollView's delegate is declared as weak so there's no need to break the cycle manually (nor is there in Obj-C).

1 Like

deinit is the wrong place to break a reference cycle though, because it will never get called if the object in question is still referenced.

Zeroing out pointers prior to deallocation was an idiom found in older memory-unsafe languages, because it gives you more deterministic behavior in the event of a use-after-free.

This is not relevant for a memory-safe language like Swift. In fact an optimizing compiler could as well just delete any writes into an object that is about to be deallocated, because there is no way to observe those writes as having taken place.

6 Likes

Sure, I skipped over that part. And @moreindirection, this applies to Obj-C as well, as the references would keep it alive, unless you were back in manual retain / release days and just weren't retaining your delegates.

Really you'd need a method to call to break the cycle, like URLSession's invalidateAndCancel() or finishTasksAndInvalidate(). Given how unreliable that can be (users, you know), such patterns are essentially unheard of in languages with auto-niling weak references.

CKSyncEngine doesn't even have a setter for the delegate! We don't even know if it is stored as a weak. It is supplied in the configuration struct the init takes.