Swift optionals, when used correctly, are a very powerful tool. When used incorrectly, they lead to one of the most common Swift crashes on Stack Overflow. When it comes to the correct usage of optionals, implicitly unwrapped optionals always get it wrong. There are two circumstances in Swift in which we deal with implicitly unwrapped optionals: @IBOutlets
and interoperating with Objective-C code, which has not properly added nullability annotations. Outside of these two exceptions, we should avoid implicitly unwrapped optionals.
Despite the inherent danger of implicitly unwrapped optionals, it has come to our attention that Natasha the Robot is actually recommending them! This article serves to highlight the problems of implicitly unwrapped optionals and the danger of the pattern recommended in the aforementioned article.
To be clear, for my own code, I prefer using nibs and custom initializers, which allows me to completely avoid the problem Natasha is trying to resolve. I also recommend this solution to other developers here at Metova. Don’t get me wrong, storyboards are nice, and before Swift, I definitely preferred them. However, using storyboards with Swift cuts out many of the safeties the Swift compiler gives us. I want to also mention here that one of the disadvantages of using storyboards rather than nibs with custom initializers is that we have eliminated the possibility of using let
constants with values set in initialization in our view controllers. We can’t guarantee that once set, our properties will not change without additional trickery in the set method.
If we’ve decided to use storyboards, the properties of our view controller must all be var
. The question remaining to be answered is how to treat these values between actual initialization of the view controller and our first opportunity to set these properties in prepareForSegue
.
We have three choices.
1. Non-optional
2. Optional
3. Implicitly unwrapped optional
Each of these choices have their own advantages & disadvantages. When we weigh all the choices, implicitly unwrapped optionals seem to be the worst choice, no matter what sort of pattern we write around them. Let’s look at these choices in detail.
“When we weigh all the choices, using implicitly unwrapped optionals is the worst.” Click to Tweet
Non-Optionals
Non-optionals are probably the safest choice. There’s no risk of finding nil when accessing non-optional properties because they can’t store nil. The problem with non-optionals is there isn’t a very good way of indicating that the value hasn’t been initialized yet–you have to provide a default sentinel value.
Optionals
Optionals do away with the major disadvantage of non-optionals’ inability to indicate they are as of yet uninitialized by the fact that they can be nil
. The major problem with optionals is they can add a lot of verbosity throughout your code with all of the unwrapping you have to do to access them safely. The only way to get an unwrapping crash with optionals is by force unwrapping, and that can easily be identified with a tool like SwiftLint.
Implicitly Unwrapped Optionals
Like optionals, implicitly unwrapped optionals have a good way of identifying that they’ve not yet been initialized by simply being nil
. They also have an advantage over optionals in that they don’t require that verbose unwrapping code just to access the value. The problem is, this isn’t really an advantage. If we declared these properties as optionals, we wouldn’t be okay with force-unwrapping them all over the place, would we? And yet, that’s exactly what an implicitly unwrapped optional is.
We need to be clear about what’s going on with implicitly unwrapped optionals. It’s syntactic sugar for force-unwrapping everywhere, because at the end of the day, the variable is still just an optional.
Despite being declared as implicitly unwrapped optionals, all of the unwrapping syntax for full optionals still works with implicitly unwrapped optionals:
var implicitOptional: Int!let zero = implicitOptional ?? 0if let value = implicitOptional { // Safely use value with type Int}guard let value = implicitOptional else { // implicitOptional was nil return}// Safely use value with type Int
And unlike non-optionals, we can assign nil
and optional results into implicitly unwrapped optionals without complaint from the compiler:
func functionReturningOptional() -> Int? { return nil}var implicitOptional: Int!implicitOptional = functionReturningOptional()implicitOptional = nil
If we were using a non-optional, this stuff wouldn’t compile. But with an implicitly unwrapped optional, the compiler is perfectly happy. It treats implicitly unwrapped optionals no differently from regular optionals.
And here’s where the real problem comes. When I’m trying to assign into a non-optional, the compiler should throw a fit if I’m trying to give it nil
or an optional value.
But if value
here is instead declared as type Int!,
the compiler throws no warnings or errors, even though the rest of our code is written assuming that value
will never be nil
.
And because we’re using storyboards, which prevents us from declaring our properties as let
constants, we don’t have full control over what will happen to these properties. Even if we mark the property as private
, we can’t control what other developers on the project will do today, tomorrow, or years down the road when this is considered legacy code.
The best we can hope for is to write our code in a way that gives any future maintainer the best possible chance of using the code safely and effectively. Using implicitly unwrapped optionals eliminates the possibility of getting significant help from the compiler.