Reflectable enums in Swift 3

Suppose you are working on a Swift program that needs a data model to represent a contact, such as a person from the user’s address book or a FaceBook friend. You might start off by modeling this as a Contact struct, like this:

struct-type

This approach is simple but has a significant drawback. Notice how the Contact struct has two optional properties: phoneNumber and username. These properties are conditionally assigned a value, based on the contact type.

struct-instances

The rules for when each property should be nil are not obvious unless a developer happens to read the property comments. A design like this enables developers to make mistakes that could be easily avoided. It would be better if you could not accidentally access a property whose value is nil, because you forgot to check the contact type.

A common way to solve this problem in Swift is to create an enum:

enum-initial

The Contact enum has two cases. Each case has an associated struct value. When working with this enum it is impossible to accidentally access the wrong property, such as an AddressBook contact’s username, or a FaceBook contact’s phone number, because there are separate structs defined for each kind of contact. Those structs contain exactly the values relevant to their contact type. No need for optional properties. Hurrah!

However, there is also a drawback to this approach. In many cases it is convenient to access the properties common to each kind of contact in a uniform manner, like this:

enum-explicit-usage

To facilitate this, you could create computed properties on the Contact enum that read the corresponding property from each associated struct value in a switch block.

enum-explicit-extension

In this simple example the code above might not seem too bad. But in a realistic application where the data models have many properties, and there are many model entities to deal with, this type of code quickly becomes a tedious chore to write and maintain. Repetitive, boring code is a fertile source of bugs. There must be a better way…

This problem can be remedied through the use of Swift’s Mirror API. Mirrors are Swift’s way of allowing for introspective code; in other words, for code to reflect upon itself at run time. Compared to other platforms, like .NET or Java, a Mirror provides only very rudimentary reflection support, but it’s enough for our purposes here.

Let’s take a look at my ReflectableEnum protocol:

reflectable-enum

This protocol’s sole method has a default implementation provided by a protocol extension. It uses Mirrors to peek inside an enum instance and its associated struct in order to find the value of a property. This protocol can be used to alleviate the coding burden seen above, like so:

reflectable-use-case

It’s important that the properties added to Contact have the exact same name as the properties they map to on the structs associated with the enum cases. The usage of Swift’s #function means that the name of the property which calls value(of:) is passed in as the propertyName argument.

The downside to this approach is that it is less safe. This relies on the use of naming conventions. It is not something the compiler can guarantee will work at run time. The implementation of the value(of:) method force-unwraps optional values in several places, which is a sign that this code is going to crash if things are not correctly set up. I want it to crash if it encounters a problem (such as if a struct does not have a property with a certain name and type), but that’s only necessary because the compiler cannot police this kind of code. If you prefer the comfort of knowing that the Swift compiler always has your back, this solution is not for you.

The code presented here is just a rough sketch. It might not meet the needs of your application, but it can be a simple starting point for further enhancements.

An Xcode playground containing the ReflectableEnum code is available here:

https://github.com/ijoshsmith/reflectable-enum

Happy Swifting!

This entry was posted in Swift, Uncategorized and tagged . Bookmark the permalink.

10 Responses to Reflectable enums in Swift 3

  1. Josh Adams says:

    Great suggestion! I had never realized that reflection is possible for enums.

    Unless I am mistaken, in the second code snippet, Tom should have a ContactType of .faceBook. Also, Facebook is not camel cased.

    • Josh Smith says:

      Thanks Josh. Good catch on the ContactType. I updated that code snippet. This just goes to show how bad that API design is! 🙂

      I used camel case for FaceBook because I preferred to have it use the same casing as AddressBook. No big deal. But thanks for pointing it out.

  2. Wait, why not:
    “`
    struct Contact {
    let id: String
    let name: String
    let contactType: ContactType

    enum ContactType {
    case addressBook(phoneNumber: String)
    case faceBook(username: String)
    }
    }
    “`?
    Doesn’t that solve all your problems?

    • ninoscript says:

      (sorry, I don’t know how to format code in WordPress)

    • Josh Smith says:

      Thanks for the feedback. Sure, your code would definitely do the trick for the small example I used in this article, but it would become less usable when working with larger model types. Suppose the Contact enum had ten cases instead of two, and the various cases had 6 shared properties, and 5-10 unique properties each. Imagine how tedious it would be to unwrap the contactType in a switch block, how many ways you could get it wrong. What about when some of the contact types need to have properties added or removed? In that situation it’s much easier to have separate structs defined for each contact type, which is why I promoted that in this article.

      • ninoscript says:

        I would make each case have a struct for the unique properties, that would solve most issues (and is what I’m doing today). The only case I see your method could be better, would be in the case of the removal of a shared property from one of the cases.

  3. It’s nice for unwrapped the value by switch-case like this:

    let contacts = [Contact.addressBook(bob),
    Contact.faceBook(tom)]

    contacts.forEach {

    switch $0 {
    case let .addressBook(ab) :
    print (ab.phoneNumber)

    case let .faceBook(fb) :
    print(fb.username)

    }
    }

  4. Vincent says:

    Interestig, the protocol extension should be augmented to deal with classes. In the case where this pattern would be use with different classes sharing a commun base class on which the properties are stored. Currently only the subclass properties would be inspected.

  5. AC says:

    Very interesting way to use Mirror but those force-unwrap make it seems a bit hacky (or at least dangerous).
    Maybe I misunderstood your issue but wasn’t it enough to keep the contactType unrelated stuff (id & name) in the struct Contact and just move the specific parts in the type ?

    struct PhoneNumber {

    let number: String
    let ext: String
    }

    struct Email {

    let address: String
    }

    enum ContactType {

    case phone(PhoneNumber)
    case email(Mail)
    }

    struct Contact {

    let contactType: ContactType

    let id: String
    let name: String
    }

    let c1 = Contact(contactType: .phone(PhoneNumber(number: “0666666666”, ext: “+33”)),
    id: “c1”,
    name: “Tata”)

    let c2 = Contact(contactType: .phone(Email(address: “yoyo@yopmail.com”))
    id: “c2”,
    name: “Yoyo”)

  6. Pingback: Dew Drop - April 17, 2017 (#2459) - Morning Dew

Comments are closed.