How to make a Swift enum with associated values equatable

26,711

Solution 1

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
}

Solution 2

You can add something like below, check this link for more information. Return statement for options depend on your needs.

#if swift(>=4.1)
#else
func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
    switch (lhs, rhs) {
    case (let .heading(lhsString), let .heading(rhsString)):
        return lhsString == rhsString

    case (let .options(id1, title1, enabled1),let .options(id2, title2, enabled2)):
        return id1 == id2 && title1 == title2 && enabled1 == enabled2
    default:
        return false
    }
}
#endif

Solution 3

Elegant way to work with associated value ( even if the enum is indirect):

first you need to have the value property:

indirect enum MyEnum {
    var value: String? {
        return String(describing: self).components(separatedBy: "(").first
    }
    case greeting(text: String)
    case goodbye(bool: Bool)
    case hey
    case none
}

print(MyEnum.greeting(text: "Howdy").value)
// prints : greeting

now you can use the value to implement Equatable like this:

    indirect enum MyEnum: Equatable {
     static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {
        lhs.value == rhs.value
     }
    
     var value: String? {
        return String(describing: self).components(separatedBy: "(").first
     }
     case greeting(text: String)
     case goodbye(bool: Bool)
     case hey
     case none
   }

Solution 4

Maybe not relevant for the OP but this might help others:

Remember that if you only want to compare an enum value against a fixed value, you can simply use pattern matching:

if case let ViewModel.heading(title) = enumValueToCompare {
  // Do something with title
}

If you care about the associated value, you can add some conditions on it:

if case let ViewModel.heading(title) = enumValueToCompare, title == "SomeTitle" {
  // Do something with title
}
Share:
26,711

Related videos on Youtube

Stamp
Author by

Stamp

Updated on July 09, 2022

Comments

  • Stamp
    Stamp almost 2 years

    I have an enum of associated values which I would like to make equatable for testing purposes, but do not know how this pattern would work with an enum case with more than one argument.

    For example, summarised below I know the syntax for making heading equatable. How would this work for options, which contains multiple values of different types?

    enum ViewModel {
        case heading(String)
        case options(id: String, title: String, enabled: Bool)
    }
    
    func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
        switch (lhs, rhs) {
        case (let .heading(lhsString), let .heading(rhsString)):
            return lhsString == rhsString
        case options...
        default:
            return false
        }
    }
    

    I know Swift 4.1 can synthesize conformance for Equatable for us, but at present I am not able to update to this version.

  • jowie
    jowie almost 4 years
    Needs to be static func
  • Pascale Beaulac
    Pascale Beaulac almost 3 years
    This is true only if all case have associated value. if for some reason not all case have associate values then you need to empilement the func == (l, r) -> bool method.
  • Martin R
    Martin R almost 3 years
    @PascaleBeaulac: No, I do not think that is true.
  • Pascale Beaulac
    Pascale Beaulac almost 3 years
    try it. I know I have such a case in code and using swift 5. I need to do as Mehrdad posted below to remove the error. Just adding Equatable does not work. You get the error that it is not conforming to Equatable and you need to add stubs. if all cases do have associated value like in ur example then yes it does works.
  • Martin R
    Martin R almost 3 years
    @PascaleBeaulac: Cannot reproduce. enum Foo : Equatable { case bar; case baz(String) } compiles without problems for me.
  • Pascale Beaulac
    Pascale Beaulac almost 3 years
    I get the error... As I said in my app I have 1 enum that goes like this enum Foo : Equatable { case bar; case baz; case boz(SSO); case piff } and if we don't add the stub we get the error and it does not compile. edit: SSO simple swift object
  • Martin R
    Martin R almost 3 years
    @PascaleBeaulac: Does SSO conform to the Equatable protocol?
  • Pascale Beaulac
    Pascale Beaulac almost 3 years
    Yup, the SSO was not equatable. So it explains the error, and I do wish Xcode would put the error on the offending line. You got the thank from me and my team leader (who wrote the code in question ;) )