As expected, there was so much goodness in WWDC 2020 last week. From exciting new software changes in Swift UI, collection views, and much more to the anticipated move away from Intel to Apple Silicon, there was something exciting for just about everyone. For this post, I’m just going to focus on a tiny sliver of the new changes in Swift 5.3 related to my last post Cleaning Up Swift Enumeration Evaluation – using the Comparable
protocol to evaluate enum
expressions.
Comparing Enumerations before Swift 5.3
Previously we looked at some different ways to evaluate whether an expression was present in an enumeration with switch
statements, computed properties, and RawRepresentable
. With Swift 5.3 and specifically proposal SE-0266, enums without an associated type or with one conforming to Comparable
can now conform to Comparable
themselves and gain the ability to compare values.
What does this mean? Well, prior to this change, if we had an enum that was intrinsically sorted (like ContainerLevel
below), we would have had to either declare a type for it or use a private backing variable with a switch
or some custom logic to implement sorting and comparability. For example, the following works but requires that the enumeration use Int
raw values to conform with Comparable
and we have to manually code the <
operator.
// Here we have to declare the enum as a Comparable type to implement the protocol
enum ContainerLevel: Int, Comparable {
case empty = 0
case almostEmpty
case halfFull
case almostFull
case full
static func < (lhs: ContainerLevel, rhs: ContainerLevel) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
//Are we running low?
let coffeeMugLevel: ContainerLevel = .almostFull
let needMoreCoffee = coffeeMugLevel > .halfFull
print(needMoreCoffee) //true
What’s Changed?
With iOS 14 and Xcode 12, we can now declare Comparable
with no explicit overload needed resulting in much cleaner, more succinct code.
// With iOS 14, we can just use the `Comparable` protocol and the intrinisic ordering of the enum
enum ContainerLevel: Comparable {
case empty
case almostEmpty
case halfFull
case almostFull
case full
}
//Are we running low?
let coffeeMugLevel: ContainerLevel = .almostFull
let needMoreCoffee = coffeeMugLevel > .halfFull
print(needMoreCoffee) //true
Sorting By Associated Values
You can also implicitly sort enumerations with associated values as well and it works as expected. Let’s pretend we have a function to look at coffee machine water levels for example.
enum ContainerLevel: Comparable {
case empty
case partiallyFull(Int)
case full
static func isAlmostFull(_ level: ContainerLevel) -> Bool {
//More than 90% full
return level > partiallyFull(90)
}
static func isAlmostEmpty(_ level: ContainerLevel) -> Bool {
//Less than 10% full
return level < partiallyFull(10)
}
}
//Running close to empty! Yikes!
let coffeeMachine: ContainerLevel = .partiallyFull(5)
print(ContainerLevel.isAlmostFull(coffeeMachine)) //false
print(ContainerLevel.isAlmostEmpty(coffeeMachine)) //true
As we would also anticipate, sorting an array of enums works as well.
let levels: [ContainerLevel] = [.full, .empty, .partiallyFull(75), .partiallyFull(15)].sorted()
print(levels.map({"\($0)"})) //"empty", "partiallyFull(15)", "partiallyFull(75)", "full"
Nice! We’ve got a properly sorted list from the unsorted input that we provided. Obviously this is a trivial example; but hopefully you can leverage Comparable and enum
sooner rather than later! Since this change is tied to Swift as well, you should be able to use it in apps on earlier versions of iOS as well with Xcode 12 and Swift 5.3. Happy coding!