Using Swift 3, I have some NSObject
subclasses that I am overriding the hash
property and isEqual()
functions for. (I want the classes to be able to be used as keys in a dictionary, and I want an array of them to be able to be sorted, but it doesn't really matter why I'm overriding them.)
Harkening back to my old C++/Java days, I recalled that a "proper" hash involved prime numbers and the hashes of the object's properties. These questions talk about this style. Something like this:
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
}
At least, that's what I thought. While running my code, I saw a very peculiar crash in my hash
override:
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
Looking here at StackOverflow, I saw a lot of these crashes being asked about, and the answer was usually that nil is being implicitly unwrapped, causing a crash. But there are no optionals in my hash. After playing around in lldb, I realized that the problem was integer overflow. If you do this in a playground, you'll see it causes an error:
`9485749857432985 * 39847239847239` // arithmetic operation '9485749857432985 * 39847239847239' (on type 'Int') results in an overflow
Well, I do a lot of addition and multiplication in my hash overrides. (It's hard to see in a playground, but in lldb it was obvious that overflow was causing my crash.) Reading about Swift crashes due to Int overflow, I found that you can use &*
and &+
to prevent overflow. I'm not sure how well the hashes work, but this wouldn't crash, for example:
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
}
Here's my question: what is the "proper" way to write this sort of hash
override, without the potential for overflow, and in a way that actually provides good hashing?
Here is an example you can pop into a playground to try out. I think this would definitely lead to the EXC_BAD_INSTRUCTION for anybody:
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"
Bonus question: is there a proper way to handle making the hash
value, when a property on your class uses hashValue
instead? I've been using them pretty interchangeably but I'm not sure if that's correct.