2016-11-09 53 views
0

使用Swift 3,我有一些NSObject的子类,我重写了hash属性和isEqual()函数。 (我希望这些类可以用作字典中的键,并且我希望能够对它们进行排序,但是为什么我会覆盖它们并不重要)。Swift:覆盖NSObject散列没有溢出崩溃

回想我的旧C++/Java时代,我回忆说,一个“正确”散列涉及素数和对象属性的哈希。 Thesequestions谈论这种风格。就像这样:

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)

看这里的StackOverflow,我看到被问了很多这些崩溃有关,答案通常是零正在隐含地解开,导致崩溃。但是我的散列中没有可选项。在玩过lldb后,我意识到问题是整数溢出。如果你这样做的操场,你会看到它导致错误:

`9485749857432985 * 39847239847239` // arithmetic operation '9485749857432985 * 39847239847239' (on type 'Int') results in an overflow 

嗯,我在我的散列覆盖做了很多加法和乘法。 (很难在操场上看到,但在lldb中很明显溢出导致了我的崩溃。)关于Swift crashes due to Int overflow,我发现可以使用&*&+来防止溢出。我不知道该散列如何工作,但这不会崩溃,例如:

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呢?我一直在相互交换使用它们,但我不确定这是否正确。

回答

0

hashValue(或hash)确实可以是任何东西。只要为isEqual返回true的两个对象也具有相同的散列值。

你根本不需要担心基于所有属性提出一些魔法值。

您的散列可以简单地返回单个属性的散列。这将避免任何溢出。或者你可以对多个属性的哈希值进行一些操作。 “或”,“和”和“异或”的某种组合。

至于你的奖金问题,在计算NSObject hash方法的结果时,在某些Swift数据类型上调用hashValue没有问题。两者都返回Int

+0

只要'isEqual'为true,哈希值就是相同的。你如何为我上面的'DateClass'做到这一点,在执行'isEqual'时考虑到所有属性,而不考虑散列中的所有属性? – nickjwallin

+0

你的'isEqual'方法很好。鉴于此,您的'hash'可能会为每个对象返回'1',并且符合条件(实际上不会这样做,因为它会产生性能问题)。请记住,条件是相同的对象具有相同的散列。相反的情况并不一定是真的。具有相同散列的对象并不相同,这是完全正确的。 – rmaddy