Why should I add! = Make Equatable?

Why should I add! = To make the comparison correct?

import UIKit

class Person: NSObject {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

extension Person {
    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
    static func !=(lhs: Person, rhs: Person) -> Bool {
        return !(lhs == rhs)
    }
}

let first = Person(name: "John", age: 26) 
let second = Person(name: "John", age: 26)

/**
 * return false (which is correct) when we implement != function. But,
 * it will return true if we don't implement the != function.
 */
first != second 

      

Update: So I figured out why I had to add a function !=

for it to work. this is because the class inherits NSObject

, which uses the method isEqual

behind the scenes. But why does adding a function !=

make it work? Any explanation here?

+3


source to share


3 answers


Sorry, this is not a direct answer to your question.

As Alexander pointed out, the Swift standard library has this standard implementation !=

:

Equatable.swift

  @_transparent
  public static func != (lhs: Self, rhs: Self) -> Bool {
    return !(lhs == rhs)
  }

      

I cannot explain this behavior well, but the operator ==

in the default implementation above is allowed for the ==

default operator for NSObject

because NSObject

(as well as its descendants) already Equatable

and has an operator ==

that matches Equatable

. Thus, even if the explicit representation is exactly the same as your definition !=

, the operators ==

are resolved for different implementations.


General pointer to define your own equality for class NSObject

-descendant:

Make ==

and isEqual(_:)

consistent

You can store an instance of a class inside NSArray

or NSDictionary

(implicitly in many cases). Used isEqual(_:)

internally when an equality test is required, not an operator ==

.

So, simply by defining an operator ==

, without being consistently overridden on isEqual(_:)

, such methods will generate unexpected results.

To make consistent ==

and isEqual(_:)

,

just override only isEqual(_:)

and not define ==

and !=

explicitly
.

The default implementation ==

for NSObject

(and also !=

) uses isEqual(_:)

.



class Person: NSObject {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    override func isEqual(_ object: Any?) -> Bool {
        if let other = object as? Person {
            return self.name == other.name && self.age == other.age
        }
        return false
    }
}

      

(See ONE MORE THING below.)


Addition

Similar behavior can be found in classes not NSObject

.

class BaseClass {
    var a: Int

    init(a: Int) {
        self.a = a
    }
}
extension BaseClass: Equatable {
    static func == (lhs: BaseClass, rhs: BaseClass) -> Bool {
        print("`==` of BaseClass")
        return lhs.a == rhs.a
    }
}
let b1 = BaseClass(a: 0)
let b2 = BaseClass(a: 0)
print(b1 != b2) //->`==` of BaseClass, false ### as expected

class DerivedClass: BaseClass {
    var b: Int

    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}
extension DerivedClass {
    static func == (lhs: DerivedClass, rhs: DerivedClass) -> Bool {
        print("`==` of DerivedClass")
        return lhs.a == rhs.a && lhs.b == rhs.b
    }
}
let d1 = DerivedClass(a: 0, b: 1)
let d2 = DerivedClass(a: 0, b: 2)
print(d1 != d2) //->`==` of BaseClass, false ### `==` of DerivedClass and true expected

      

It seems we need some extra care when overriding ==

for Equatable

classes already .


ONE MORE THING

(Thanks for Hamish.)

You know that when you create a matching type Hashable

, you must execute ==

and sequentially hashValue

. NSObject

is declared as Hashable

and its hashValue

must match hash

. So when you override isEqual(_:)

in your NSObject

-dessent, you should also override hash

according to your overridden isEqual(_:)

.

So your class Person

should look something like this:

class Person: NSObject {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    override func isEqual(_ object: Any?) -> Bool {
        if let other = object as? Person {
            return self.name == other.name && self.age == other.age
        }
        return false
    }

    override var hash: Int {
        //### This is just an example, but not too bad in practical use cases.
        return name.hashValue ^ age.hashValue
    }
}

      

+2


source


NSObject

matches Equatable

, but uses its own method isEqual

, and in terms isEqual

both instances are not equal. NSObject

calls ==

only if your form !=

containing the ==

.

If you remove NSObject

(and add Equatable

) the implementation ==

works as expected.



The recommended way to do this NSObject

is to override isEqual

with a custom implementation and skip ==

(s !=

).

+3


source


What you are doing is wrong. You shouldn't use ==

or !=

. The NSObject subclass automatically implements ==

both isEqual:

. You are breaking this. You have to realize isEqual:

that is all.

+1


source







All Articles