我觉得 Swift 编译器给我的信息很复杂。
我不明白这是什么问题。
这就是我所拥有的,我希望这些枚举成为字典的键:
protocol ArbitraryKey : CaseIterable, Hashable {}
enum HearNoEvil : ArbitraryKey { case a, b, c }
enum SeeNoEvil : ArbitraryKey { case d, e, f }
enum SpeakNoEvil : ArbitraryKey { case g, h, i }
这就是我想要避免:
var dictionary : [ AnyHashable : String ] = [:]
这就是我想做的:
var dictionary : [ any ArbitraryKey : String ] = [:]
协议
ArbitraryKey
最终会有我希望能够在枚举值上调用的方法。
然而,iOS 16.4 / Swift 5.7+ 编译器反对我尝试过的一切:
如果我这样做:
protocol ArbitraryKey : CaseIterable, Hashable {}
var dictonary : [ArbitraryKey : String] = [:]
类型 'any ArbitraryKey' 不能符合 'Hashable' 使用协议“ArbitraryKey”作为类型必须写成“any ArbitraryKey” 将 'ArbitraryKey' 替换为 'any ArbitraryKey'
所以我“修复”它:
protocol ArbitraryKey : CaseIterable, Hashable {}
var dictonary : [any ArbitraryKey : String] = [:] // <-- added 'any'
类型 'any ArbitraryKey' 不能符合 'Hashable'
所以我“修复”了:
protocol ArbitraryKey : CaseIterable {} // <-- removed Hashable
var dictonary : [any ArbitraryKey : String] = [:]
类型 'any ArbitraryKey' 不符合协议 'Hashable'
总结:
在这一点上,笨蛋被难住了。
有人可以解释发生了什么吗?
对干净直接的不同方法有什么想法吗?
您在标题中提出的基本问题是编译错误“必须符合 Hashable”和“不能符合 Hashable”如何兼容。它们不仅兼容;他们是一样的。
正如我在评论中向您解释的那样,当您将字典声明为
[Foo: Bar]
类型时,您正在解析 Key 和 Value 通用占位符。 只有具体类型的名称才能做到这一点。 对于 Key,您输入的类型名称必须另外是采用 Hashable 的具体类型的名称。
好吧,协议不是那样的。它不是具体类型,也不能符合 Hashable(协议不能符合协议)。事实上,这正是 Swift 5.8 强制你说
any
的原因,这样你才能面对事实。当编译器让你说 any MyProtocol
时,它试图让你明白这是一个 existential,而不是具体类型。
无论如何,你的标题中提出的问题的答案是编译器非常清楚地说,“这里必须是符合 Hashable 的类型,并且协议存在cannot符合 Hashable 所以它不能到这里。 “
关于您的评论:
我想用具体的几种枚举中的任何一种来索引该字典
好吧,在我看来,这似乎是一件愚蠢的事情,但关键是,如果你这样做,你将 not 能够通过对出于我刚刚演示的原因,你的字典的键类型。您需要执行某种类型的擦除。
例如,您可以从字典中隐藏一个事实,即它的所有键枚举都符合某个协议,方法是让这个事实成为you知道的东西,而不是试图让字典知道它。为此,您可以将字典包装在一个守卫门的类中,以便唯一可接受的键是符合您的协议的类型。像这样:
protocol ArbitraryKey : CaseIterable, Hashable {}
enum HearNoEvil : ArbitraryKey { case a, b, c }
enum SeeNoEvil : ArbitraryKey { case d, e, f }
enum SpeakNoEvil : ArbitraryKey { case g, h, i }
class DictionaryWrapper {
var dict = [AnyHashable: String]()
func setKey<T: ArbitraryKey>(_ key: T, toValue value: String) {
dict[key] = value
}
func getValueForKey<T: ArbitraryKey>(_ key: T) -> String? {
return dict[key]
}
}
这可能看起来很愚蠢,确实如此;这只是一个例子。但它确实非常简单地完成了您(出于只有您知道的原因)要求做的事情:我们已经保证给我们代码的调用者(例如
setKey
和getValueForKey
这个键是一个类型符合 ArbitraryKey 协议。所以你可以继续为我们的 DictionaryWrapper 编写方法,直到你完成你的最终目标。
[Extra Point:] 我认为这里的部分混淆在于对
protocol ArbitraryKey : Hashable {}
等表达方式的含义的把握。你在评论中问了这个问题,让我觉得你认为这意味着 ArbitraryKey 符合 Hashable。它没有。协议不符合协议。该代码正在谈论将符合 this 协议(ArbitraryKey)的 concrete 类型。它说:“为了符合我的要求,你必须also(除了我可能施加的任何要求之外)符合 Hashable。”它不是关于协议做什么的声明,而是关于符合类型必须做什么才能符合的声明。
如果目标是制作一个字典,其中的键仅限于符合特定协议的类型(您的示例中的枚举),我可以考虑为该协议创建一个特定的字典。
protocol P1: Hashable {}
struct P1KeyDict<Value> {
private var items: [AnyHashable: Value] = [:]
subscript<K:P1>(key: K) -> Value {
get {
return items[key as AnyHashable]!
}
set {
items[key as AnyHashable] = newValue
}
}
}
使用方法如下:
var dict: P1KeyDict<String> = P1KeyDict()
enum E1: P1 { case a, b, c }
enum E2: P1 { case d, e, f }
enum E3 { case g, h, i } // for Testing a Type not conforming to the protocol
dict[E1.a] = "Hello"
dict[E2.f] = "World"
print(dict[E1.a])
print(dict[E2.f])
dict[E3.h] = "Good" // Compile-time Error as Expected:
// Subscript 'subscript(_:)' requires that 'E3' conform to 'P1'
这也适用于枚举以外的类型,例如结构,但有一个警告: 如果 AnyHashable(obj1) 和 AnyHashable(obj2) 相同,它会导致相同的密钥,这可能不是您想要的。 对于这种情况,除了使用 AnyHashable 之外,您还可以实现散列方法。