Replace Magic Strings with Enumerations in Swift

“What can I do to avoid these ‘magic strings’ in my code?” – This was the question I asked myself recently as I found myself in prepareForSegue comparing segue.segueIdentifier to in-line hard-coded Strings.  This kind of in-line hard-coding of a String for comparison purposes is what I mean by “magic strings” in this article.  I knew this felt like a bad idea, but the solution to a cleaner option wasn’t readily apparent to me.

I used to do things like create static string constants, or #define expressions so that I could easily change these values if I ever needed to update them for some reason.  You know… back in a former Objective-C developer life when these tactics were available to me.  But these options don’t exist in Swift.  What to do??

Enumerations to the rescue!

Specifically, Enumerations with pre-populated default values (called raw values).

By creating an Enumeration that stores raw String values, I was able to encapsulate what would otherwise be “magic strings” in a type-safe construct for easier, cleaner use in my code.

The Gist

Consider this fabricated example:

I have a storyboard with one main View Controller that connects to three other View Controllers through three segues:  “otherScreenSegue1”, “otherScreenSegue2”, and “otherScreenSegue3” as defined in the utilities panel in Xcode.

An Enumeration encapsulating those segue identifiers might look something like this:

1enum SegueIdentifier: String {
2    case OtherScreenSegue1 = "otherScreenSegue1"
3    case OtherScreenSegue2 = "otherScreenSegue2"
4    case OtherScreenSegue3 = "otherScreenSegue3"
5}

With this Enumeration defined (perhaps in its own .swift file – wherever you deem would be strategic and findable again), the prepareForSegue override can become “magic string”-free:

 1override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
 2    switch segue.identifier {
 3    case SegueIdentifier.OtherScreenSegue1.toRaw():
 4        println("Going to other screen 1")
 5    case SegueIdentifier.OtherScreenSegue2.toRaw():
 6        println("Going to other screen 2")
 7    case SegueIdentifier.OtherScreenSegue3.toRaw():
 8        println("Going to other screen 3")
 9    default:
10        println("Going somewhere else")
11    }
12}

Alternatively, if you prefer to compare the enum values themselves, you could do the following (thank you to Brandon Knope for pointing this out – I think it looks even cleaner than my original code above!):

 1override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
 2    if let identifier = SegueIdentifier.fromRaw(segue.identifier) {
 3        switch identifier {
 4        case .OtherScreenSegue1:
 5            println("Going to other screen 1")
 6        case .OtherScreenSegue2:
 7            println("Going to other screen 2")
 8        case .OtherScreenSegue3:
 9            println("Going to other screen 3")
10        default:
11            println("Going somewhere else")
12        }
13    }
14}

This strategy of encapsulating my various segue identifiers in an Enumeration provides me a one-stop-shop for reviewing, and if need-be, updating the String values to match what I’ve set up in my storyboard.

The Details

I’ve chosen an Enumeration with Raw Values, because the other two kinds (Enumerations as Inherent Types, or Enumerations with Associated Values) don’t allow me to do String comparison, or don’t allow me to define a value at declaration-time, respectively.

Notice one critical aspect of the Enumeration:  because default raw values are defined, all of the raw values must be of the same Type, as explicitly specified in the declaration line:  enum SegueIdentifier: String // All of the enum cases must be Strings

The next important thing to understand is that in order to do actual comparisons with the raw value itself (see the switch statement in my code example above), I needed to call toRaw() on the Enumeration value being used (first code example), or call fromRaw() to convert the segue.identifier string to an Enumeration value (second code example):

1SegueIdentifier.OtherScreenSegue1 // Enum value of type SegueIdentifier
2SegueIdentifier.OtherScreenSegue1.toRaw() // String value, "otherScreen1Segue"
3SegueIdentifier.fromRaw("otherScreenSegue1")! // Unwrapped Enum value of type SegueIdentifier

Conclusion

In addition to segue identifiers, I’m considering using raw value Enumerations to wrap NSNotificationCenter keys as well.  Share if you find other nice uses of raw value Enumerations!

So far, this solution has provided me a nice, straight-forward, type-safe way to encapsulate groups of Strings where the urge to fall back to “magic strings” would otherwise be high.

 

comments powered by Disqus