Enums Cases as Protocol Witnesses and the Open-Closed Principle

Toby O'Connell
CodeX
Published in
2 min readJul 24, 2021
Image by Amza Andrei

Background

Recently I have been implementing a lot of analytics events in a client’s app. Tagging this application (which has a great deal of UI) was always going to a laborious task, so I was looking to architect an ergonomic API to interact with.

Whilst deciding on the interface I remembered the Swift 5.3 feature that allows enum cases to be used as protocol witnesses. This became a great teaching opportunity for the junior I was pairing with. In short, a protocol that requires a static function that returns Self can be satisfied by an enum case with an associated value. Additionally, any static variable of type Self in the protocol can be satisfied by a enum case without an associated value.

Suppose we had the following protocol:

We could satisfy it with the following struct:

Additionally, we could also satisfy the protocol with this enum:

To the API consumer both of these look the same as they are called identically:

The Open-Closed Principle

The Open-Closed principle is one of the 5 SOLID design principles. It states that “software entities should be open for extension, but closed for modification.” What this means is, our API should be extendible without having to change the original source code.

Enum vs Struct

The Open-Closed principle is one place where enums fall down. They cannot be extended to include additional cases, so every time a new event type is added to our protocol, we need to modify the original enums source. This is not the case with a struct that can be extended across multiple files.

One other consideration is the size of the protocol. Currently there are two events, but what happens when the number of events to track explodes? If we had some 60 screens each with 2-5 events we’d end up with around 210 enum cases. These would all have to sit in the same file and in the case of our name variable, we’d have an enormous switch statement too.

In a situation where a backend analytics system can only handle certain events, it may make complete sense to prevent any unknown & unhandled events slipping through by using an enum, but overall I think the struct pattern works best in this scenario.

Conclusion

Using enum cases as protocol witnesses is a cool addition to the Swift language, but we still need to consider if enums are the best tool for the job at hand. As always, when you’ve got a shiny new hammer, it’s tempting to treat everything a nail, but it’s always worth stepping back and assessing the needs of the API & the ergonomics of its ongoing use.

--

--

Toby O'Connell
CodeX
Writer for

Swift / iOS developer - I write about things that I find interesting or innovative.