swift cocoa

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.