2016-11-09 52 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