Optionals and force unwrapping

Some of my rules of thumb for approaching optionals, force unwrapping, guard and if statements.

A screenshot of Xcode showing an if/let/else statement
A screenshot of Xcode showing an if/let/else statement

This little guide shows some of my rules of thumb for approaching optionals, force unwrapping, guard and if statements.

When/why

So we know that optionals are great, for where it’s not guaranteed that we will have a value.They’re also great for indicating when something has never been set or loaded, e.g:

var user: User?

However, sometimes we end up in situations where we need a variable to be there, in order to do some work. The temptation is to force unwrap at the point of use, especially if you think a variable is going to be there anyway:

self.textLabel.text = self.user!.name

Every time we introduce a force unwrap, we can open ourselves up to a fatal error if the data isn’t actually there. So lets avoid this scenario...

Guard + let

Think of a guard statement as a bouncer on a door. We should use guards as early exits. They should appear at the start of a function.

func someFunction(p1: String?, p2: String?) {
    guard let p1 = p1 else { return }

Where cases are combined, these should be split these over several lines:

func someFunction(p1: String?, p2: String?) {
    guard
        let p1 = p1,
        let p2 = p2
      else {
          return
      }


This enables us to set break points on the individual guarded criteria.

⚠️We should not use guard statements for conditional blocks of code, e.g:

func someFunction(p1: String?, p2: String?) {
    guard
        let p1 = p1,
        let p2 = p1
    else {
        let alert = Alertbuilder.someAlert(“Fail”)
        self.present(alert, animated: true, completion: nil)
        return
    }

In this scenario we should consider returning a failure value or state, and handle the presentation of an alert at the call site to the function, or moving our logic into an if/let block instead, so the flow is clear.

This keeps our rule simple: guards are essentially a list for what a function needs to do it’s work, and are not a place for flow or UI.


If/let

Use these for conditional flow when you need to do one thing or another depending on optional values being present.


For if statements with multiple AND criteria, separate these out on separate lines, so we can break-point the individual criteria.

So for example:

func someFunction(p1: String?, p2: String?) {
    if
      let p1 = p1, 
      let p2 = p2
    {
       let someVC = SomeViewController()
       someVC.p1 = p1
       someVC.p2 = p2
       self.present(someVC, animated: true, completion: nil)       
    } else {
        let alert = Alertbuilder.someAlert(“Fail”)
        self.present(alert, animated: true, completion: nil)
    }
}

Try and avoid large > 10 lines blocks of code for conditions, if so, think about breaking your function out into separate functions.

The exceptions to the rule (UITableView / UICollectionView, cellForRow(at … )

When we implement UITableView or UICollectionView’s cellForRow(at function, we can use a force unwrap when dequeuing a cell.

For example:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell") as! CustomTableViewCell {
        return cell
}

In this case, the risk of a force unwrap is weighed against the elegance of the code.
You can if/let, or you can guard, but you still have to return a default cell. You can send back UITableViewCell() but if you're never actually going to use it, that’s really not great.

Think about it further. Why might we hit nil when dequeuing our custom cell?

This can only happen because we’ve not registered it for reuse with the UITableView. If this happens, then we’d have bigger problems than the risk of the force unwrap.

In practice, UI tests can provide a check that the app is not crashing when the cells are displayed.

TLDR

  • Guards should be used as early exits, and appear only at the start of functions.
  • Guards should not contain logic, they should just be used for early exiting, or returning values.
  • Guards with multiple AND criteria, should split each criteria over separate lines.
  • Blocks of code inside if/let blocks should not be longer than 10 lines. If they are, consider creating a new function for the logic block inside of them.
  • Force unwrapping in cellForRowAtIndex  is an acceptable risk vs. awkward syntax, where we have UI Tests as well.

Final note

In practice, your mileage may vary, of course. On any sizable project I think it's worth laying down some ground rules, and articulating why you have them, even if they're different to these. Understand your constraints, or preferences, and work accordingly!