Swift中的NSObject子类:hash vs hashValue,isEqual vs ==

时间:2015-10-24 15:37:56

标签: swift cocoa

在Swift中继承NSObject时,你应该覆盖哈希还是实现Hashable?另外,你应该覆盖isEqual:或者实现==?

3 个答案:

答案 0 :(得分:86)

NSObject已符合Hashable协议:

extension NSObject : Equatable, Hashable {
    /// The hash value.
    ///
    /// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
    ///
    /// - Note: the hash value is not guaranteed to be stable across
    ///   different invocations of the same program.  Do not persist the
    ///   hash value across program runs.
    public var hashValue: Int { get }
}

public func ==(lhs: NSObject, rhs: NSObject) -> Bool

我找不到官方参考资料,但似乎hashValuehash调用NSObjectProtocol方法,==调用 isEqual:方法(来自同一协议)。 请参阅更新 答案结束了!

对于NSObject子类,似乎是正确的方法 覆盖hashisEqual:,这是一个实验 证明:

1。覆盖hashValue==

class ClassA : NSObject {
    let value : Int

    init(value : Int) {
        self.value = value
        super.init()
    }

    override var hashValue : Int {
        return value
    }
}

func ==(lhs: ClassA, rhs: ClassA) -> Bool {
    return lhs.value == rhs.value
}

现在创建两个不同的类实例 "等于"并将它们放入一套:

let a1 = ClassA(value: 13)
let a2 = ClassA(value: 13)

let nsSetA = NSSet(objects: a1, a2)
let swSetA = Set([a1, a2])

print(nsSetA.count) // 2
print(swSetA.count) // 2

如您所见,NSSetSet都将对象视为不同。 这不是理想的结果。阵列也有意想不到的结果:

let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]

print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil

设置断点或添加调试输出显示被覆盖 永远不会调用==运算符。我不知道这是一个bug还是 预期的行为。

2。覆盖hashisEqual:

class ClassB : NSObject {
    let value : Int

    init(value : Int) {
        self.value = value
        super.init()
    }

    override var hash : Int {
        return value
    }

    override func isEqual(object: AnyObject?) -> Bool {
        if let other = object as? ClassB {
            return self.value == other.value
        } else {
            return false
        }
    }
}

对于 Swift 3,isEqual:的定义已更改为

override func isEqual(_ object: Any?) -> Bool { ... }

现在所有结果都符合预期:

let b1 = ClassB(value: 13)
let b2 = ClassB(value: 13)

let nsSetB = NSSet(objects: b1, b2)
let swSetB = Set([b1, b2])

print(swSetB.count) // 1
print(nsSetB.count) // 1

let nsArrayB = NSArray(object: b1)
let swArrayB = [b1]

print(nsArrayB.indexOfObject(b2)) // 0
print(swArrayB.indexOf(b2)) // Optional(0)

更新:现在记录了此行为 Interacting with Objective-C APIs 在"使用Swift与Cocoa和Objective-C"参考:

  

NSObject类只执行身份比较,因此您应该在从NSObject类派生的类中实现自己的isEqual:方法。

     

作为实现类的相等性的一部分,请确保根据Object比较中的规则实现hash属性。

答案 1 :(得分:1)

对于 NSObject,最好覆盖 hashisEqual。它已经符合 HashableEquatable,并且已经合成了依次调用 hashisEqual 的一致性。因此,由于它是一个 NSObject,因此请使用 ObjC 方式并覆盖也会影响 ObjC 哈希值和相等性的值。

class Identity: NSObject {

    let name: String
    let email: String

    init(name: String, email: String) {
        self.name = name
        self.email = email
    }

    override var hash: Int {
        var hasher = Hasher()
        hasher.combine(name)
        hasher.combine(email)
        return hasher.finalize()
    }

    override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? Identity else {
            return false
        }
        return name == other.name && email == other.email
    }
}

答案 2 :(得分:0)

实施Hashable,这也要求您为您的类型实施==运算符。这些用于Swift标准库中的许多有用的东西,例如indexOf函数,它只适用于实现Equatable的类型的集合,或仅适用于Set<T>类型的Hashable类型实现=query(A2:D20, "select A, MAX(C) GROUP BY A pivot B",1) 的类型。