【问题标题】:Swift: Overriding NSObject hash Without Overflow CrashSwift:覆盖 NSObject 哈希而不溢出崩溃
【发布时间】:2016-11-09 03:08:40
【问题描述】:

使用 Swift 3,我有一些 NSObject 子类,我覆盖了 hash 属性和 isEqual() 函数。 (我希望这些类能够用作字典中的键,并且我希望它们的数组能够被排序,但我为什么要覆盖它们并不重要。)

回到我以前的 C++/Java 时代,我回忆起“正确的”散列涉及素数和对象属性的散列。 These questions 说说这种风格。像这样的:

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,我看到很多此类崩溃被问及,答案通常是 nil 被隐式解包,导致崩溃。但是我的哈希中没有选项。在 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"

额外问题:当你的类上的属性使用 hashValue 时,是否有适当的方法来处理 hash 值?我一直在使用它们,但我不确定这是否正确。

【问题讨论】:

    标签: swift hash integer-overflow


    【解决方案1】:

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

    您真的不必担心会根据所有属性得出一些神奇的值。

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

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

    【讨论】:

    • 可以是任何东西,只要isEqual 为真,哈希值相同。对于我上面的DateClass,您如何做到这一点,它在执行isEqual 时考虑了所有属性,而不考虑哈希中的所有属性?
    • 您的 isEqual 方法可以按原样使用。鉴于此,您的 hash 可以为每个对象返回 1 并且满足该条件(实际上不要这样做,因为它会出现性能问题)。请记住,条件是相等的对象具有相同的散列。相反不一定是真的。拥有相同哈希但不相等的对象是完全可以的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-17
    • 1970-01-01
    • 1970-01-01
    • 2017-04-27
    • 2015-11-22
    • 2017-05-02
    相关资源
    最近更新 更多