Surprisingly, I have not seen anyone talking about what I just discovered in the iOS 9.0 API Diffs. (Well, actually what Max von Webel discovered.) There’s a hidden gem in the UIKit diffs. We no longer have to suffer through tracking down obscure bugs due to non-zeroing weak references.
Non-zeroing weak references
What are non-zeroing weak references? Mike Ash’s Friday Q&A on Automatic Reference Counting covers this and everything else you ever wanted to know about ARC. You should probably read this now, even if you have before. It’s great.
Briefly, there are
strong references and
weak references in ARC. These describe the ownership behavior of a pointer. Either an object owns the pointed-to object (
strong, increments the retain count), or it does not own the pointed-to object (
weak, the retain count is unchanged). Then,
weak references can be zeroing or non-zeroing, meaning that when the pointed-to object is deallocated, the pointer is zeroed out or not.
The issue is that a non-zeroing reference ends up pointing to invalid memory (because the reference is not zeroed out), thus when you attempt to access that memory (by sending the object a message) — you’ll crash. Mike puts it nicely in his post:
You must ensure that you never use such a pointer (preferably by zeroing it out manually) after the object it points to has been destroyed. Be careful, as non-zeroing weak references are playing with fire.
Playing with fire
Why do these details about weak references matter? Because up until iOS 8, UIKit views that have a
dataSource property have been declared as the following.
Note: this extends well beyond UITableView and UICollectionView. UIBarButtonItem.target is assign. UIGestureRecognizer.delegate is assign. UIActionSheet.delegate is assign. UIAccelerometer.delegate is assign. The list goes on, everywhere in UIKit.
These references are
assign (non-zeroing). That is, not
weak (zeroing). Have you ever had a bug because of this? I have. What happens is the
delegate object is deallocated before the view, and then the view attempts to send a message to an object that has been destroyed, and you crash. And then you confusingly look through obscure stack traces and repeatedly fail to reproduce the issue. You lie awake at night, unable to sleep.
For correctness, you should be setting these to
dealloc. In the comments of Mike’s post, he explains further:
You’re correct that __unsafe_unretained is the same as a regular assignment done the old way, but most Cocoa programmers don’t exercise the caution they should when using those. How much code have you seen that manually zeroes out delegate and data source pointers in a window controller’s -dealloc. It’s almost unheard of. And yet, it’s absolutely required for correctness, because you can’t know the order of object destruction, and the view might message its data source or delegate after the controller has been destroyed. In practice, this happens rarely, but it does happen, and is really annoying to track down.
Deliver us from evil
As of iOS 9, the aforementioned APIs have changed. In fact, all of UIKit looks like it has been audited. Everywhere I looked,
assign was replaced with
I can’t say for certain, but my guess is that we can thank Swift for this. Swift was the impetus for nullability annotations and generics in Objective-C, and we know the teams at Apple have been working hard to update all of the Cocoa frameworks with these features. Maybe
assign affects interoperability with Swift? In any case, non-zeroing weak references are finally gone. As developers, we can finally get some sleep.