Post

How to Create Protocols That Enums Can Conform to with Their Cases

Learn how to create protocols for enums to conform with their cases, including handling associated values, inspired by insights from the Composable Architecture.

How to Create Protocols That Enums Can Conform to with Their Cases

When working with enums in Swift, you might want to define a protocol that your enums can conform to, particularly for their cases. This idea is inspired by techniques used in The Composable Architecture (TCA). In this post, we’ll explore how to create such protocols and handle associated values effectively.

Defining a Simple Protocol for Enum Cases

Imagine you have an enum like this:

1
2
3
enum ButtonAction {
    case tapped
}

You want to enforce that any enum conforming to a protocol must include a case representing a button action, such as tapped. Here’s how you can define a protocol to achieve this:

1
2
3
public protocol ButtonTapAction {
    static var tapped: Self { get }
}

Key Points:

  1. Static Property: The property must be static because when you call ButtonAction.tapped, you’re accessing the enum’s static case.
  2. Self as Return Type: The return type is Self to ensure the protocol works for any conforming enum.
  3. Immutable: The property is read-only (get), meaning it cannot be modified.

Now, conforming to this protocol looks like this:

1
2
3
4
5
6
7
public protocol ButtonTapAction {
    static var tapped: Self { get }
}

enum MyButtonAction: ButtonTapAction {
    case tapped
}

That’s it! Your enum now conforms to the protocol, and you can enforce this behavior across other enums.


Handling Associated Values

What if you want to add associated values to your enum cases? For example, you might want to track the number of taps:

1
2
3
enum MyButtonAction {
    case tappedWith(count: Int)
}

Using a static var in the protocol won’t work here because associated values require parameters. Instead, we can use a static func in the protocol:

1
2
3
public protocol ButtonTapAction {
    static func tappedWith(count: Int) -> Self
}

Key Points:

  1. Static Function: Just like the static property, the function must also be static.
  2. Self as Return Type: The return type is still Self, ensuring flexibility for any conforming enum.
  3. Matching Parameters: The function’s parameters must match the associated value type of the enum case.

Here’s how the protocol and the enum would look together:

1
2
3
4
5
6
7
8
9
public protocol ButtonTapAction {
    static var tapped: Self { get }
    static func tappedWith(count: Int) -> Self
}

enum MyButtonAction: ButtonTapAction {
    case tapped
    case tappedWith(count: Int)
}

Summary

Creating protocols that enums can conform to is a powerful way to enforce consistent behavior. Here’s what we covered:

  1. Use static var for simple cases without associated values.
  2. Use static func for cases with associated values to handle parameters.

This pattern is clean, expressive, and leverages Swift’s type safety to create reusable and maintainable code.

Let me know your thoughts in the comments!

☕ Support My Work

If you found this post helpful and want to support more content like this, you can buy me a coffee!

Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏

This post is licensed under CC BY 4.0 by the author.