expectNotNil: Handling Swift optionals that should never be nil

Fail gracefully (i.e., don't crash), but let me know something went wrong so I can fix it in my next release.

expectNotNil: Handling Swift optionals that should never be nil

Here's a fairly common situation in the Nihongo codebase:

self.navigationController?.setNavigationBarHidden(true)

I know from the logic of the app that this view controller is always going to be embedded in a UINavigationController. But the compiler doesn't. So I have to deal with the fact that navigationController is an optional, even though I don't expect it to ever be nil.

I encounter lots of these "never-nil" optionals. For example UIColor(named:) and UIImage(named:) return optionals, even when I know the associated assets exist. viewController.storyboard is an optional, even when I know my view controller is defined in a storyboard. view.superview is an optional, even when the logic of my app dictates that a particular view always has a superview.

Swift does give you a way to express your certainty that an optional is non-nil. Instead of the ? operator, I could use !. But it's a very blunt instrument. And the reality is, I can never be 100% sure I haven't made a logical error, and my optional actually is nil. What if I rename a color, and forget to update a call to UIColor(named:), for example?

What I really want is for the app to fail gracefully (i.e., not crash), but let me know something went wrong so I can fix it in my next release. That's why I started using something I've been calling expectNotNil:

expectNotNil(self.navigationController)?.setNavigationBarHidden(true)

expectNotNil takes an optional, and returns that optional back without modifying it. But if that optional is in fact nil, it also logs an error to Crashlytics. It uses the filename as the domain, and line number as the error code.

expectNotNil error in Crashlytics

You could accomplish the same thing by explicitly handling the nil case using an if let or guard let,  but I've found this causes an explosion of boilerplate error handling. I've been using expectNotNil all over Nihongo for about a year, and have found it to be a great alternative.

func expectNotNil<T>(_ optionalObject: Optional<T>, file: String = #file, line: UInt = #line) -> Optional<T> {
    if optionalObject == nil {
        let filename = URL(fileURLWithPath: file).lastPathComponent
        let error = NSError(domain: "ExpectedNotNil-\(filename)", code: Int(line), userInfo: nil)
        Crashlytics.sharedInstance().recordError(error)
    }
        
    return optionalObject
}

There are some downsides that make it not a good fit for optionals you truly expect could be nil. For example, if the line number changes across releases, it will show up as a new error. But as a lightweight alternative to ! and ?, I think it's a good tool to have in your tool chest.