Подкласс NSObject в Swift: hash vs hashValue, isEqual vs ==

При создании подкласса NSObject в Swift, вы должны переопределить хэш или реализовать Hashable? Кроме того, вы должны переопределить isEqual: или реализовать ==?

4 ответа

Решение

NSObject уже соответствует 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

Я не мог найти официальную ссылку, но кажется, что hashValue вызывает hash метод из NSObjectProtocol, а также == вызывает isEqual: метод (из того же протокола). Смотрите обновление в конце ответа!

За NSObject подклассы, правильный способ, кажется, переопределить hash а также isEqual: и вот эксперимент, который демонстрирует, что:

1. Переопределить hashValue а также ==

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
}

Теперь создайте два разных экземпляра класса, которые считаются "равными", и поместите их в набор:

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

Как видите, оба NSSet а также Set относиться к объектам как к разным. Это не желаемый результат. Массивы также имеют неожиданные результаты:

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

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

Установка точек останова или добавление результатов отладки показывает, что переопределенный == оператор никогда не вызывается. Я не знаю, является ли это ошибкой или предполагаемым поведением.

2. Переопределить hash а также 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
        }
    }
}

Для Swift 3, определение isEqual: изменился на

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

Теперь все результаты соответствуют ожиданиям:

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)

Обновление: теперь поведение описано в разделе Взаимодействие с API-интерфейсами Objective C в разделе "Использование Swift с какао и Objective-C":

Класс NSObject выполняет только сравнение идентификаторов, поэтому вы должны реализовать свой собственный метод isEqual: в классах, производных от класса NSObject.

Как часть реализации равенства для вашего класса, убедитесь, что реализовали свойство hash в соответствии с правилами сравнения объектов.

Ибо лучше всего переопределить и. Он уже соответствует Hashable а также Equatable и синтезировал соответствия для того, что, в свою очередь, вызывает hash а также isEqual. Так как это NSObject, сделайте это способом ObjC и переопределите значения, которые также влияют на хэш-значение ObjC и равенство.

      class Identity: NSObject {

    let name: String
    let email: String

    init(name: String, email: String) {
        self.name = name
        self.email = email
    }

    override var hash: Int {
        var hasher = Hasher()
        hasher.combine(name)
        hasher.combine(email)
        return hasher.finalize()
    }

    override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? Identity else {
            return false
        }
        return name == other.name && email == other.email
    }
}

«Кроме того, следует ли вам переопределить isEqual: или реализовать ==?»

Вы могли сделать и то, и другое. И в случае, если вы хотите заставить реализации вести себя по-другому, вы также добавите изюминку в жизнь пользователей вашего кода. Был там, сделал это. Это весело.

Воплощать в жизнь Hashable, что также требует от вас реализации == Оператор для вашего типа. Они используются для многих полезных вещей в стандартной библиотеке Swift, таких как indexOf функция, которая работает только для коллекций типа, который реализует Equatable, или Set<T> тип, который работает только с типами, которые реализуют Hashable,

Другие вопросы по тегам