Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 725
rmaddy Avatar asked Oct 07 '16 05:10

rmaddy


People also ask

How do you implement Equatable?

To adopt the Equatable protocol, implement the equal-to operator ( == ) as a static method of your type. The standard library provides an implementation for the not-equal-to operator ( != ) for any Equatable type, which calls the custom == function and negates its result.

What is the use of Equatable protocol in Swift?

In Swift, an Equatable is a protocol that allows two objects to be compared using the == operator. The hashValue is used to compare two instances. To use the hashValue , we first have to conform (associate) the type (struct, class, etc) to Hashable property.

Can a protocol conform to Equatable?

The short answer is that we can't. Each conforming type can be Equatable (in fact both our concrete types are) but we don't have a way of checking equality at the level of the protocol since the protocol does not declare any properties.

Can a protocol conform to Equatable Swift?

Overview. Types that conform to the Equatable protocol can be compared for equality using the equal-to operator ( == ) or inequality using the not-equal-to operator ( != ). Most basic types in the Swift standard library conform to Equatable .


1 Answers

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
}
like image 97
rmaddy Avatar answered Sep 17 '22 13:09

rmaddy