The Joys of Swift – Enums

hsoi blog, talk 1 Comment

I’ve been using Apple’s Swift programming language fairly exclusively since around December 2014. While it started out with frustrations, I’ve really come to love and embrace the language. I cannot see going back to Objective-C – I don’t see what the gain would be.

Well, in fairness, there are a few gains. The Swift toolset is still nowhere as mature as the Objective-C toolset. Xcode woes are frequent, including constant SourceKitService crashes and the fact the Swift compiler is still pretty slow. Xcode 7.2.1 is an improvement, and Xcode 7.3 with Swift 2.2 (the first release post-open-sourcing that will really incorporate the gains from open source contribution) I expect should be a good gain as well. Those don’t hold me up too badly tho in a day, and the gains I receive from writing more robust code I think win out in the long run.

One of my favorite things about Swift? Enums.

Enums – More than integers

In C-based languages, a enum is really little more than a glorified integer. It’s good for what it is and serves a fair purpose over plain old integers, but no matter what it’s still limited by the notion of being an integer.

In Swift, enums are a whole lot more. From the Swift 2.1 Language guide:

“An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.

If you are familiar with C, you will know that C enumerations assign related names to a set of integer values. Enumerations in Swift are much more flexible, and do not have to provide a value for each case of the enumeration. If a value (known as a “raw” value) is provided for each enumeration case, the value can be a string, a character, or a value of any integer or floating-point type.

Alternatively, enumeration cases can specify associated values of any type to be stored along with each different case value, much as unions or variants do in other languages. You can define a common set of related cases as part of one enumeration, each of which has a different set of values of appropriate types associated with it.

Enumerations in Swift are first-class types in their own right. They adopt many features traditionally supported only by classes, such as computed properties to provide additional information about the enumeration’s current value, and instance methods to provide functionality related to the values the enumeration represents. Enumerations can also define initializers to provide an initial case value; can be extended to expand their functionality beyond their original implementation; and can conform to protocols to provide standard functionality.”

Excerpt From: Apple Inc. “The Swift Programming Language (Swift 2.1).” iBooks. https://itun.es/us/jEUH0.l

This is powerful.

To me, one of the biggest boons of Swift enumerations is that they are not just integers; in fact, they aren’t anything other than their own full-fledged type. This avoids a host of issues (because it’s not just some other type masked with a name), plus the ability to associate values with the enumeration provides a great deal of flexibility. The enum is a string? Awesome. The enum is a tuple? Think of the possibilities.

Let me provide an example from a project I’ve been working on.

In this project, we process a document that is essentially fields with values. The fields are well-established, but the values could be “anything”. We do have a well-known set of possible values, but it’s always possible something outside of our knowledge could occur. In code, we need to perform checks of “is this field’s value X?”, and sometimes we need to collapse the fields from the very-specific values to more generalized “family” values. All of these checks and comparisons could be done with raw values, but as they are strings it’s complicated and cumbersome to do so throughout the code. So, it makes sense to declare an enum for these values as it makes working with the data much easier. And not just easier, we can get the compiler to help us.

First, in working with the data, I can do something like:

enum Fruit {
    case Orange
    case Grapefruit
    case Watermelon
    case Cantaloupe
    case Other(String)

    init(string: String?) {
        if let string = string where !string.isEmpty {
            switch string.lowercaseString {
            case "orange":
                self = .Orange
            case "grapefruit"
                self = .Grapefruit
            case "watermelon"
                self = .Watermelon
            case "cantaloupe"
                self = .Cantaloupe
            default:
                self = .Other(string)
            }
        }
        else {
            self = .Other(NSLocalizedString("Unknown Fruit", comment: ""))
        }
    }

    var isCitrus: Bool {
        switch self {
        case .Orange, .Grapefruit:
            return true
        case .Watermelon, .Cantaloupe, .Other:
            return false
        }
    }
}

So let’s say my data field is “fruit” and while I know some of the possible fruits, if I don’t have total control over what the values could be, this still enables me some level of mapping. I get the raw data from my document, I create a Fruit passing the raw value from my document. If that raw value matches one of our known values, we get it, else we fall into the .Other case, which allows us to consider that it’s something else yet still preserve the value from our document. Now, working with Fruit is much easier than trying to know about strings, comparing them, and so on.

We can perform simple operations on a Fruit instance, such as determining if it isCitrus or not. This points out another valuable aspect of Swift – switch statements must be complete and explicitly handle all possible values. A lot of people would solve this by added a default case, but I try to avoid default as much as possible. Why? This is where I can put the compiler to work for me. Consider I discover we can now receive values of "Apple" so I wish to add a case Apple to the enum. When I do this, the list of all possible Fruit has changed. If a switch statement had a default, then the default is what will be used (as will happen in the init() function). But in the case of isCitrus, the compiler will now generate an error because this switch is no longer exhaustive. Consider the impacts upon code maintenance! Now instead of having to hunt for all uses of the enum — or even remembering to hunt for all instances — the compiler will handle it all for you. This leads to ensuring code is correct and complete, sooner rather than later.

Consider if we bring in protocols, such as CustomStringConvertible. We could have an encapsulated way to convert the Fruit for display. What if the data from the document wasn’t very human-readable, we can easily make it so and not have to have some sort of secondary utility function to make it happen — it’s all part of the type, e.g.:

extension Fruit: CustomStringConvertible {
    var description: String {
        switch self {
        case .Orange:
            return "A yummy Florida orange"
        case .Grapefruit:
            return "A sour grapefruit"
        case .Watermelon:
            return "A juicy watermelon"
        case .Cantaloupe:
            return "A ripe cantaloupe"
        case .Other(let value):
            return value
        }
    }
}

Which also demonstrates how we can extract the .Other value and return it. It may not be as pretty, but at least data is being preserved.

Think about other protocols, such as Equatable or Comparable. With a C-style enum, either you’d have to count on equality/comparison by integer value, or you’d have to author some other function (e.g. here we’d want maybe an alphabetical sort) and then have to remember to invoke that special function to perform the comparison. Here the enum is a first-class type, so you can just perform operations directly upon it.

Swift enumerations bring great power to the table — something Objective-C just can’t do. I’ve used Objective-C for almost 20 years and love the language, but Swift can do so much more. Anything that helps me write more expressive, more maintainable, and just “better” code, is a good and useful thing.

Comments 1

  1. Pingback: Swiftly geeky | Stuff From Hsoi

Leave a Reply