При подклассификации NSObject в Swift следует переопределить хэш или реализовать Hashable? Кроме того, следует ли переопределить isEqual: или реализовать ==?
Подкласс NSObject в Swift: hash vs hashValue, isEqual vs ==
Ответ 1
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 с Cocoa и Objective-C":
Класс NSObject выполняет сравнение идентичности, поэтому вы должны реализовать свой собственный метод isEqual: в классах, которые происходят из класса NSObject.
Как часть реализации равенства для вашего класса, обязательно реализуйте свойство хэша в соответствии с правилами сравнения объектов.
Ответ 2
Внедрите Hashable
, который также требует, чтобы вы выполняли оператор ==
для вашего типа. Они используются для большого количества полезных материалов в стандартной библиотеке Swift, такой как функция indexOf
, которая работает только с коллекциями типа, который реализует Equatable
или тип Set<T>
, который работает только с типами, которые реализуют Hashable
.