Implicitly Unwrapped Optionals and IBOutlets

Today I got burned by Xcode. It’s happened before and I’m sure it will happen again. I really do appreciate the help that you give me Xcode, but why are you such an enabler?

Implicitly Unwrapped Optionals as an IBOutlet goes BOOM The long story short: a tester noticed a crash in my app that ended up being due to the fact that Xcode’s Assistant Editor

helped me to create an implicitly unwrapped optional IBOutlet that ended up being nil in tester’s flow. When you try to access a nil implicitly unwrapped optional, your app goes BOOM (aka crash).

What went wrong My incorrect assumption was that the

IBOutlet would never be nil. Turns out that isn’t a safe assumption. In my case, it was a slightly complicated UITableView where one cell was attempting an operation on another cell that was off the screen. While the UITableView was able to find the destination cell in question, that cell’s IBOutlets weren’t alive at that moment, aka they were nil. So why I tried to access them directly, without concern for their ability to be nil, an error results. Where Xcode steered me wrong, was in how it automatically created the IBOutlet for me. Take a look at this: implicitly unwrapped optionals When the Assistant Editor creates the corresponding variable, it’s defined as an implicitly unwrapped optional. In my opinion this is bad, and the root cause of this error. I would never think to write an automated test to verify flows in the code around those being nil – well I will going forward. And in my opinion, totally enables me the developer to go on thinking that I never need to worry about IBOutlets being nil. Since Apple built Xcode to do this, it’s like the a complicit in my crime of not worrying about whether my IBOutlet was nil or not.

The Fix

It’s a pretty easy fix, but one that’s easy to overlook. First, take a look at this risky code:

class SampleCell: UITableViewCell {

  @IBOutlet weak var button: UIButton!

  func styleCellWith(color: UIColor) {
    button.setTitleColor(color, forState: .Normal)
  }
}

It’s well within the rules of the Swift compiler to write that code. And in fact, Xcode even points you in the direction of writing this code. The bad part is that since

button is an implicitly unwrapped optional, that means any reference to it with a ? mark means that if it’s nil, your app will crash. If you don’t believe me, try setting it to nil right before the title color is set, and try to run an app. There’s a couple safer ways to do this. Any of these work:

@IBOutlet weak var button: UIButton?

Defining

button in this way will force you to explicitly treat the optional everywhere you use it. While it’s more verbose, I kind of like this technique because I think this is the essence of where Swift excels – it forces you to be a little more defensive or explicit with handling “anything that could go wrong.”

Writing a test for it My big gripe with this is that Xcode makes it so easy to write the faulty code based on how the Assistant Editor guides you to create the

IBOutlet in code. If I’m following my normal Test Driven Development flow, I’d never think to write a test first to verify that an IBOutlet was not nil. But of course, now that someone actually found the bug in my code, I need to add a test to ensure that this specific case doesn’t actually happen again. Luckily, it’s pretty easy:

class SampleCellTests: XCTestCase {

  func testStyleCellWith_DoesNotCrash_WhenButtonIsNil() {
    let toTest = SampleCell()
    toTest.styleCellWith(UIColor.blueColor())
    // No need to do an assert here , as long as the test doesn't crash, it will be marked passed
  }

}

Just setup the object with the

IBOutlet, call the method under test. No need for an assertion, as long as the test doesn’t crash, we can consider that a pass. Here’s a pseudo screencast of my test driven development flow for fixing this bug and adding a test: implicitly unwrapped optionals

Beat Xcode to Submission! Well not really, in fact, you should totally try to work

with Xcode. It’s important to be aware of it’s shortcomings though, and I totally got burned today! Luckily I had an awesome tester discover that there are cases when you can’t totally trust an IBOutlet to exist, despite connecting it right from the storyboard. In case you want to try out the sample code, I posted it on Github. Check it out here. You can read more about implicitly unwrapped optionals in Apple’s documentation here.