Based on a talk given at Swift Summit San Francisco 2015.
Protocols are great.
Since all types — structs, classes, enums — can conform to protocols, they’re a great way to make the type system more compositional, in contrast to the inheritance model of classes. Protocols themselves have inheritance, which means you can build up a set of protocols in this way as well.
With protocol extensions and default method implementations, protocols can do much more than just specify an interface. But what kinds of things make sense to put into protocols?
To help answer this question, I turned to the Swift standard library. I thought, how does the way the Swift team uses protocols give us hints on how we should do it?
By the Numbers
As of this writing, the Swift standard library includes 54 public protocols. By “public” I mean protocols marked as
public and don’t begin with an underscore, which is the compiler developer’s universal signal for “maybe don’t use this yourself, mkay?”
The numbers look like this:
- 89 public protocols in the headers
- 35 public protocols starting with
- That leaves 54 public protocols
- But there are 55 in the docs since they include one underscore-prefixed one because of…reasons, I’m sure. This is where the “55” in the talk title comes from.
I’ve grouped the standard library protocols into three categories:
Let’s take these one at a time.
First up are the can-do protocols. These describe things that the type can do or have done to it. They also end in the -able suffix, which makes them easy to spot.
For example, a type that conforms to the
Hashable protocol means that you can hash it down to an
Comparable protocols mean you can compare two instances against each other with the various equality and comparison operators.
There’s a small subset of protocols in this group that have to do with alternate views too. Take
CustomPlaygroundQuickLookable, as an example: it means your type can be quick looked in a playground, which means you need to provide an alternate view for your value for the quick look to display.
The idea here is that the intrinsic value is the same: you’re just changing the outside representation of it. The “can-do” operation is to quick-look the thing, which means you need to provide what that looks like.
Next up are the is-a protocols. These describe what kind of thing it is. In contrast to the can-do protocols, these are more based on identity.
These make up fully half of the public protocols in the standard library and you can identify them because they end in the word Type.
CollectionType, for example, which arrays, dictionaries, and sets conform to. And there’s
GeneratorType if you’re building something collection-ish and you want to support iteration. And there’s
ErrorType to use with the new error handling model.
Then there’s MirrorPathType, which has this delightful comment next to it in the headers:
/// Do not declare new conformances to this protocol; they will not /// work as expected. public protocol MirrorPathType
Many of the protocols in this category are ones where you might use the conforming types, but not create your own types to conform to them.
Finally, there are the can be types. These end in the word Convertible and mean that the type can either be converted from or converted to something else. It’s all about change!
There are the simple initializer-style ones like
FloatLiteralConvertible, which means your conforming type needs to have an initializer that takes some kind of a float literal and builds your type. Then there’s the reverse direction of
CustomStringConvertible, which specifies that your type can be converted into a string.
This category is pretty straightforward. If you have types that can become other types, don’t just add a method to do the conversion or add another initializer – think about setting it up as a protocol. By using a protocol instead of trusting yourself to use the correct method/initializer signature, you can ensure there’s a consistent, well-defined interface.
We’ve seen three kinds of protocols from the standard library, having to do with capability, identity, and conversion.
What are the broad patterns to think about for our own code? We have:
Operations. If there are a common set of operations that you have to perform on your types, consider breaking those out into a protocol.
Related to that is alternate views. If your type has an alternate view or representation that’s not quite a full-on conversion, think about whether it belongs as a protocol.
For identity, this is your chance to have something like multiple inheritance. Thinking about identity and what the types are, and grouping similar types together with protocols.
And finally, conversions. Whether you convert from a type, or to a particular type a lot, consider breaking out that very common conversion as a protocol to help you keep track and keep things consistent.
Thinking about what to put in protocols is just the first step; there’s a whole world of reading and experimentation to do with putting it all into practice and trying out protocol-oriented programming.
Seeing how much Apple has done to move common functionality into protocols in the standard library is a great inspiration on how powerful they can be to create these feature bundles in protocols, and then use those bundles throughout your code. I definitely encourage you try this out in your own code, and look at your types through the lens of what they have in common, using protocols.
Thanks very much to Swift Summit for inviting me to speak and give this talk!Tweet