Weekly Swift articles, podcasts and tips by John Sundell.

Composing types in Swift

Published on 28 Jan 2018

Composition is a super useful technique that lets us share code between multiple types in a more decoupled fashion. It's often posed as an alternative to subclassing, with phrases like "Composition over inheritance" - which is the idea of composing functionality from multiple individual pieces, rather than relying on an inheritance tree.

While subclassing/inheritance is also super useful (and Apple's frameworks, which we all depend on, rely heavily on that pattern), there are many situations in which using composition can let you write simpler and more robustly structured code.

This week, let's take a look at a few such situations and how composition can be used with structs, classes and enums in Swift.

Struct composition

Let's say that we're writing a social networking app, that has a User model and a Friend model. The user model is used for all kinds of users in our app, and the friend model contains the same data as the user one, but also adds new information - like on what date two users became friends.

When deciding how to set these models up, an initial idea (especially if you're coming from languages that traditionally have relied heavy on inheritance, like Objective-C) might be to make Friend a subclass of User that simply adds the additional data, like this:

class User {
    var name: String
    var age: Int
}

class Friend: User {
    var friendshipDate: Date
}

While the above works, it has some downsides. Three in particular:

Let's use composition instead! Let's make User and Friend structs (which lets us take advantage of Swift's built in mutability features) and instead of making Friend directly extend User, let's compose a User instance together with the new friend-specific data to form a Friend type, like this:

struct User {
    var name: String
    var age: Int
}

struct Friend {
    let user: User
    var friendshipDate: Date
}

Note above how the user property of a Friend is a let, meaning that it can't be mutated, even if a stand-alone User instance can. Pretty neat! πŸ‘

Class composition

So does that mean that we should make all of our types structs? I definitely don't think so. Classes are super powerful, and sometimes you want your types to have reference semantics rather than value semantics. But even if we choose to use classes, we can still use composition as an alternative to inheritance in many situations.

Let's build a UI that displays a list of some of our Friend models from the previous example. We'll create a view controller - let's call it FriendListViewController - that has a UITableView to display our friends.

One very common way to implement a table view-based view controller is to have the view controller itself be the data source for its table view (it's even what UITableViewController defaults to):

extension FriendListViewController: UITableViewDataSource {
   func tableView(_ tableView: UITableView, 
                  numberOfRowsInSection section: Int) -> Int {
        return friends.count
    }

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        ...
    }
}

I'm not going to say that doing the above is bad and you should never do it, but if we're looking to make this functionality more decoupled and reusable - let's use composition instead.

We'll start by creating a dedicated data source object that conforms to UITableViewDataSource, that we can simply assign our list of friends to and it'll feed the table view with the information that it needs:

class FriendListTableViewDataSource: NSObject, UITableViewDataSource {
    var friends = [Friend]()

    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int {
        return friends.count
    }

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        ...
    }
}

Then, in our view controller, we keep a reference to it and use it as the table view's data source:

class FriendListViewController: UITableViewController {
    private let dataSource = FriendListTableViewDataSource()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = dataSource
    }

    func render(_ friends: [Friend]) {
        dataSource.friends = friends
        tableView.reloadData()
    }
}

The beauty of this approach is that it becomes super easy to reuse this functionality in case we want to display a list of friends somewhere else in our app (in a "Find a friend" UI for example). In general, moving things out of view controllers can be a good way to avoid the "Massive view controller" syndrome, and just like we made our data source a separate, composable type - we can do the same thing for other functionality as well (data & image loading, caching, and so on).

Another way to use composition with view controllers in particular is to use child view controllers. Check out "Using child view controllers as plugins in Swift" for more on that.

Enum composition

Finally, let's take a look at how composing enums can give us a more granular setup that can lead to less code duplication. Let's say that we're building an Operation type that lets us perform some heavy work on a background thread. To be able to react to when an operation's state changes, we create a State enum that has cases for when the operation is loading, failed or finished:

class Operation {
    var state = State.loading
}

extension Operation {
    enum State {
        case loading
        case failed(Error)
        case finished
    }
}

The above might look really straight forward - but let's now take a look at how we might use one of these operations to process an array of images in a view controller:

class ImageProcessingViewController: UIViewController {
    func processImages(_ images: [UIImage]) {
        // Create an operation that processes all images in
        // the background, and either throws or succeeds.
        let operation = Operation {
            let processor = ImageProcessor()
            try images.forEach(processor.process)
        }

        // We pass a closure as a state handler, and for each
        // state we update the UI accordingly.
        operation.startWithStateHandler { [weak self] state in
            switch state {
            case .loading:
                self?.showActivityIndicatorIfNeeded()
            case .failed(let error):
                self?.cleanupCache()
                self?.removeActivityIndicator()
                self?.showErrorView(for: error)
            case .finished:
                self?.cleanupCache()
                self?.removeActivityIndicator()
                self?.showFinishedView()
            }
        }
    }
}

At first glance it might not seem like anything is wrong with the above code, but if we take a closer look at how we handle the failed and finished cases, we can see that we have some code duplication here.

Code duplication is not always bad, but when it comes to handling different states like this it's usually a good idea to duplicate as little code as possible. Otherwise we'll have to write more tests and do more manual QA to test all possible code paths - and with more duplication it's easier for bugs to slip through the cracks when we're changing things.

This is another situation in which composition is super handy. Instead of just having a single enum, let's create two - one to hold our State and another one to represent an Outcome, like this:

extension Operation {
    enum State {
        case loading
        case finished(Outcome)
    }

    enum Outcome {
        case failed(Error)
        case succeeded
    }
}

With the above change in place, let's update our call site to take advantage of these composed enums:

operation.startWithStateHandler { [weak self] state in
    switch state {
    case .loading:
        self?.showActivityIndicatorIfNeeded()
    case .finished(let outcome):
        // All common actions for both the success & failure
        // outcome can now be moved into a single place.
        self?.cleanupCache()
        self?.removeActivityIndicator()

        switch outcome {
        case .failed(let error):
            self?.showErrorView(for: error)
        case .succeeded:
            self?.showFinishedView()
        }
    }
}

As you can see, we have gotten rid of all code duplication and things are looking a lot more clear πŸ‘.

Conclusion

Composition is a great tool that can lead to simpler, more focused types that are easier to maintain, reuse & test. While it's not a complete replacement for either subclassing or inlining code directly into the types that use it, it's a technique that's good to keep in mind when setting up the relationships between various types.

There are of course many other ways to use composition in Swift than what this post covered, in particular there are two cases that I'll write about in an upcoming post - composing functions and protocols.

What do you think? Do you use some of these composition techniques in your code base already, or is it something you'll try out? Let me know, along with any questions, comments or feedback you might have, on Twitter @johnsundell.

Thanks for reading! πŸš€