NSObject subclass in Swift: hash vs hashValue, isEqual vs ==

lorsque vous sous-classez NSObject dans Swift, devez-vous remplacer hash ou implémenter Hashable? En outre, devez-vous outrepasser isEqual: ou implement ==?

30
demandé sur Martin R 2015-10-24 18:37:56

2 réponses

NSObject est déjà conforme au protocole 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

Je n'ai pas pu trouver de référence officielle, mais il semble que hashValue appelle la méthode hash de NSObjectProtocol , et == appelle la Méthode isEqual: (du même protocole). voir Mise à jour au fin de la réponse!

pour les sous-classes NSObject , la bonne façon semble être remplacer hash et isEqual: , et voici une expérience qui démontre que:

1. Remplacer hashValue et ==

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
}

maintenant créer deux instances différentes de la classe qui sont considérées "égaler" et les mettre dans un ensemble:

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

comme vous pouvez le voir, les deux NSSet et Set traitent les objets comme différents. Ce n'est pas le résultat souhaité. Les tableaux ont des résultats inattendus ainsi:

let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]

print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil

le réglage des points de rupture ou l'ajout de la sortie de débogage révèle que le dépassement L'opérateur == n'est jamais appelé. Je ne sais pas si c'est un bug ou d'une comportement voulu.

2. Remplacer hash et 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
        }
    }
}

pour Swift 3, la définition de isEqual: changée en

override func isEqual(_ object: Any?) -> Bool { ... }

Maintenant, tous les résultats sont comme prévu:

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)

mise à jour: le comportement est maintenant documenté dans interaction avec les APIs objectifs dans la" Utilisation de Swift avec le cacao et L'objectif-C "référence:

la classe NSObject effectue uniquement une comparaison d'identité, vous devez donc implémenter votre propre méthode isEqual: dans les classes qui dérivent de la classe NSObject.

Dans le cadre de la mise en œuvre de l'égalité pour votre classe, assurez-vous de mettre en œuvre la propriété hash selon les règles dans la comparaison D'objet.

75
répondu Martin R 2016-11-03 08:01:23

implémenter Hashable , qui vous oblige également à implémenter l'opérateur == pour votre type. Ceux-ci sont utilisés pour beaucoup de choses utiles dans la bibliothèque standard de Swift comme la fonction indexOf qui ne fonctionne que sur les collections d'un type qui implémente Equatable , ou le type Set<T> qui ne fonctionne qu'avec des types qui implémentent Hashable .

0
répondu Kametrixom 2015-10-24 16:11:40