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

标签 swift cocoa nsobject

在 Swift 中子类化 NSObject 时,应该重写 hash 还是实现 Hashable

此外,您应该重写 isEqual: 还是实现 == 运算符?

最佳答案

NSObject 已经符合 Hashable 协议(protocol):

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: 方法(来自同一协议(protocol))。 查看更新 答案结束!

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

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

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)
<小时/>

更新:该行为记录在《使用 Swift 与 Cocoa 和 Objective-C》一书中的“与 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 上仍然可见。页面的。

关于swift - Swift 中的 NSObject 子类 : hash vs hashValue, isEqual 与 ==,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45874901/

相关文章:

ios - UIKit<-segue->SpriteKit 内存泄漏

swift - 从框架加载 Swift 主 Storyboard和 View Controller

swift - KVC 和关联类型

cocoa - SQLite 核心数据存储保存在 Lion 中的哪里?

ios - 如何使用单独的 UITableViewDataSource 类管理#selector?

swift - 如何使 firebase 数据库数据成为 UICollectionView 的数据源?

swift - 根据该标题内的 Collection View 动态内容显示 collectionView 的标题大小不起作用

cocoa - NSString 初始化

objective-c - 有没有办法替换默认的 X11 菜单栏?

objective-c - Apple 是否提供 isEqual : 的默认实现