1

I have a class A which conforms to Equatable and a class B which inherits from A. I defined == for each of these classes.

When comparing two B objects, the correct == function is used to determine the equality. When checking to see whether an array of B contains an object B, the == defined for A will be called.

//: Playground - noun: a place where people can play

class A: Equatable {
    var id: Int

    init(id: Int) {
        self.id = id
    }
}

class B: A {
}

func ==(lhs: A, rhs: A) -> Bool {
    print("Comparing using A's ==")
    return false
}


func ==(lhs: B, rhs: B) -> Bool {
    print("Comparing using B's ==")
    return lhs.id == rhs.id
}

let b1 = B(id: 1)
let b2 = B(id: 2)

let array: [B] = [b1, b2] // an array explicitly defined as containing B objects

print("\((b2 == b2))")
print("\(array.index(of: b2))")
print("\(array.contains(b2))")

Output:

Comparing using B's ==
true
Comparing using A's ==
Comparing using A's ==
nil
Comparing using A's ==
Comparing using A's ==
false

Why is this happening?

EDIT: SOLVED!

As Hamish commented below, this is because B will rely on A's == in certain cases, for more, see here, point #2: == overload for custom class is not always called

To work around this I did the following: I defined a method called isEqualTo(object:) that is called from A's ==. That method is overridden from B.

This is probably a little bit more inefficient because of the overhead of going through another few method calls, but it works good enough for the data I am dealing with.

//: Playground - noun: a place where people can play

class A: Equatable {
    var id: Int

    init(id: Int) {
        self.id = id
    }

    // define an isEqualTo method which takes another A instance
    // this will be called from A's ==
    func isEqualTo(object: A) -> Bool {
        return self == object
    }
}

class B: A {

    // override the isEqualTo: method
    override func isEqualTo(object: A) -> Bool {

        // if we are actually dealing with an object of type B, cast it 
        // first, then call B's ==
        if let bObject = object as? B {
            return self == bObject
        }

        // fallback to A's  ==
        return super.isEqualTo(object: object)
    }
}

func ==(lhs: A, rhs: A) -> Bool {
    print("Comparing using A's ==")
    // use a designated, overridable method to determine equality
    return lhs.isEqualTo(object: rhs)
}


func ==(lhs: B, rhs: B) -> Bool {
    print("Comparing using B's ==")
    return lhs.id == rhs.id
}

let b1 = B(id: 1)
let b2 = B(id: 2)

let array: [B] = [b1, b2]

print("\((b2 == b2))")
print("\(array.index(of: b2))")
print("\(array.contains(b2))")

Output:

Comparing using B's ==
true
Comparing using A's ==
Comparing using B's ==
Comparing using A's ==
Comparing using B's ==
Optional(1)
Comparing using A's ==
Comparing using B's ==
Comparing using A's ==
Comparing using B's ==
true
Community
  • 1
  • 1
clawoo
  • 791
  • 6
  • 14
  • 2
    The problem is that `B` doesn't get to define it's own conformance to `Equatable` – it instead relies on `A`'s conformance when `==` is dynamically dispatched to. The `(B, B) -> Bool` overload can however be called with static dispatch. – Hamish Mar 01 '17 at 19:56
  • 1
    Thanks, I don't know how I missed the other post! Anyway, I would say this is some very surprising and counter-intuitive behavior. – clawoo Mar 01 '17 at 20:06

0 Answers0