【问题标题】:NSObject subclass in Swift: hash vs hashValue, isEqual vs ==Swift 中的 NSObject 子类:hash vs hashValue,isEqual vs ==
【发布时间】:2015-10-24 15:37:56
【问题描述】:

在 Swift 中继承 NSObject 时,应该覆盖 hash 还是实现 Hashable

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

【问题讨论】:

    标签: swift cocoa nsobject


    【解决方案1】:

    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 子类,正确的方法似乎是 覆盖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)
    

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

    == 运算符的默认实现调用isEqual: 方法,=== 运算符的默认实现检查指针是否相等。对于从 Objective-C 导入的类型,您不应覆盖相等或恒等运算符。

    NSObject 类提供的isEqual: 的基本实现等价于指针相等的身份检查。您可以在子类中覆盖 isEqual:,让 Swift 和 Objective-C API 根据对象的内容而不是它们的身份来确定相等性。

    这本书在 Apple Book 应用中可用。

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

    【讨论】:

    • 谢谢!这是极好的。惊讶于覆盖甚至不适用于 Swift 数据结构。
    • 您不必覆盖-hash,而只需覆盖-isEqual:。显然,对于所有可变类,-hash 没有很好的实现。唯一可能的实现是返回一个常量(即类对象的地址或一个幻数。)此外,在集合中具有可变类是不可能满足使用集合的期望(没有两个实例是相等的)。这是核心数据中公平和身份一致的原因之一。
    • 这是正确的答案,对于任何实用的 Swift 程序员来说都是有用的信息。
    • @AminNegm-Awad 至少在 Swift 2.2 中,您确实需要同时覆盖 hash-isEqual:
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 2014-02-27
          • 2010-11-17
          • 2019-09-05
          • 1970-01-01
          • 2013-12-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-09-29
          相关资源
          最近更新 更多