Sorry, this is not a direct answer to your question.
As Alexander commented, Swift Standard Library has this default implementation of !=
:
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 solved to the default ==
operator for NSObject
, as NSObject
(and also its descendants) is already Equatable
and has an ==
operator to conform to Equatable
. So, even if the explicit representation is exactly the same as your !=
definition, the ==
operators are solved to different implementations.
A general guidline to define your own equality to an NSObject
-descendant class:
Make ==
and isEqual(_:)
consistent
You may store your class's instance inside NSArray
or NSDictionary
(in many cases implicitly). Inside their methods, isEqual(_:)
is used when equality check is needed, not the ==
operator.
So, just defining the ==
operator without giving a consistent override to isEqual(_:)
, such methods will generate unexpected result.
To make consistent ==
and isEqual(_:)
,
just override only isEqual(_:)
and do not define ==
and !=
explicitly.
The default implementation of ==
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 at the bottom.)
ADDITION
Similar behavior can be found on non-NSObject
classes.
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
Seems we need extra care when overriding ==
for already Equatable
classes.
ONE MORE THING
(Thanks for Hamish.)
You know you need to implement ==
and hashValue
consistently when creating a type conforming to Hashable
. NSObject
is declared as Hashable
, and its hashValue
needs to be consistent with hash
. So, when you override isEqual(_:)
in your NSObject
-descendent, you also should override hash
consistent with your overridden isEqual(_:)
.
So, your Person
class should be 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
}
}