NSObject是可哈希的,但采用NSObject的协议却不是?

7

在我提交radar之前,想向社区进行一次合理性检查:

在一个Obj-C的.h文件中:

@protocol myProto <NSObject> 
@end

在一个.swift文件中(通过桥接头文件访问上述协议定义):
class myClass {
    // This line compiles fine
    var dictOne: [NSObject:Int]?
    // This line fails with "Type 'myProto' does not conform to protocol 'Hashable'"
    var dictTwo: [myProto:Int]?
}

检查NSObject类后发现它(或它映射到的NSObjectProtocol)没有实现Hashable协议所需的hashValue方法,也没有明确采用它。
因此,在幕后的某个地方,尽管如此,NSObject被标记为Hashable,但不扩展采用NSObject / NSObjectProtocol的协议。
我有Bug还是漏掉了什么?
:) Teo
附加信息: 文档建议:
字典键类型的唯一要求是它是可哈希的,并且它实现了“==”。 您确实可以使用协议。
字典键类型的哈希值

一个类型必须是可哈希的才能用作字典的键类型,也就是说,该类型必须提供一种计算自身哈希值的方法。哈希值是一个Int值,对于所有比较相等的对象都是相同的,因此如果a == b,则a.hashValue == b.hashValue。

所有Swift的基本类型(如String、Int、Double和Bool)默认都是可哈希的,这些类型都可以用作字典的键。枚举成员值没有关联值(如枚举中所述)也默认是可哈希的。

注意 您可以通过使它们符合Swift标准库中的Hashable协议来将自定义类型用作字典键类型。符合Hashable协议的类型必须提供一个可获取的名为hashValue的Int属性,并且还必须提供“相等”运算符(==)的实现。由类型的hashValue属性返回的值不需要在同一程序的不同执行或不同程序中相同。 有关符合协议的更多信息,请参见协议。

3个回答

11

NSObjectProtocol 不继承自 Hashable,这是关键问题。

NSObjectProtocol 无法继承 Hashable,因为 Hashable 要求一个名为 hashValue 的方法,而 NSObjectProtocol 要求一个名为 hash 的方法。

另一方面,NSObject 类可以同时实现 NSObjectProtocolHashable

Equatable 也存在同样的问题。

编辑:

还有一个更微妙的问题。您不能在期望 Equatable 的地方使用协议,您总是需要使用采用了 Equatable 的类类型或值类型。原因是仅使键符合 Equatable 是不够的,字典中的所有键都必须彼此可比。

例如,如果您有一个采用 Equatable 的类 A 和一个采用 Equatable 的类 B,那么您可以将 A 实例与其他 A 实例进行比较,并且可以将 B 实例与其他 B 实例进行比较,但是您不能将 A 实例与 B 实例进行比较。这就是为什么您不能在同一字典中使用 A 实例和 B 实例作为键的原因。

请注意,每个 NSObject 都可以与任何其他 NSObject 相等,因此 NSObject 是字典键的允许类型。


感谢您的评论。我确实看到(如我在问题中提到的),NSObjectProtocol没有实现所需的Hashable方法,但我不明白“更微妙的问题”论点;相等部分只是比较哈希值,并不关心A和B是什么,只要它们采用了该协议。 - Teo Sartori
我认为问题在于桥接无法将Swift的Hashable hashValue和Equatable ==映射到Objective-C NSObject协议的isEqual:hash - Teo Sartori
@TeoSartori 这种方式是不可能的。Obj-C没有办法表示==运算符,你也不能将一个方法名桥接到另一个方法名。一个对象可以实现两个方法,但你不能让它们相等,那将是一个危险的先例。无论如何,即使是纯Swift协议,你也不能将其用作字典键的类型。 - Sulthan

0

我同意这似乎是一个缺失的功能。对于任何有兴趣的人,我做了一个小包装来处理它。

struct HashableNSObject<T: NSObjectProtocol>: Hashable {
    let value: T

    init(_ value: T) {
        self.value = value
    }

    static func == (lhs: HashableNSObject<T>, rhs: HashableNSObject<T>) -> Bool {
        return lhs.value.isEqual(rhs.value)
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(value.hash)
    }
}

你甚至可以通过将T替换为NSObjectProtocol来使其非泛型化,但我认为这样更清晰。

使用起来相当容易,但在不断映射包含的值时有点冗长。

let foo = [MyObjectProtocol]()
let bar = Set<HashableNSObject<MyObjectProtocol>>()
fun1(foo.map { HashableNSObject($0) })
fun2(bar.map { $0.value })

0

或者,您可以使用 NSObjectProtocol.hash 作为键。

var dictTwo: [Int:Int]?
dictTwo[myProtoInstance.hash] = 0

对象的哈希值不是唯一的。你可以简单地将其用作字典中的键。 - Andriy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接