To Optional or Not to Optional: IBOutlet
Optionals are a new concept to the Cococa world. Where-as Objective-C allowed for willy-nilly nil messaging, Swift wants you to be explicit and know when the possibility of nil exists. I'm going to assume you've played around with Swift enough to grasp the idea of optionals vs implicitly unwrapped optionals. What I want to talk about today is their use as IBOutlets.
When you declare an outlet in Swift, you should make the type of the outlet an implicitly unwrapped optional. This way, you can let the storyboard connect the outlets at runtime, after initialization. When your class is initialized from a storyboard or xib file, you can assume that the outlet has been connected. - Using Swift with Cocoa and Objective-C, Apple
Apple repeats this in the Connect the UI to Code Start Developing iOS Apps (Swift) lessons.
What Apple is recommending would read as:
@IBOutlet weak var emailField: UITextField!
As opposed to:
@IBOutlet weak var emailField: UITextField?
If you've played with Swift you'll know the subtle difference that'll make: if you have to test for nil before using the variable or not:
//using ! - an implicitly unwrapped optional
emailField.placeholder = "Your email"
//vs ? - a standard optional, gotta test for nil first
emailField?.placeholder = "Your email"
One thing I've come to learn in programming Swift full-time since v1.1: it saves you some if-let checks and unwrapping, but that !
is dangerous. I mean, they used that character for a reason. Your app will crash if it ends up being nil when you try to read from it.
This has proven true with IBOutlets, too. So now I'm calling BS on using implicitly unwrapped optionals for IBOutlets as a best-practice. It's bit me too many times.
The idea behind the recommendation is fair enough: writing your IBOutlets as implicitly unwrapped optionals may save you dozens of ?
checks in a view controller. Why should you check anyway? Whenever your view controller is loaded, the view will be around, and therefore the outlets will be connected. Right?
Except in practice there are tons of edge cases in the lifecycle of a view controller where this simply isn't true. And what happens when you try to access emailField when the view isn't loaded for some reason? The app crashes.
I've had this happen in viewWillAppear
. Freaking viewWillAppear
! I've seen this happen in a dozen other unique situations in my indie app alone over the last 6 months. The count of users affected is always reasonably low as a percentage of the total user base (<1%), but I don't ever want to see configuring a view crash my app.
It kinda makes sense that we might have situations like this sometimes. Why else would Apple have introduced the new viewIfLoaded
property on view controllers in iOS 9? If the view isn't loaded the IBOutlet will be nil, too, but that loading/unloading isn't always perfectly in sync with our other view controller logic. It is an optional.
(In some places as a quick triage I've added a guard let _ = viewIfLoaded else { return }
check before a ton of view-related code, but that's not gonna scale as well as a plain old optional.)
UIKit was written during the era of nil messaging, and I've come to realize it isn't safe to 100% assume IBOutlets can't be nil. Going forward I'll be using optionals for my IBOutlets. I have a task in my bug tracker to scrub all my IBOutlets to covert them from implicitly unwrapped to standard optionals. A few extra question marks never hurt anyone; I'd rather my app not crash.