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?
source to share
Sorry, this is not a direct answer to your question.
As Alexander pointed out, the Swift standard library has this standard implementation !=
:
@_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
}
}
source to share
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 !=
).
source to share