Swift: переопределение хэша NSObject без сбоя переполнения
Используя Swift 3, у меня есть некоторые NSObject
подклассы, которые я перекрываю hash
собственность и isEqual()
функции для. (Я хочу, чтобы классы могли использоваться в качестве ключей в словаре, и я хочу, чтобы их массив мог быть отсортирован, но на самом деле не имеет значения, почему я переопределяю их.)
Возвращаясь к моим старым дням в C++/Java, я вспомнил, что "правильный" хэш включает простые числа и хэши свойств объекта. Эти вопросы говорят об этом стиле. Что-то вроде этого:
override public var hash: Int {
var hash = 1
hash = hash * 17 + label.hash
hash = hash * 31 + number.hash
hash = hash * 13 + (ext?.hash ?? 0)
return hash
}
По крайней мере, так я и думал. Во время работы моего кода я увидел очень специфический сбой в моем hash
переопределять:
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
Посмотрев здесь на Stackru, я увидел множество этих сбоев, о которых спрашивали, и обычно ответом было то, что nil неявно разворачивается, вызывая сбой. Но в моем хэше нет опций. Поиграв в lldb, я понял, что проблема в переполнении целых чисел. Если вы сделаете это на детской площадке, вы увидите, что это вызывает ошибку:
`9485749857432985 * 39847239847239` // arithmetic operation '9485749857432985 * 39847239847239' (on type 'Int') results in an overflow
Ну, я делаю много сложений и умножений в своих переопределениях хэшей. (Трудно увидеть на детской площадке, но в lldb было очевидно, что переполнение стало причиной моего сбоя.) Читая о сбоях Swift из-за переполнения Int, я обнаружил, что вы можете использовать &*
а также &+
предотвратить переполнение. Я не уверен, насколько хорошо работают хэши, но это не сработает, например:
override public var hash: Int {
var hash = 1
hash = hash &* 17 &+ label.hash
hash = hash &* 31 &+ number.hash
hash = hash &* 13 &+ (ext?.hash ?? 0)
return hash
}
Вот мой вопрос: каков "правильный" способ написания такого рода hash
переопределить, без возможности переполнения, и таким образом, который действительно обеспечивает хорошее хеширование?
Вот пример, который вы можете попробовать на игровой площадке. Я думаю, что это определенно приведет к EXC_BAD_INSTRUCTION для всех:
class DateClass: NSObject {
let date1: Date
let date2: Date
let date3: Date
init(date1: Date, date2: Date, date3: Date) {
self.date1 = date1
self.date2 = date2
self.date3 = date3
}
override var hash: Int {
var hash = 1
hash = hash + 17 + date1.hashValue
hash = hash + 31 + date2.hashValue
hash = hash + 13 + date3.hashValue
return hash
}
override public func isEqual(_ object: Any?) -> Bool {
guard let rhs = object as? DateClass else {
return false
}
let lhs = self
return lhs.date1 == rhs.date1 &&
lhs.date2 == rhs.date2 &&
lhs.date3 == rhs.date3
}
}
let dateA = Date()
let dateB = Date().addingTimeInterval(10)
let dateC = Date().addingTimeInterval(20)
let dateD = Date().addingTimeInterval(30)
let dateE = Date().addingTimeInterval(40)
let class1 = DateClass(date1: dateA, date2: dateB, date3: dateC)
let class2 = DateClass(date1: dateB, date2: dateC, date3: dateD)
let class3 = DateClass(date1: dateC, date2: dateD, date3: dateE)
var dict = [DateClass: String]()
dict[class1] = "one"
dict[class2] = "two"
dict[class3] = "three"
Бонусный вопрос: есть ли правильный способ справиться с hash
значение, когда свойство вашего класса использует hashValue
вместо? Я использовал их довольно взаимозаменяемо, но я не уверен, что это правильно.
1 ответ
hashValue
(или же hash
) действительно может быть что угодно. Пока два объекта, которые возвращаются true
за isEqual
также имеют такое же значение хеш-функции.
Вам не нужно беспокоиться о том, чтобы придумать какую-то магическую ценность, основанную на всех свойствах.
Ваш хеш может просто вернуть хеш одного свойства. Это позволит избежать любого переполнения. Или вы могли бы немного поработать со значениями хеша нескольких свойств. Некоторая комбинация "или", "и" и "xor".
Что касается вашего бонусного вопроса, нет проблем с вызовом hashValue
на некоторых типах данных Swift при расчете результата NSObject hash
метод. Оба возвращаются Int
,