Articles, podcasts and news about Swift development, by John Sundell.

Extending optionals in Swift

Published on 18 Nov 2018
Basics article available: Optionals

One thing that most modern programming languages have in common is that they provide some form of language-level support for expressing optional values. Instead of risking crashes or other runtime failures when a value turns out to be missing, languages like Swift let us leverage the compiler to verify that the right checks have been made, and that we have correctly unwrapped any value that was defined as optional.

A really elegant aspect of Swift's implementation of optionals, is that a large part of this feature is implemented using the type system - since all optional values are actually represented using the enum Optional<Wrapped> under the hood. That gives us some interesting capabilities, since we can extend that enum - just like any other type - to add our own convenience APIs and other kinds of functionality.

This week, let's take a look at how to do just that, and how doing so can let us deal with certain optional values in a really nice way.

Converting nil into errors

When working with optional values, it's very common to want to convert a nil value into a proper Swift error, that can then be propagated and displayed to the user. For example, here we're preparing an image to be uploaded to our server through a series of operations. Since each operation might return nil, we're unwrapping the result of each step, throwing an error if nil was encountered - like this:

func prepareImageForUpload(_ image: UIImage) throws -> UIImage {
    guard let watermarked = watermark(image) else {
        throw Error.preparationFailed
    }

    guard let encrypted = encrypt(watermarked) else {
        throw Error.preparationFailed
    }

    return encrypted
}

The above code works, but let's see if we can use the power of extensions to make it a bit more concise. First, let's create an extension on the Optional enum type that lets us either return its wrapped value or throw an error in case it contained nil, like this:

extension Optional {
    func orThrow(
        _ errorExpression: @autoclosure () -> Error
    ) throws -> Wrapped {
        guard let value = self else {
            throw errorExpression()
        }

        return value
    }
}

Above we use @autoclosure so that we only have to evaluate the error expression if needed, as to not do any unnecessary work - without requiring the caller of our new function to use any additional syntax.

Using the above, along with a bit of optional chaining using flatMap, we can now construct a very nice "chain of operations" that lets us deal with any errors resulting from nil values at the end of the chain, and easily convert any such missing value into a proper error - like this:

func prepareImageForUpload(_ image: UIImage) throws -> UIImage {
    return try watermark(image)
        .flatMap(encrypt)
        .orThrow(Error.preparationFailed)
}

The above flatMap call works thanks to Swift's first class function capabilities. For more on that, check out "First class functions in Swift".

Doing something like the above might seem like a purely cosmetic change, but it enables us to increase the predictability of our code by modelling it as a sequence of operations - rather than having to keep track of multiple local variables. Of course it doesn't hurt that it looks nice as well 😉.

Expressive checks

Another common scenario when dealing with optionals is wanting to perform some kind of check on the unwrapped value. For example, when implementing a form UI, we might want to change the border color of each text field whenever its text changed - depending on whether that string is currently empty:

extension FormViewController {
    @objc func textFieldDidChange(_ textField: UITextField) {
        // Since the text field's text property is an optional
        // we need to provide a default value here
        if textField.text?.isEmpty ?? true {
            textField.layer.borderColor = UIColor.red.cgColor
        } else {
            textField.layer.borderColor = UIColor.green.cgColor
        }
    }
}

Using a default value when an optional contains nil, like we do above, is often a nice solution when used in a clear context - but it can also result in code that becomes harder to read due to the extra syntax required. Since checking whether a string (or any other kind of collection) is empty is quite a common operation - let's again see if we can improve the readability of our code by using an extension on Optional.

This time we'll add a computed property called isNilOrEmpty to all optionals that contain a type conforming to Collection, in which we essentially perform the same check as we did above:

extension Optional where Wrapped: Collection {
    var isNilOrEmpty: Bool {
        return self?.isEmpty ?? true
    }
}

If we now update our text field handling code from before to use our new property, we end up with a control flow that is a bit nicer and easier to read:

extension FormViewController {
    @objc func textFieldDidChange(_ textField: UITextField) {
        if textField.text.isNilOrEmpty {
            textField.layer.borderColor = UIColor.red.cgColor
        } else {
            textField.layer.borderColor = UIColor.green.cgColor
        }
    }
}

The beauty of using extensions on Optional to implement convenience APIs is that we can easily reuse those same APIs in many different contexts. For example, we might want to check if an optional set of completed tutorial steps is nil or empty to determine what initial screen to show to the user, or to use the same API to check if the user has added any friends in our app:

let showInitialTutorial = completedTutorialSteps.isNilOrEmpty
let hasAddedFriends = !user.friends.isNilOrEmpty

Using extensions to wrap certain common expressions can also be a great way to make our code more self-documenting, since the intent of those expressions becomes crystal clear. For more tips and techniques for more self-documenting code, check out "Writing self-documenting Swift code".

Matching against a predicate

Next, let's take a look at how we can add matching capabilities to optionals. Just like how we previously checked whether collections were empty, it's also common to want to match optional values against a more custom expression while the optional is being unwrapped.

For example, here we're unwrapping a search bar's text, and then verifying that it contains at least 3 characters before performing a search:

guard let query = searchBar.text, query.length > 2 else {
    return
}

performSearch(with: query)

Again, the above code works, but let's see if we can make it a bit more elegant by enabling any optional to be matched against a given predicate. To do that, let's add another Optional extension. This one adds a function called matching, which takes a predicate in the form of a closure that returns a Bool after being passed an unwrapped optional value:

extension Optional {
    func matching(_ predicate: (Wrapped) -> Bool) -> Wrapped? {
        guard let value = self else {
            return nil
        }

        guard predicate(value) else {
            return nil
        }

        return value
    }
}

Using the above, we can now make our search bar handling code very nice and expressive, by constructing yet another chain of optional expressions. First we use our new matching API to check that the search bar's text matches our length requirement, and then we map the result of that operation directly into our performSearch method - like this:

searchBar.text.matching { $0.count > 2 }
              .map(performSearch)

While the above is pretty cool, where this approach really shines is when we want to match a given optional against a series of different predicates. Using a guard statement with a longer list of conditions can quickly become messy, but with our new matching function we now have an easy way to chain multiple predicates together - like in this example where we're verifying that a database record matches two different requirements:

let activeFriend = database.userRecord(withID: id)
    .matching { $0.isFriend }
    .matching { $0.isActive }

We can now treat our optionals a bit like queryable values. Pretty cool! 😎

Assigning reusable views

Finally, let's take a look at how we can extend the Optional type to make working with reusable views a bit nicer. A common pattern in Apple's UI frameworks is for views to provide certain "slots" where we as the API user can insert our own custom subviews. For example, UITableViewCell provides an accessoryView property that lets us place any view we want at the trailing edge of a cell - which is super convenient when building custom lists.

However, since those slots need to support any kind of view, the type we're dealing with is most often Optional<UIView> - which means that we almost always have to do typecasting to convert the value of such a property into our own view type, leading to many if let dances that look something like this:

// Since we want to reuse any existing accessory view if possible,
// we first need to cast it to our own view type
if let statusView = cell.accessoryView as? TodoItemStatusView {
    statusView.status = item.status
} else {
    let statusView = TodoItemStatusView()
    statusView.status = item.status
    cell.accessoryView = statusView
}

This is yet another situation in which an extension on Optional can come very much in handy. Let's write an extension on all optionals that wrap a UIView, and again use the power of @autoclosure to enable us to pass an expression that creates a new view if needed, which is only used in case we don't have an existing one:

extension Optional where Wrapped == UIView {
    mutating func get<T: UIView>(
        orSet expression: @autoclosure () -> T
    ) -> T {
        guard let view = self as? T else {
            let newView = expression()
            self = newView
            return newView
        }

        return view
    }
}

Using the above extension we can now make our cell configuration code from before a lot nicer - transforming it into two simple lines of code:

let statusView = cell.accessoryView.get(orSet: TodoItemStatusView())
statusView.status = item.status

Not only have we eliminated boilerplate by getting rid of the need to manually unwrap and assign an accessory view, we've also made our code more declarative by replacing if and else conditions with a clearly defined expression. Big win! 😀

Conclusion

The way Swift's type system was designed to support custom extensions on any type - and the fact that optionals are implemented using a standard enum - makes it possible to do some really interesting things. By wrapping common expressions and operations in well-targeted Optional extensions, we can both reduce boilerplate and make our code dealing with optionals more clear and expressive.

However, it's also important to carefully decide what type of extensions that we wish to introduce into our code base, since a large number of extensions can make our project's learning curve a lot steeper. In general, avoiding premature optimization becomes key here, and only introducing an extension once we see a pattern repeated in multiple places might be a good policy to have.

What do you think? Do you have any extensions on Optional that you really like, or could any of the ones from this article be useful in your code base? Let me know - along with any questions, comments or feedback that you might have - on Twitter @johnsundell.

Thanks for reading! 🚀