Подкласс 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
,