Swift enumerations with associated values and how to compare them
Discovering associated values in swift enumerations is quite exhilarating as it opens numerous possibilities by way of mapping out your data and states.
But, you soon start to face difficulties. Indeed, enums! Once they have associated values, they start to act differently to a normal enum.
Let’s have a look at a normal enum:
enum DownloadState {
case notStarted
case downloading
case finished
case failed
}
With the above kind of enum, you could write:
let state: DownloadState = .downloading
if state == .downloading {
print("It's downloading!")
} else {
print("It's not downloading!")
}
And simply enough, that works just as you need it to work.
Now, what if we use associated values?
enum DownloadState {
case notStarted
case downloading(Double) // Pass the progress
case finished
case failed(Error) // Pass the error
}
Yep, our previous comparison will not compile anymore. Hopefully Swift provides us with a nice solution which works in various situations:
let state: DownloadState = .downloading(0.5)
if case .downloading = state {
print("It's downloading!") // Executed
} else {
print("It's not downloading!") // Not executed
}
if case .downloading(0.5) = state {
print("50% of the download done!") // Executed
}
if case .downloading(1) = state {
print("100% of the download done!") // Not executed
}
But, what if we want to compare the associated values as well? Well, this is possible too:
let state: DownloadState = .downloading(0.5)
if case let .downloading(progress) = state, progress > 0.25 {
print("Downloaded more than 25%!") // Executed
}
if case let .downloading(progress) = state, progress <= 0.25 {
print("Downloaded less than 25%!") // Not Executed
}
Now, this makes us able to override comparison operators to create some custom comparisons using extensions:
extension DownloadState {
static func ==(lhs: DownloadState, rhs: DownloadState) -> Bool {
switch (lhs, rhs) {
case (.notStarted, .notStarted): return true
case (.downloading(let lhsProgress), .downloading(let rhsProgress)): return lhsProgress == rhsProgress
case (.finished, .finished): return true
case (.failed(let lhsError), .failed(let rhsError)): return lhsError.localizedDescription == rhsError.localizedDescription
default: return false
}
}
static func ~=(lhs: DownloadState, rhs: DownloadState) -> Bool {
switch (lhs, rhs) {
case (.notStarted, .notStarted),
(.downloading(_), .downloading(_)),
(.finished, .finished),
(.failed(_), .failed(_)): return true
default: return false
}
}
}
let state1: DownloadState = .downloading(0.5)
let state2: DownloadState = .downloading(0.5)
let state3: DownloadState = .downloading(0.4)
let state4: DownloadState = .finished
if state1 == state2 {
print("State 1 and 2 are the same!") // Executed
}
if state1 == state3 {
print("State 1 and 3 are the same!") // Not executed
}
if state1 ~= state3 {
print("State 1 and 3 are the similar!") // Executed
}
if state1 ~= state4 {
print("State 1 and 4 are the similar!") // Not executed
}
This was written using Swift 5.1.2.