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.

SOURCES

Comparing enums without their associated values - Swift.org

Tancrede ChazalletBitbuildr