How to properly implement the Equatable protocol in a class hierarchy?
After lots of research and some trial and error I finally came up with a working solution. The first step was moving the ==
operator from inside the class to the global scope. This fixed the errors about static
and final
.
For the base class this became:
func == (lhs: Base, rhs: Base) -> Bool { return lhs.x == rhs.x}class Base : Equatable { var x : Int}
And for the subclass:
func == (lhs: Subclass, rhs: Subclass) -> Bool { return true}class Subclass : Base { var y : String}
Now the only part left is figuring out how to call the ==
operator of the base class from the ==
operator of the subclass. This led me to the final solution:
func == (lhs: Subclass, rhs: Subclass) -> Bool { if lhs.y == rhs.y { if lhs as Base == rhs as Base { return true } } return false}
That first if
statement results in a call to the ==
operator in the base class.
The final solution:
Base.swift:
func == (lhs: Base, rhs: Base) -> Bool { return lhs.x == rhs.x}class Base : Equatable { var x : Int}
Subclass.swift:
func == (lhs: Subclass, rhs: Subclass) -> Bool { if lhs.y == rhs.y { if lhs as Base == rhs as Base { return true } } return false}class Subclass : Base { var y : String}
Following the other answers I came up with this:
class Base : Equatable { var x : Int static func == (lhs: Base, rhs: Base) -> Bool { return lhs.x == rhs.x }}class Subclass : Base { var y : String static func == (lhs: Subclass, rhs: Subclass) -> Bool { return lhs.y == rhs.y && (lhs as Base) == (rhs as Base) }}
I know it's been a while since the question is posted, but I hope my answer helps.
TLDR -- Instead of trying to override ==
, you provide a custom comparing method, make ==
call it, and override the custom comparing method if needed.
So you said
All of the classes will only be used in Swift so I do not want to involve
NSObject
or theNSCopying
protocol.
But if you were to subclass NSObject
, how will you write your custom comparison method? You will override isEqual(Any?)
, right? And if you try to conform to Equatable
protocol in your subclass, compiler will complain about "Redundant conformance to protocol Equatable
" because NSObject
already conformed to Equatable
.
Now that gives us some hints about how NSObject
handles this problem -- it provides a custom comparing method isEqual(Any?)
, call it inside ==
, and its subclasses can override it if needed. You can do the same in your own base class.
Without further ado, let's do some experiments(in Swift 4). Define some classes
class Grandpa: Equatable { var x = 0 static func ==(lhs: Grandpa, rhs: Grandpa) -> Bool { return lhs.isEqual(to: rhs) } func isEqual(to object: Any?) -> Bool { guard object != nil && type(of: object!) == Grandpa.self else { return false } let value = object as! Grandpa return x == value.x }}class Father: Grandpa { var y = 0 override func isEqual(to object: Any?) -> Bool { guard object != nil && type(of: object!) == Father.self else { return false } let value = object as! Father return x == value.x && y == value.y }}class Son: Father { var z = 0 override func isEqual(to object: Any?) -> Bool { guard object != nil && type(of: object!) == Son.self else { return false } let value = object as! Son return x == value.x && y == value.y && z == value.z }}
And write some test code
let grandpa1 = Grandpa()let grandpa2 = Grandpa()let grandpa3: Grandpa? = nillet grandpa4: Grandpa? = nillet father1 = Father()let father2 = Father()let father3 = Father()father3.y = 1let son1 = Son()let son2 = Son()let son3 = Son()son3.z = 1print("grandpa1 == grandpa2: \(grandpa1 == grandpa2)")print("grandpa1 == grandpa3: \(grandpa1 == grandpa3)")print("grandpa3 == grandpa4: \(grandpa3 == grandpa4)")print("grandpa1 == father1: \(grandpa1 == father1)")print("father1 == father2: \(father1 == father2)")print("father1 == father3: \(father1 == father3)")print("son1 == son2: \(son1 == son2)")print("son1 == son3: \(son1 == son3)")
Run it and you should get
grandpa1 == grandpa2: truegrandpa1 == grandpa3: falsegrandpa3 == grandpa4: truegrandpa1 == father1: falsefather1 == father2: truefather1 == father3: falseson1 == son2: trueson1 == son3: false