Subclase NSObject en Swift: hash vs hashValue, isEqual vs==
cocoa (2)
Al subclasificar NSObject en Swift, ¿debería anular el hash o implementar Hashable? Además, ¿debería anular isEqual: o implementar ==?
Implemente
Hashable
, que también requiere que implemente el operador
==
para su tipo.
Se utilizan para muchas cosas útiles en la biblioteca estándar de Swift, como la función
indexOf
, que solo funciona en colecciones de un tipo que implementa
Equatable
, o el tipo
Set<T>
que solo funciona con tipos que implementan
Hashable
.
NSObject
ya se ajusta al protocolo
Hashable
:
extension NSObject : Equatable, Hashable {
/// The hash value.
///
/// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
///
/// - Note: the hash value is not guaranteed to be stable across
/// different invocations of the same program. Do not persist the
/// hash value across program runs.
public var hashValue: Int { get }
}
public func ==(lhs: NSObject, rhs: NSObject) -> Bool
No pude encontrar una referencia oficial, pero parece que
hashValue
llama al método
hash
desde
NSObjectProtocol
, y
==
llama al método
isEqual:
(desde el mismo protocolo).
¡Vea la actualización al final de la respuesta!
Para
NSObject
subclases de
NSObject
, la forma correcta parece ser anular
hash
e
isEqual:
y aquí hay un experimento que demuestra que:
1. Anular
hashValue
y
==
class ClassA : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hashValue : Int {
return value
}
}
func ==(lhs: ClassA, rhs: ClassA) -> Bool {
return lhs.value == rhs.value
}
Ahora cree dos instancias diferentes de la clase que se consideran "iguales" y colóquelas en un conjunto:
let a1 = ClassA(value: 13)
let a2 = ClassA(value: 13)
let nsSetA = NSSet(objects: a1, a2)
let swSetA = Set([a1, a2])
print(nsSetA.count) // 2
print(swSetA.count) // 2
Como puede ver, tanto
NSSet
como
Set
tratan los objetos como diferentes.
Este no es el resultado deseado.
Las matrices también tienen resultados inesperados:
let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]
print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil
Establecer puntos de interrupción o agregar resultados de depuración revela que el operador
==
anulado nunca se llama.
No sé si esto es un error o un comportamiento previsto.
2. Anular
hash
e
isEqual:
class ClassB : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hash : Int {
return value
}
override func isEqual(object: AnyObject?) -> Bool {
if let other = object as? ClassB {
return self.value == other.value
} else {
return false
}
}
}
Para
Swift 3,
la definición de
isEqual:
cambiada a
override func isEqual(_ object: Any?) -> Bool { ... }
Ahora todos los resultados son los esperados:
let b1 = ClassB(value: 13)
let b2 = ClassB(value: 13)
let nsSetB = NSSet(objects: b1, b2)
let swSetB = Set([b1, b2])
print(swSetB.count) // 1
print(nsSetB.count) // 1
let nsArrayB = NSArray(object: b1)
let swArrayB = [b1]
print(nsArrayB.indexOfObject(b2)) // 0
print(swArrayB.indexOf(b2)) // Optional(0)
Actualización: El comportamiento ahora está documentado en Interactuar con las API de Objective-C en la referencia "Uso de Swift con Cocoa y Objective-C":
La clase NSObject solo realiza una comparación de identidad, por lo que debe implementar su propio método isEqual: en clases que se derivan de la clase NSObject.
Como parte de la implementación de la igualdad para su clase, asegúrese de implementar la propiedad hash de acuerdo con las reglas en la comparación de objetos.