We have multiple instances of a custom Swift class, which inherits from SKSpriteNode, and were able to execute the following code (grossly simplified for this question) correctly:
let instance1 = CustomClass()
let instance2 = CustomClass()
let instance3 = CustomClass()
let instance4 = CustomClass()
let array1 = [instance1, instance2]
let array2 = [instance3, instance4]
func check(testInstance: CustomClass) -> Bool {
return array1.filter({ $0 == testInstance }).count > 0
}
check(testInstance: instance3)
In other words, executing check(testInstance: instance3)
returned false
as expected.
However, we made a bunch of changes, and check
stopped working.
CustomClass
does not implement the Equatable
protocol. We just want to detect unique instances.
It only started working when we used ObjectIdentifier
, meaning the function changed to this:
func check(testInstance: CustomClass) -> Bool {
return array1.filter({ ObjectIdentifier($0) == ObjectIdentifier(testInstance) }).count > 0
}
Why is ObjectIdentifier
needed, and when should it be used for object equality?
This was written with Swift 3.
Why is ObjectIdentifier
needed, and when should it be used for object equality?
You don't need to use ObjectIdentifier
in order to perform an identity comparison in this case, you can simply use the identity operator ===
instead which, as Martin says here, for class instances is equivalent to using ObjectIdentifier
's ==
overload:
func check(testInstance: CustomClass) -> Bool {
return array1.contains(where: { $0 === testInstance })
}
Also note we're using contains(where:)
over filter{...}.count > 0
, as the former will short-circuit upon finding a matching element, whereas the latter evaluates the entire sequence (and creates an unnecessary intermediate array).
The direct use of ==
to perform an identity comparison of objects may have worked due to the fact that CustomClass
ultimately inherits from NSObject
, which conforms to Equatable
by defining an ==
overload that calls isEqual(_:)
, which by default performs an identity comparison.
However in general, this should not be relied upon – the implementation of isEqual(_:)
can be overridden to perform a comparison based on property values rather than identity. Furthermore, semantically Equatable
requires that the implementation of ==
is based all on visible aspects (i.e property values) of the instances being compared.
From the documentation:
Equality implies substitutability — any two instances that compare
equally can be used interchangeably in any code that depends on their
values. To maintain substitutability, the ==
operator should take into
account all visible aspects of an Equatable
type.
Therefore using ==
for an identity comparison on your class was never correct, even though it may have worked initially.
As for when ObjectIdentifier
should be used, really you should never need it just to perform an identity comparison. For classes, you should use the ===
operator, and for metatypes, you should simply use the ==
overload defined specifically for them (as in that case, identity just happens to equality, as each new metatype instance is unique).
The main use of ObjectIdentifier
is really its hashValue
implementation, which is derived from the pointer value of the thing that it's initialised with. This can be useful, for example, in allowing metatypes to be Dictionary
keys (compare Make a Swift dictionary where the key is "Type"?).