Search code examples
swiftsubclassswift3equatable

How to properly implement the Equatable protocol in a class hierarchy?


I'm trying to implement the == operator (from Equatable) in a base class and its subclasses in Swift 3. All of the classes will only be used in Swift so I do not want to involve NSObject or the NSCopying protocol.

I started with a base class and a subclass:

class Base {
    var x : Int
}

class Subclass : Base {
    var y : String
}

Now I wanted to add Equatable and the == operator to Base. Seems simple enough. Copy the == operator signature from the documentation:

class Base : Equatable {
    var x : Int

    static func == (lhs: Base, rhs: Base) -> Bool {
        return lhs.x == rhs.x
    }
}

So far so good. Now for the subclass:

class Subclass : Base {
    static override func == (lhs: Base, rhs: Base) -> Bool {
        return true
    }
}

But this results in an error:

Operator function overrides a 'final' operator function

OK. After some research (still learning Swift 3) I learn that static can be replaced with class to indicate the type method can be overridden.

So I attempt to change static to class in Base:

class Base : Equatable {
    var x : Int

    class func == (lhs: Base, rhs: Base) -> Bool {
        return lhs.x == rhs.x
    }
}

But that results in a new error:

Operator '==' declared in non-final class 'Base' must be 'final'

Ugh. This is far more complicated than it should be.

How do I implement the Equatable protocol and the == operator properly in a base class and a subclass?


Solution

  • 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
    }