Swift: Why You Shouldn’t Use Default Implementations in Protocols

I’m currently doing a very big refactor of try! Swift Data now that the Tokyo conference is over and I have time to repay some technical debt. As part of the refactor, I’m removing a bunch of large switch statements for view-level display data and putting them into individual view models that conform to a strict protocol.

The Setup

The conference app has different sessions – talks, breakfast, lunch, announcements, etc. They are all displayed in a Table View with a title, subtitle, location, etc. Before, the data layer was messy like this:

So I extracted the data display methods into a protocol:

And created individual view models for each session type. For example, here is the BreakfastSessionViewModel:

This allowed me to have only one switch statement:

The Problem

The big thing here is that multiple session view models have the same default data implementation. That is why I created a SessionDataDefaults object to access the default data easily (see the use-case in the BreakfastSessionViewModel implementation).

So as you can imagine, some of the session view model implementations (check the BreakfastSessionViewModel for reference) use the default data values.

When I shared this refactor with a friend, he immediately saw a new refactoring opportunity – create default implementations of the relevant methods in the protocol!

Default Implementation?

At first, that sounded great, but after thinking about it I decided against the default implementation refactor. Here’s why:

  • Each variable of a session needs a lot of thought put into it. Even if the implementation details end up being the same as the default, I want it to be a strong conscious choice. If I make it a default implementation, it would be too easy to forget and not think about much. Oh, and the compiler won’t complain!
  • I want it to be easy to change the variable for each session. If a variable is not included in the file because the implementation is in the protocol, it’s more work to make the decision to add that into the file. The assumption is that the default should win. If it’s already there, it’s easier to just go in and make a small change.
  • Similar to the above point, I want it to be super readable where each variable is coming from. If a bunch of variables are in the default implementation of the protocol, it’s not as clear just by looking at the file.

I think adding default implementations to protocols should be considered very very carefully. The default needs to be something that is consistent and is the default for most cases, so if someone forgets to implement it (because the compiler won’t complain), it’s most likely not a big deal.

The one case where I love default implementations in protocols is when the function is not included in the the protocol definition – it’s just a common method with no interface exposed. Otherwise, it’s way too easy to forget about and introduce confusing bugs later!

Enjoy the article? Join over 17,500+ Swift developers and enthusiasts who get my weekly updates.

  • Philip Zhao

    I don’t really see the need of using protocol here. Why can’t you create a concrete object and set up a builder pattern to configure its properties? Protocol seems a bit overkill.

  • I’ve actually been down exactly the same path in my rewrite of Harken. Initially I wrote a default implementation, then realised I needed to specialise it in 6 out of the 7 classes that conformed to the protocol. And it feels like the default implementations are a bit too magical and it’s easy to forget they exist, almost like a side effect and that smells wrong to me.