How to make a Swift enum with associated values equatable
SE-0185 Synthesizing Equatable and Hashable conformance has been implemented in Swift 4.1, so that it suffices do declare conformance to the protocol (if all members are Equatable
):
enum ViewModel: Equatable {
case heading(String)
case options(id: String, title: String, enabled: Bool)
}
For earlier Swift versions, a convenient way is to use that tuples can be compared with ==
.
You many also want to enclose the compatibility code in a Swift version check, so that the automatic synthesis is used once the project is updated to Swift 4.1:
enum ViewModel: Equatable {
case heading(String)
case options(id: String, title: String, enabled: Bool)
#if swift(>=4.1)
#else
static func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
switch (lhs, rhs) {
case (let .heading(lhsString), let .heading(rhsString)):
return lhsString == rhsString
case (let .options(lhsId, lhsTitle, lhsEnabled), let .options(rhsId, rhsTitle, rhsEnabled)):
return (lhsId, lhsTitle, lhsEnabled) == (rhsId, rhsTitle, rhsEnabled)
default:
return false
}
}
#endif
}
Swift enum conformance to Equatable when result type used as associated value: Type doesn't conform to protocol Equatable
Enum can automatically conform to Equatable
if its associated values conform to Equatable
, from docs:
For an enum, all its associated values must conform to Equatable. (An enum without associated values has Equatable conformance even without the declaration.)
And the Result<Success, Failure>
only conforms to Equatable
when
Success
conforms toEquatable
,Failure
conforms toEquatable
, andFailure
conforms toError
.
Your result's failure is only conform to Error
and Error
is not Equatable
yet. You can try to replace your Error
with a type that conforms to both Error
and Equatable
enum BookAction: Equatable {
case dataResponse(Result<Book, ActionError>)
}
struct ActionError: Error, Equatable { }
Ref:
https://developer.apple.com/documentation/swift/equatable
https://developer.apple.com/documentation/swift/result
How to test equality of Swift enums with associated values
Swift 4.1+
As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185, Swift also supports synthesizing Equatable
and Hashable
for enums with associated values.
So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2)
works. The key is to add the Equatable
protocol to your enum.
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
Before Swift 4.1
As others have noted, Swift doesn't synthesize the necessary equality operators automatically. Let me propose a cleaner (IMHO) implementation, though:
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
switch (lhs, rhs) {
case let (.Name(a), .Name(b)),
let (.Number(a), .Number(b)):
return a == b
default:
return false
}
}
It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.
How do I know the type of an enum with associated value in a generic way?
You just need to make your enumeration conform to Equatable and compare the associated values for equality:
enum MyEnum: Equatable {
case one
case two
case three(user: String)
}
var array: [MyEnum] = []
func add(_ value: MyEnum) {
if array.contains(value) { return }
array.append(value)
}
add(.one)
add(.one)
array
add(.three(user: "a"))
add(.three(user: "b"))
add(.three(user: "a"))
array[0] // one
array[1] // three(user: "a")
array[2] // three(user: "b")
To check if the last element is of case .three:
if case .three = array.last {
print(true)
}
Automatic synthesis of Equatable conformance for Swift struct or enum through an extension
The proposal (SE-0185) for the synthesis says something different to the documentation you linked:
Users must opt-in to automatic synthesis by declaring their type as Equatable or Hashable without implementing any of their requirements. This conformance must be part of the original type declaration or in an extension in the same file (to ensure that private and fileprivate members can be accessed from the extension).
According to the proposal, declaring conformance in extensions in the same file also automatically generates the required members, which is in line with the actual behaviour. If you declare the extension in a different file from the type, you should see an error message:
Extension outside of file declaring struct 'Point' prevents automatic synthesis of '==' for protocol 'Equatable'
Making an enum with associated values conform to decodable?
You need to create a custom init(from decoder: Decoder) throws
, and treat your associated values like they are normal fields in the object, using CodingKeys
to decode them. Details depend on how your associated values are actually represented in encoded object.
For example if your values are encoded as:
{ "team1": "aaa", "team2": "bbb" }
and
{ "individual": ["x", "y", "z"]}
You can:
enum SportType: Decodable {
case team(String, String) //Two team names
case individual([String]) //List of player names
// Define associated values as keys
enum CodingKeys: String, CodingKey {
case team1
case team2
case individual
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Try to decode as team
if let team1 = try container.decodeIfPresent(String.self, forKey: .team1),
let team2 = try container.decodeIfPresent(String.self, forKey: .team2) {
self = .team(team1, team2)
return
}
// Try to decode as individual
if let individual = try container.decodeIfPresent([String].self, forKey: .individual) {
self = .individual(individual)
return
}
// No luck
throw DecodingError.dataCorruptedError(forKey: .individual, in: container, debugDescription: "No match")
}
}
Test:
let encoded = """
{ "team1": "aaa", "team2": "bbb" }
""".data(using: .utf8)
let decoded = try? JSONDecoder().decode(SportType.self, from: encoded!)
switch decoded {
case .team(let a, let b):
print("I am a valid team with team1=\(a), team2=\(b)")
case .individual(let a):
print("I am a valid individual with individual=\(a)")
default:
print("I was not parsed :(")
}
Prints:
I am a valid team with team1=aaa, team2=bbb
Swift Enum Equatable expected argument issue
One simple way to test if a variable is of type .poules
is to move the actual comparison inside a computed property of the enum. This also makes sense since this comparison isn't really what you would use for Equatable
var isAnyPoules: Bool {
if case .poules = self { return true }
return false
}
This will make it easier to perform the check
if router.currentView.isAnyPoules
or
.transition(viewRouter.originView.isAnyPoules ? X : X)
Related Topics
Physicsbody: Could Not Create Physics Body
Swift: Double Conversion Inconsistency. How to Correctly Compare Doubles
Making Swift Generics Play with Overloaded Functions
Animating a Navigation Bar Color
Gcd with Static Functions of a Struct
How to Stream Remote Audio in iOS 13? (Swiftui)
Multi-Face Detection in Realitykit
Binding in a Foreach in Swiftui
Why Is Calendar.Date(From: Datecomponents) Adding Time
Extension for Generic Type 'Unsafemutablepointer<Uint8>'
Preventing a Coredata Crash for Upgrading Users
Nstimer.Scheduledtimerwithtimeinterval in Swift Playground
How to Subclass Nsoperation in Swift to Queue Skaction Objects for Serial Execution
Swift Extension on Generic Struct Based on Properties of Type T
Pattern Match and Conditionally Bind in a Single Switch Statement