Give your simulator superpowers

Click here to
Build Apps Faster

Equatable conformance in Swift explained with code examples

Equatable conformance allows you to compare one object with another. Based on whether the objects match, you can perform a specific operation. You can rely on default comparison implementations or custom logic to compare two objects.

Many standard types are already comparable, but you must implement protocol conformance for your custom types. Secondly, you could run into objects defined inside 3rd party libraries that aren’t equatable. Let me explain how you can deal with these scenarios.

How to conform to the Equatable protocol

You can make a type conform to equatable by implementing the Equatable protocol:

struct Content: Equatable {
    let id = UUID()
    let title: String
}

After doing so, you’ll be able to compare two instances using the ==, or != operator:

let contentA = Content(title: "The Best Of SwiftUI")
let contentB = Content(title: "Becoming more productive")
print(contentA == contentB) // Prints: false

This is all you have to do most of the time due to automatically synthesized conformance.

Automatically synthesized Equatable conformance

The compiler started automatically synthesizing equatable conformance after SE-0185 got implemented. A type synthesizes conformance to Equatable if all of its members are Equatable. For example, this content struct automatically becomes comparable without the need to implement a custom equation method since all members (UUID and String) are comparable:

struct Content: Equatable {
    let id = UUID()
    let title: String
}

However, if we add a new member that doesn’t conform to Equatable:

struct Author {
    let name: String
}

struct Content: Equatable {
    let id = UUID()
    let title: String
    let author: Author
}

we’ll run into the following error:

Type ‘Content’ does not conform to protocol ‘Equatable’

Equatable conformance will only automatically satisfy if all members are equitable.
Equatable conformance will only automatically satisfy if all members are comparible.

Since Author’s name is a comparable String, we can solve the above error by adding the Equatable protocol conformance:

struct Author: Equatable {
    let name: String
}

In case you don’t have access to the type definition, you can use the following code instead:

extension Author: Equatable { }

Comparing enums

Enums benefit from automatically synthesized implementations as long as their associated values are comparable. You can learn more about this by reading Enums and Equatable.

Custom equatable implementations

Automatic conformance works excellently in most cases, but sometimes you want more control over the equatable comparison logic. For example, you might have different content sources that deliver articles from the same author. While the authors are similar, they could result in objects with unique IDs:

/// Imported from RSS feed A
let authorA = Author(id: "1", name: "Antoine van der Lee")

/// Imported from RSS feed B
let authorB = Author(id: "32", name: "Antoine van der Lee")

print(authorA == authorB) // Prints: false

You can write a custom comparison implementation to match authors solely on their name:

extension Author: Equatable {
    static func == (lhs: Author, rhs: Author) -> Bool {
        lhs.name == rhs.name
    }
}

print(authorA == authorB) // Prints: true

Matching two different objects

You might have two objects representing the same type of data. For example, a native CLLocationCoordinate2D and a server response containing location data named as LocationResponse:

let locationResponse = LocationResponse(latitude: 52.371807, longitude: 4.896029)
let location = CLLocationCoordinate2D(latitude: 52.371807, longitude: 4.896029)
print(locationResponse == location)

Both instances represent the same latitude and longitude data, but the last line results in the following error:

Binary operator ‘==’ cannot be applied to operands of type ‘LocationResponse’ and ‘CLLocationCoordinate2D’

We can solve this by writing a global comparison method for handling cases with both types:

func == (lhs: LocationResponse, rhs: CLLocationCoordinate2D) -> Bool {
    lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}

You’ll be more flexible using this technique, and you can bring unique types together.

Conclusion

You can compare objects if they implement the Equatable protocol or if a global comparison method exists for both types. You can rely on automatically synthesized conformance or go for more flexibility by implementing custom comparing logic.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!