Just some quick and simple Swift stuff in this post around writing cleaner code. It’s pretty common to see a pattern like the following in existing code when evaluating enumerations.
enum IceCreamFlavor {
case chocolate
case chocolateChip
case coffee
case peach
case strawberry
case vanilla
}
let currentFlavor: IceCreamFlavor = .chocolate
///...code...
// Long-winded test to determine if flavor is basic, non-premium flavor.
// Let's fix this!!
let isBasicFlavor = currentFlavor == .strawberry || currentFlavor == .chocolate || currentFlavor == .vanilla
print(isBasicFlavor) //True
While the above code works, it’s also not very legible, is repetitive, and can be hard to maintain as well as error-prone if multiple enum
values need to be tested. So how can we make this better?
Using a Function and switch
It’s always important to consider your different options when coding and weigh the pros and cons of each approach. Let’s step through some different ways to make our code better!
One of the simplest scenarios to try would be to use the switch
statement to at least make things more readable and reduce repetitive code. For example:
func isBasicFlavor(_ flavor: IceCreamFlavor) -> Bool {
switch flavor {
case .strawberry, .chocolate:
return true
default:
return false
}
}
print(isBasicFlavor(currentFlavor))
That’s a little better and encapsulates the logic; but we might be better served adding this function to the enum
itself so that any caller could use it more simply and conveniently.
Using Computed Properties
Creating a computed property called isBasicFlavor
on the enum
lets us do just that.
enum IceCreamFlavor {
case chocolate
case chocolateChip
case coffee
case peach
case strawberry
case vanilla
var isBasicFlavor: Bool {
switch self {
case .strawberry, .chocolate, .vanilla:
return true
default:
return false
}
}
}
let isBasicFlavor = currentFlavor.isBasicFlavor
print(isBasicFlavor)
There are still other ways to make this even better and replace the possibly clunky switch
statement. One approach would be to use a static
array as the computed property and the contains
statement to evaluate it from the calling code (and maybe show that list of basic flavors in the calling app as a bonus!).
enum IceCreamFlavor {
case chocolate
case chocolateChip
case coffee
case peach
case strawberry
case vanilla
static var basicFlavors: [IceCreamFlavor] {
return [.chocolate, .vanilla, .strawberry]
}
}
let isBasicFlavor = IceCreamFlavor.basicFlavors.contains(currentFlavor)
print(isBasicFlavor)
Still Another Option Leveraging Extensions
Another method could be to leverage the raw value of the enum
to perform some form of comparison with a Swift extension. This can come in handy when you don’t control the source code or values from the extension itself (which might be in someone else’s library). While we currently cannot create a Swift extension for an enum
directly, we can create one on a protocol associated with the enum
.
In our example below, we have based the enum on the String
type which as a raw value type means the enum will now conform to the RawRepresentable
protocol. This then allows us to create a simple extension like that essentially does the reverse of the previous example.
enum IceCreamFlavor: String {
case chocolate = "Chocolate"
case chocolateChip = "Chocolate Chip"
case coffee = "Coffee"
case peach = "Peach"
case strawberry = "Strawberry"
case vanilla = "Vanilla"
static var basicFlavors: [IceCreamFlavor] {
return [.chocolate, .vanilla, .strawberry]
}
}
extension RawRepresentable where RawValue: Equatable {
/// Evaluate if input is present in array of values
/// - Parameter input: Input
/// - Returns: Boolean true if the input present in array
func isContained(in input: [RawValue]) -> Bool {
return input.contains(rawValue)
}
}
let isBasicFlavor = currentFlavor.isContained(in: IceCreamFlavor.basicFlavors.map({ $0.rawValue }))
print(isBasicFlavor)
This example was a little convoluted and is, in my opinion, less satisfactory but does present yet another way to skin the proverbial cat or deal with certain cases. Because the extension relies on RawRepresentable
, similar functionality would work with many different types of enumerations (or custom objects implementing Equatable
and RawRepresentable
).
Where To Go Next?
This article will hopefully help you explore some options around Swift enumerations and some of the available tools. I’d also encourage you to explore the Swift language more deeply as well as look at other possibilities for working with enumerations such as the CaseIterable
protocol, introduced a couple years ago in Swift 4.2 and Xcode 10. Try listing all of the ice cream flavors above directly from the enum
with CaseIterable
and allCases
for example!
More importantly it’s much more important (and fun!) to realize that coding is an evolution, that there are always different approaches (both better and worse) to solving problems, and that we can always make our code better and learn at the same time! With WWDC 2020 just around the corner, knowing and believing this will put you ahead of the game.
One thought on “Cleaning Up Swift Enumeration Evaluation”