【问题标题】:NSObject is Hashable but a protocol that adopts NSObject is not?NSObject 是 Hashable 但采用 NSObject 的协议不是?
【发布时间】:2014-09-16 06:59:33
【问题描述】:

在我提交雷达之前,请与社区进行一次健全性检查:

在 .h Obj-C 文件中:

@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 的协议。

我有错误还是遗漏了什么?

:) 特奥

其他信息:

documentation 建议:

  • 字典的键类型的唯一要求是它是 Hashable 并且它实现了==
  • 您确实可以使用协议。
字典键类型的哈希值

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

所有 Swift 的基本类型(例如 String、Int、Double 和 Bool)在默认情况下都是可散列的,并且所有这些类型都可以用作字典的键。默认情况下,没有关联值的枚举成员值(如枚举中所述)也是可散列的。

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

【问题讨论】:

    标签: objective-c swift protocols hashable


    【解决方案1】:

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

    它实际上不能从Hashable 继承,因为Hashable 需要一个名为hashValue 的方法,而NSObjectProtocol 需要一个名为hash 的方法。

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

    Equatable 也会出现同样的问题。

    编辑:

    还有一个更微妙的问题。您不能在需要Equatable 的地方使用协议,您始终需要使用采用Equatable 的类类型或值类型。原因是一个键采用Equatable是不够的,字典中的所有键必须彼此相等。

    例如,如果您有一个类A 和一个类B,两者都符合Equatable,那么您可以将A 的实例与A 的其他实例进行比较,并且您可以比较BB 的其他实例,但您无法将 A 的实例与 B 的实例进行比较。这就是为什么您不能将A 的实例和B 的实例用作同一字典中的键。

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

    【讨论】:

    • 感谢您的 cmets。我确实看到(如我的问题中所述) NSObjectProtocol 没有实现所需的 Hashable 方法,但我不遵循“更微妙的问题”的论点; equatable 部分只是比较哈希值,并不关心 A 和 B 是什么,只要它们采用协议即可。
    • 我看到的问题是,桥接无法将 Swift 的 Hashable hashValue 和 Equatable == 映射到 Objective-C NSObject 协议的 isEqual:hash。跨度>
    • @TeoSartori 以这种方式连接它们是不可能的。 Obj-C 无法表示 == 运算符,您不能只是将方法名称桥接到另一个方法名称。一个对象可以同时实现这两者,但你不能只是让它们相等,那将是一个危险的先例。无论如何,你不能对字典键使用协议类型,即使那是一个纯 Swift 协议。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      或者,您可以使用 NSObjectProtocol.hash 作为您的密钥。

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

      【讨论】:

      • 对象的哈希值不是唯一值。您可以简单地将其用作字典中的键。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多