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

NSObject subclass in Swift: hash vs hashValue, isEqual vs ==

在 Swift 中子类化 NSObject 时,您应该覆盖 hash 还是实现 Hashable

此外,您应该覆盖 isEqual: 还是实施 == 运算符?

实现 Hashable,这还需要您为您的类型实现 == 运算符。这些用于 Swift 标准库中的许多有用的东西,例如 indexOf 函数,它仅适用于实现 EquatableSet<T> 类型的类型的集合它仅适用于实现 Hashable.

的类型

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

我找不到官方参考资料,但似乎 hashValueNSObjectProtocol 调用 hash 方法,== 调用 isEqual: 方法(来自相同的协议)。 查看更新 回答完毕!

对于NSObject子classes,正确的做法好像是 覆盖 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
}

现在创建 class 的两个不同实例 “相等”并将它们放入集合中:

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

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

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
        }
    }
}

对于Swift3,定义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)

更新: 该行为记录在“Using Swift with Cocoa and Objective-C”一书中,在“Interacting with Objective-C API":

The default implementation of the == operator invokes the isEqual: method, and the default implementation of the === operator checks pointer equality. You should not override the equality or identity operators for types imported from Objective-C.

The base implementation of the isEqual: provided by the NSObject class is equivalent to an identity check by pointer equality. You can override isEqual: in a subclass to have Swift and Objective-C APIs determine equality based on the contents of objects rather than their identities.

可以在 Apple Book 应用程序中找到这本书。

它也记录在 Apple 的网站上,但已被删除,并且在页面的 WebArchive snapshot 上仍然可见。

对于 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
    }
}

“另外,你应该覆盖 isEqual: 还是实现 ==?”

你可以两者都做。如果您要实施 行为不同,您也会为代码用户的生活增添光彩。去过也做过。很好玩