Swift & Protocols - getting into the mess of conformance

I came across an excellent introductory article on Swift & Protocols by Ash Furrow, and albeit using an obligatory cat example, explains the use of protocol members and functions to contractually bind classes.

Say we want to model some animals and food. Animals eat food. We can have each animal type be aware of all the different food types, but that would tightly couple the types together. Instead, we can use the following Food protocol.

protocol Food {
    var caloriesPerGram: Double { get }
}

Then, we can define our Cat type.

struct Cat {
    var calorieCount: Double = 0
    mutating func eat(food: Food, grams: Double) {
        calorieCount += food.caloriesPerGram * grams
    }
}

In order to feed our cat, we need a type that conforms to the Food protocol. struct Kibble: Food { var caloriesPerGram: Double { return 40 } }

Super. Using the two together is really easy: let catFood = Kibble() var dave = Cat()

dave.eat(catFood, grams: 30)

(I know that this is fundamental stuff that you probably already know, but it’s important to make sure we’re on the same page before we continue.)

Here’s the problem I ran into: in a pure Swift environment, I want to have optional methods in my protocol so that objects that conform to it may opt-in to certain functionality. It turns out that this is really really hard.

Let’s revisit our example of the cat and the food. Cats – turns out – have not evolved to eat kibble; they rely on moisture from their food to hydrate themselves. (That’s why kitties often have problems with urinary tract infections – they’re dehydrated.) Some foods, like canned cat food, have moisture. Some foods, like kibble, have none. Since I believe this difference is fundamental enough to different foods, I’d like to expand my protocol to have an optional amount of moisture. That way all the foods that don’t have any moisture don’t have to bother implementing those part of the protocol. Easy, right?

protocol Food {
    var caloriesPerGram: Double { get }
    optional var milliLitresWaterPerGram: Double { get }
}

Er, no, actually. This code produces a compiler error advising you that the *’optional’ keyword can only be a applied to members of an @objc protocol. *Huh, that’s weird.

Fine, so we put @objc at the beginning of our protocol declaration.

@objc protocol Food {
    var caloriesPerGram: Double { get }
    optional var milliLitresWaterPerGram: Double { get }
}

But then we run into another problem: Swift structs, like Kibble, cannot conform to Objective-C protocols.

It seems like quite the pickle. I want to use all the cool awesome new Swift features, but my solution requires the use of legacy Objective-C. So what do I do?

I tried creating a second protocol and asking if the food parameter conformed to it, like so.

protocol WetFood: Food { var milliLitresWaterPerGram: Double { get } }

struct Cat {
    var calorieCount: Double = 0
    var hydrationLevels: Double = 0
    mutating func eat(food: Food, grams: Double) {
        calorieCount += food.caloriesPerGram * grams

        if let wetFood = food as? WetFood {
            // do something here
        }
    }
}

But the compiler still errors: Cannot downcast from ‘Food’ to non-@objc protocol type ‘WetFood’. Swift protocols are just not designed to be used in this way.

After playing around for a while, things seemed a bit hopeless. Eventually, I realized that I was trying to solve a Swift problem using Objective-C methodology – something that can only lead to tears. I’ve been saying for a while that we need to reevaluate our approaches to familiar problems with Swift, and it was time to follow my own advice.

Let’s revisit the high-level problem I’m trying to solve: I have two types and I want them to talk to each other without coupling.

So let’s take a break from Cat and Food and look at table views. How might they look in pure Swift?

The table view delegate/datasource protocols have always mystified me – no one has ever given me a satisfactory rule dividing what is a datasource method from what is a delegate method. The datasource protocol alone has eleven methods, all but two of which are optional.

The methods are optional because the creators of UITableView wanted the behaviour to be opt-in (hey, just like our Food example!). By having optional components of their contract, table views behave differently. If you don’t implement the methods to reorder the table view, it doesn’t show the reordering controls (for example).

Imagine Apple has hired you to rewrite UITableView in pure Swift. Let’s start with the existing protocol implementation. Currently, UITableView has an optional dataSource property.

unowned(unsafe) var dataSource: UITableViewDataSource?

Ok, now how about that UITableViewDataSource protocol?

protocol UITableViewDataSource : NSObjectProtocol {

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int


    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

    optional func numberOfSectionsInTableView(tableView: UITableView) -> Int

    optional func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? 
    optional func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String?

    // Editing

    optional func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool

    // Moving/reordering

    optional func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool

    // Index

    optional func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! 
    optional func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int 

    // Data manipulation - insert and delete support

    optional func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)

    // Data manipulation - reorder / moving support

    optional func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath)
}

Yikes! I’ve included some of the comments from Apple’s header files to show you how complex this protocol is. For example, the “Moving/reordering” function determines if a given row can be moved, but is only invoked if the datasource also implements the final method in the protocol. One wonders why these two interdependent methods are so far apart in the header file.

If we were going to write a new, purely Swift TableView type, we’ll need a data source to get information about the content we’ll be displaying. By following Justin’s advice, it’s not hard to see how things could be rewritten (let’s assume we have Swift equivalent types for NSIndexPath, UITableViewCell, etc).

class TableView {
    var numberOfSections: Int = 1

    weak var dataSource: TableViewDataSource?
    weak var titlesDataSource: TableViewTitlesDataSource?
    weak var editingDataSource: TableViewEditingDataSource?
    weak var reorderingDataSource: TableViewReorderingDataSource?
    weak var indexSataSource: TableViewIndexDataSource?

    // TODO: Finish re-implementing UITableView
}

protocol TableViewDataSource: class {

    func tableView(tableView: TableView, numberOfRowsInSection section: Int) -> Int
    func tableView(tableView: TableView, cellForRowAtIndexPath indexPath: IndexPath) -> TableViewCell
}

protocol TableViewTitlesDataSource: class {

    func tableView(tableView: TableView, titleForHeaderInSection section: Int) -> String?
    func tableView(tableView: TableView, titleForFooterInSection section: Int) -> String?
}

protocol TableViewEditingDataSource: class {
    func tableView(tableView: TableView, canEditRowAtIndexPath indexPath: IndexPath) -> Bool
    func tableView(tableView: TableView, commitEditingStyle editingStyle: TableViewCell.EditingStyle, forRowAtIndexPath indexPath: IndexPath)
}

protocol TableViewReorderingDataSource: class {
    func tableView(tableView: TableView, canMoveRowAtIndexPath indexPath: IndexPath) -> Bool
    func tableView(tableView: TableView, moveRowAtIndexPath sourceIndexPath: IndexPath, toIndexPath destinationIndexPath: IndexPath)
}

protocol TableViewIndexDataSource: class {
    func sectionIndexTitlesForTableView(tableView: TableView) -> [String]!
    func tableView(tableView: TableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int
}

I’m not saying that this is necessarily how I would actually write things – it’s only supposed to show you how dividing areas of concern into separate protocols makes things a lot more clear.

First, we moved the section count into the TableView type itself. All we need to populate the table view is to give it an object that conforms to the TableViewDataSource protocol.

Extending behaviour is very easy: you want to have cell reordering in your table view? Then set the table view’s reorderingDataSource property to something that will handle reordering. You want titles? Go ahead and use TableViewTitlesDataSource. And so on.

Sure, you’ll probably have only one object that conforms to all the protocols you need (and be honest – it’s probably a view controller, isn’t it?). But the power of this technique is not that we can divide the various data sources into different objects. Instead, the advantage is that we don’t have additional semantic coupling between functions in the protocol. Of course canMoveRowAtIndexPath can only be called if moveRowAtIndexPath is also implemented – they’re in the same protocol.

(Of course, UITableViewDelegate could benefit from a similar rewriting, but as it contains no fewer than thirty three functions – all optional – I will leave that as an exercise for the reader.)

(Also note that the : class suffix of the protocols. This specifies that the protocols may only be conformed to by classes and not structs or enums – those two types cannot be weakly referenced.)

OK, so UITableView sucks and like most things that suck in Objective-C, they’re way better in Swift. So what?

So, Swift programmers, you have a choice now. The next time you write a protocol and it needs optional functions, don’t just add @objc to the declaration. Split it out into multiple protocols like a responsible adult. There, that’s better. Your mother and I are so proud of you.

But wait – what about my first example? With the cat and the food and everything? What do we do there? Well, just like my Twitter question the other day, the answer isn’t so simple.

Our problem before is that the Cat struct didn’t know if the Food it was passed in contained water it should keep track of. Not all foods have water in them, after all. But wait a second – everything that consumes food has a current calorie count and hydration. Let’s flip things around: instead of making the Cat responsible for updating its state when it eats something, let’s make the Food responsible for updating the state of whatever is consuming it. protocol FoodConsumer { var calorieCount: Double { get set } var hydrationLevel: Double { get set } }

protocol Food {
    func beConsumedBy(consumer: FoodConsumer, grams: Double) -> FoodConsumer
}

struct Cat: FoodConsumer {
    var calorieCount: Double = 0
    var hydrationLevel: Double = 0
}

struct Kibble: Food {
    let caloriesPerGram: Double = 40

    func beConsumedBy(consumer: FoodConsumer, grams: Double) -> FoodConsumer {
        var newConsuner = consumer
        newConsuner.calorieCount += grams * caloriesPerGram
        return newConsuner
    }
}

struct FancyFeast: Food {
    let caloriesPerGram: Double = 80
    let milliLitresWaterPerGram: Double = 0.2

    func beConsumedBy(consumer: FoodConsumer, grams: Double) -> FoodConsumer {
        var newConsuner = consumer
        newConsuner.calorieCount += grams * caloriesPerGram
        newConsuner.hydrationLevel += grams * milliLitresWaterPerGram
        return newConsuner
    }
}

This is a lot better. The different foods are responsible for doing their own thing to whatever consumer comes their way and the consumer itself is only responsible for things that are common to all food consumers: calorie count and water levels.

(We could have also opted to use an inout FoodConsumer parameter to pass in &dave and modify the struct itself. I prefer this immutable approach.)

And how might we use this code? Well, we have a few options. Here’s one that I like.

extension Cat {
    func eat(food: Food, grams: Double) -> FoodConsumer {
        return food.beConsumedBy(self, grams: grams)
    }
}

let catFood = Kibble()
let wetFood = FancyFeast()
var dave = Cat()

dave = dave.eat(catFood, grams: 30) as Cat
dave = dave.eat(wetFood, grams: 20) as Cat

This approach obviously isn’t suited for every case, but it is a useful tool to have at your disposal.

Remember that the general problem is “how do I get different types to talk to one another without coupling?”

Source: (http://ashfurrow.com/blog/protocols-and-swift/)