所以我实施了以下内容:
LanguageType
协议,符合Hashable
Translateable
协议,该协议允许您使用[String]
作为密钥从字典中获取(并设置)LanguageType
// MARK: - LanguageType
protocol LanguageType: Hashable {
var description: String { get }
}
extension LanguageType {
var description: String { return "\(Self.self)" }
var hashValue: Int { return "\(Self.self)".hashValue }
}
func ==<T: LanguageType, U: LanguageType>(left: T, right: U) -> Bool {
return left.description == right.description
}
// MARK: - Translateable
protocol Translateable {
var translations: [LanguageType: [String]] { get set }
}
像往常一样,Swift使用LanguageType
协议的方式存在问题:
从我读过的内容来看,这与Swift不支持Existentials有关,这导致协议实际上不是一流的类型。
在泛型的上下文中,这个问题通常可以通过类型擦除的包装来解决 在我的例子中,没有泛型或相关类型。
我想要达到的目标是让translations.Key
任何 LanguageType
,而不只是一个符合LanguageType
的通用类型。
因此,例如,这不会起作用:
protocol Translateable {
typealias Language: LanguageType
var translations: [Language: [String]] { get set }
}
出于某种原因,我无法想出实现这一目标的方法。我觉得我觉得我需要某种类型擦除的包装,因为我想要
translations.Key
任何LanguageType
我想我需要删除确切的类型,它应符合LanguageType
中的Translateable
。
我该怎么做才能解决这个问题?
更新1:
刚刚在this question中确定,LanguageType
实际上具有相关的类型要求(确实符合Equatable
)。因此,我将尝试围绕LanguageType
创建一个类型擦除的包装器。
更新2:
所以我意识到,为LanguageType
创建一个类型删除的包装器实际上无法解决问题。我创建了AnyLanguage
:
struct AnyLanguage<T>: LanguageType {
private let _description: String
var description: String { return _description }
init<U: LanguageType>(_ language: U) { _description = language.description }
}
func ==<T, U>(left: AnyLanguage<T>, right: AnyLanguage<U>) -> Bool {
return left.description == right.description
}
如果我现在用它代替LanguageType
它不会做太多,因为Translateable
仍然需要相关的类型:
protocol Translateable {
typealias T
var translations: [AnyLanguage<T>: [String]] { get set }
}
我从AnyLanguage
struct AnyLanguage: LanguageType {
private(set) var description: String
init<T: LanguageType>(_ language: T) { description = language.description }
}
func ==(left: AnyLanguage, right: AnyLanguage) -> Bool {
return left.description == right.description
}
protocol Translateable {
var translations: [AnyLanguage: [String]] { get set }
}
我不确定为什么我在更新2中引入了T
,因为它没有做任何事情。但这似乎现在有效。
答案 0 :(得分:3)
您无法将协议作为Dictionary
的密钥,请参阅Swift Dictionary with Protocol Type as Key。 Swift需要将字典键绑定到具体类型。
似乎你试图在同一个构造中实现静态多态和动态多态(Translateable
协议),我不确定它是否可以实现。
解决方法是将Translateable
声明为通用结构:
struct Translateable<T: LanguageType> {
var translations: [T: [String]]
}
答案 1 :(得分:1)
可能您可以使用符合enum
的{{1}}来模仿您正在寻找的行为。在这种情况下,您无需在LanguageType
中明确包含与hashable
的一致性,因为枚举为LanguageType
。
Hashable
示例:
protocol LanguageType {
var description: String { get }
// ...
}
extension LanguageType {
var description: String { return "\(Self.self)" }
}
enum AnyLanguage : Int, LanguageType {
case English = 1, German, Swedish
// implement non-default description
var description : String {
return "Language: " + String(self)
}
}
protocol Translatable {
var myDict : [AnyLanguage:[String]] { get set }//= [:]
}
class MyFooWordList : Translatable {
private var myBackendDict : [AnyLanguage:[String]] = [:]
var myDict : [AnyLanguage:[String]] {
get {
return myBackendDict
}
set {
for (k, v) in newValue {
myBackendDict[k] = v
}
}
}
}
另一种解决方法是使用类似/* Example */
var myFooWordList = MyFooWordList()
myFooWordList.myDict = [.English: ["Hello", "World"]]
myFooWordList.myDict = [.German: ["Hallo", "Welt"]]
print("Words for '" + AnyLanguage.English.description + "': \(myFooWordList.myDict[.English] ?? ["<Empty>"])")
/* Words for 'Language: English': ["Hello", "World"] */
print("Words for '" + AnyLanguage.German.description + "': \(myFooWordList.myDict[.German] ?? ["<Empty>"])")
/* Words for 'Language: German': ["Hallo", "Welt"] */
print("Words for '" + AnyLanguage.Swedish.description + "': \(myFooWordList.myDict[.Swedish] ?? ["<Empty>"])")
/* Words for 'Language: Swedish': ["<Empty>"] */
的类,您可以&#34;动态地将成员&#34; 添加到此虚拟枚举中
enum
使用示例
class LanguageType {
class AnyLanguage: Hashable {
let id: Int
let description: String
private init(id: Int, description: String) {
self.id = id
self.description = description
}
var hashValue: Int { return id }
}
class var ENGLISH: AnyLanguage {
class English: AnyLanguage {
}
return English(id: 1, description: "English")
}
class var GERMAN: AnyLanguage {
class German: AnyLanguage {
}
return German(id: 2, description: "German")
}
class func CUSTOM(id: Int, _ description: String) -> AnyLanguage {
return AnyLanguage(id: id, description: description)
}
}
func == (lhs: LanguageType.AnyLanguage, rhs: LanguageType.AnyLanguage) -> Bool {
return lhs.id == rhs.id
}
protocol Translatable {
var myDict : [LanguageType.AnyLanguage:[String]] { get set }//= [:]
}
class MyFooWordList : Translatable {
private var myBackendDict : [LanguageType.AnyLanguage:[String]] = [:]
var myDict : [LanguageType.AnyLanguage:[String]] {
get {
return myBackendDict
}
set {
for (k, v) in newValue {
myBackendDict[k] = v
}
}
}
}
答案 2 :(得分:0)
解决方案似乎是type-erased wrapper。类型擦除通过创建包装类型来解决无法使用protocols with associated types (PATs)作为一等公民的问题,该包装类型仅公开由其包装的协议定义的属性。
在这种情况下,LanguageType
是一个 PAT ,因为它采用了Equatable
(由于采用了Hashable)
而符合它:
protocol LanguageType: Hashable { /*...*/ }
因此,它不能用作Translatable
协议中的第一类:
protocol Translatable {
var translations: [LanguageType: [String]] { get set } // error
}
为Translatable
定义关联类型无法解决问题,因为这会将LanguageType
限制为一种特定类型:
protocol Translatable {
typealias Language: LanguageType
var translations: [Language: [String]] { get set } // works
}
struct MyTranslatable<T: LanguageType>: Translatable {
var translations: [T: [String]] // `T` can only be one specific type
//...
}
如上所述,解决方案是类型擦除的包装器AnyLanguage
(Apple对其类型擦除的包装器使用相同的命名约定。例如AnySequence
):
// `AnyLanguage` exposes all of the properties defined by `LanguageType`
// in this case, there's only the `description` property
struct AnyLanguage: LanguageType {
private(set) var description: String
// `AnyLanguage` can be initialized with any type conforming to `LanguageType`
init<T: LanguageType>(_ language: T) { description = language.description }
}
// needed for `AnyLanguage` to conform to `LanguageType`, as the protocol inherits for `Hashable`, which inherits from `Equatable`
func ==(left: AnyLanguage, right: AnyLanguage) -> Bool {
return left.description == right.description
}
// the use of `AnyLanguage` allows any `LanguageType` to be used as the dictionary's `Key`, as long as it is wrapped as `AnyLanguage`
protocol Translateable {
var translations: [AnyLanguage: [String]] { get set }
}
此实现现在允许以下内容:
struct SomethingTranslatable: Translatable {
var translations: [AnyLanguage: [String]] = [:]
}
func ==(left: SomethingTranslatable, right: SomethingTranslatable) -> Bool { /*return some `Bool`*/ }
struct English: LanguageType { }
struct German: LanguageType { }
var something = SomethingTranslatable()
something.translations[AnyLanguage(English())] = ["Hello", "World"]
let germanWords = something.translations[AnyLanguage(German())]
符合LanguageType
的不同类型现在可以用作Key
。唯一的语法差异是AnyLanguage
:
AnyLanguage(English())