Protocols – My Current Recommendations

The big talk about Swift lately is around protocols. Everything should be a protocol they say! Well, that's great in theory, however, in practice, that attitude can lead to some really unfortunate side-effects.

Here are the top two things that I always try to keep in mind when working with protocols in my code:

1. Don't treat protocols as a type

A lot of solutions I see (and what I did initially) are basically treating protocols as a base class in your type hierarchy. I don't think this is really where protocols shine. This design pattern is still the "object-oriented" way of thinking about the problem.

To put it another way, if your protocol really only has meaning within your type hierarchy, ask yourself if it really makes sense to make it a protocol. I don't think an answer of, "well, I want my type to be a struct so I need to use a protocol here instead" is a good reason. Decompose it and make it more applicable if that's really the case.

Futher validation of this: http://swiftdoc.org/swift-2/. Notice anything about all of those protocols (well, all of the ones not prefixed with _)? All of them can be applied to multiple different types regardless of type hiearchy.

2. Don't make your protocols generic unless you really have too!

Hopefully this is a just a point-in-time problem, but as soon as you make your protocols generic, you lose the ability have typed collection of hetergenous instances of protocols. I consider this a serious design limitation. For instance, all of the non-Self constrained functionality of a protocol should be safely callable from any place that protocol wants to be used.

This also applies to having your protocol adhere to generic protocols, such as Equatable. Generics are infectious.

Doing this:

protocol Foo : Equatable {}

Is almost certainly going to cause you some significant grief down the line.

Here's a practical example:

Let's say we want to model an HTTP response and we want to support two different types of response types: strings and JSON data.

It might be tempting to do something like this:

class HTTPResponse<ResponseType> {
    var response: ResponseType
    init(response: ResponseType) { self.response = response }
}

I think this is bad approach. As soon as this happens, we have artificially limited our ability to use this type; it cannot be used in collections in a heterogenous fashion, for example. Now, why might I want a collection of these that have different ResponseType representation? Well, let's say I want to build a response/request playback engine for testing. The collection of responses I get back will be of my supported types: string and JSON data.

One option to address this is to simply use AnyObject. This works, but that pretty much sucks.

Another approach to address this problem is with protocols. However, instead of just creating a ResponseType protocol, let's think about what we really want from this. What I really care about is that any ResponseType that is provided to an HTTPResponse can be represented as a String.

With that in mind, we end up with something like this:

protocol StringRepresentable {
    var stringRepresentation: String { get }
}

class HTTPResponse {
    var response: StringRepresentable
    init(response: StringRepresentable) { self.response = response }
}

To me, this is vastly superior as it provides the consumers of the API to be much more flexible while still maintaining some type clarity.

Of course, this doesn't come without its own drawabks, and I'd be remiss to not point it out. If you actually want to deal with the specific type for the response, you need to cast it.

class JSONResponse : StringRepresentable {
    var stringRepresentation: String = "{}"
}

let http = HTTPResponse(response: JSONResponse())
let json = http.response as? JSONResponse

This is still significally better though. I, the caller, know what the response type is supposed to be, or what the possible values could be. This is starkly different then when I'm looping over the collection pull at the out the responses and want to get the value of the response because the consumer of the code could have actually created other response types, such as XMLResponse, and now my code would have no way of knowing about it.

In a perfect world, we could do this:

class HTTPResponse<ResponseType : StringRepresentable> {
    var response: ResponseType
}

let responses = [json, string]  // responses is an array of HTTPResponse where ResponseType is unrealized

You would still need to cast the response in the collection use case, however, using the json instance directly would still give you full type validation.

Until we can get there though, I'll take the collection type of [HTTPResponse] over [AnyObject] every time.

Protocols – My Current Recommendations