使用枚举的默认值作为字典键而不显式转换为 String

Using an enum's default value as a dictionary key without explicitly casting to String

如果我这样声明 enum

enum Keys {
    case key_one
    case key_two
}

我可以打印它会自动转换成String:

print(Keys.key_one) // prints "key_one"

如果我然后制作一个将 Strings 映射到任何内容的字典(但为了简单起见,让我们再次选择 Strings),我认为它应该能够通过使用 [=18 添加一个键=] 作为关键,对吧?错了。

var myDict = [String : String]()

/// error: cannot subscript a value of type '[String : String]' with an index
/// of type 'Keys'
myDict[Keys.key_one] = "firstKey"

如果我明确地将 Keys.key_one 转换为 String,我可以做到这一点:

myDict[String(Keys.key_one)] = "firstKey"
print(myDict) // ["key_one": "firstKey"]

所以我想这样做而不必每次都用 String() 包装我的 enum

我尝试了一些事情,通过使用 where 关键字和 KeyDictionary 中执行 extension 并尝试实现新的 subscript 功能,但我无法让它工作。有什么诀窍?

解决方案更新

我找到了我的解决方案 ()。

您的键是字符串有多重要?如果它不重要,我建议做这样的事情:

enum Keys {
    case key_one
    case key_two
}

var myDict = [Keys : String]()

myDict[Keys.key_one] = "firstKey"
print(myDict) // [Keys.key_one: "firstKey"]

或者你可以这样做:

enum Keys: String {
    case key_one
    case key_two
}

并像这样使用它:

var myDict = [String : String]()

myDict[Keys.key_one.rawValue] = "firstKey"

更新

如果你真的想让它工作,你可以按照下面的方法做。但这些都是强制展开的一些风险。

extension Dictionary where Key: StringLiteralConvertible {
    subscript (key: Keys) -> Value? {
        get {
            return self[key.rawValue as! Key]
        }
        set {
            self[key.rawValue as! Key] = newValue
        }
    }
}

然后你像这样使用它:

myDict[Keys.key_one] = "firstKey"
myDict[Keys.key_one]

Swift 4.2

的更新和改进
extension Dictionary {
    subscript(key: APIKeys) -> Value? {
        get {
            guard let key = key.stringValue as? Key else { return nil }
            return self[key]
        }
        set(value) {
            guard let key = key.stringValue as? Key else { return }
            guard let value = value else { self.removeValue(forKey: key); return }
            self.updateValue(value, forKey: key)
        }
    }
}

protocol APIKeys {}
extension APIKeys {
    var stringValue: String {
        return String(describing: self)
    }
}

enum Keys: APIKeys {
    case key_one
    case key_two
}

var myStringDict = [AnyHashable : Any]()
var model1StringDict = [String : Any]()
var model2StringDict = [String : String]()

myStringDict.updateValue("firstValue", forKey: Keys.key_one.stringValue)                    // [key_one: firstValue]
myStringDict[Keys.key_two] = "secondValue"                                                  // [key_two: secondValue, key_one: firstValue]
myStringDict[Keys.key_one] = nil                                                            // [key_two: secondValue]
myStringDict.removeValue(forKey: Keys.key_two.stringValue)                                  // []

model1StringDict.updateValue("firstValue", forKey: Model1Keys.model_1_key_one.stringValue)  // [model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue"                                // [model_1_key_two: secondValue, model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_one] = nil                                          // [model_1_key_two: secondValue]

model2StringDict.updateValue("firstValue", forKey: Model2Keys.model_2_key_one.stringValue)  // [model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue"                                // [model_2_key_two: secondValue, model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_one] = nil                                          // [model_2_key_two: secondValue]

我专门更改了 3 个词典的类型以显示键入词典的常用方式([AnyHashable : Any][String : Any][String : String]),并表明这适用于每种类型。

需要注意的是,当你的字典的键是AnyHashable时,如果你使用updateValue而不是赋值运算符,那么你需要指定String值用 .stringValue 键。否则,存储的密钥的确切类型将不会明确地是 String,如果您尝试通过将 nil 分配给删除密钥下的值,它会在以后变得混乱那把钥匙。对于键被专门键入为 String 的字典,那么 updateValue 函数将有一个编译时错误,指出键需要是 String,所以你不能把事情搞砸了。

Swift 2.3

我找到了我想要的扩展解决方案。

extension Dictionary {
    
    subscript(key: Keys) -> Value? {
        get {
            return self[String(key) as! Key]
        }
        set(value) {
            guard
                let value = value else {
                    self.removeValueForKey(String(key) as! Key)
                    return
            }

            self.updateValue(value, forKey: String(key) as! Key)
        }
    }

}

enum Keys {
    case key_one
    case key_two
}

var myStringDict = [String : String]()
/// Adding the first key value through the String() way on purpose
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one))
// myStringDict: ["key_one": "firstValue"]
// myStringDict[Keys.key_one]!: firstValue

myStringDict[Keys.key_two] = "secondValue"
// myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]

myStringDict[Keys.key_one] = nil
// myStringDict: ["key_two": "secondValue"]

请注意,声明的字典键类型是 String,但我只能使用 Keys.key_one,字典扩展中的下标会处理其余部分。

我可能可以更好地保护 as!Key 的转换,但我不确定是否需要它,因为我知道我的枚举总是可以转换为有效的 KeyString() 演员。

改进回答

更好的是,因为我将它用于 API 键,所以我制作了一个名为 APIKeys 的空白协议,每个模型将实现自己的 Keys 枚举,该枚举符合APIKeys 协议。字典的下标更新为将 APIKeys 作为 Key 值。

extension Dictionary {
    
    subscript(key: APIKeys) -> Value? {
        get {
            return self[String(key) as! Key]
        }
        set(value) {
            guard
                let value = value else {
                    self.removeValueForKey(String(key) as! Key)
                    return
            }

            self.updateValue(value, forKey: String(key) as! Key)
        }
    }

}

protocol APIKeys {}

enum Keys: APIKeys {
    case key_one
    case key_two
}

enum Model1Keys: APIKeys {
    case model_1_key_one
    case model_1_key_two
}

enum Model2Keys: APIKeys {
    case model_2_key_one
    case model_2_key_two
}

var myStringDict = [String : String]()
var model1StringDict = [String : String]()
var model2StringDict = [String : String]()

myStringDict.updateValue("firstValue", forKey: String(Keys.key_one))    // myStringDict: ["key_one": "firstValue"]
myStringDict[Keys.key_two] = "secondValue"                              // myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil                                        // myStringDict: ["key_two": "secondValue"]

model1StringDict.updateValue("firstValue", forKey: String(Model1Keys.model_1_key_one))  // model1StringDict: ["model_1_key_one": "firstValue"]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue"                            // model1StringDict: ["model_1_key_one": "firstValue", "model_1_key_two": "secondValue"]
model1StringDict[Model1Keys.model_1_key_one] = nil                                      // model1StringDict: ["model_1_key_two": "secondValue"]

model2StringDict.updateValue("firstValue", forKey: String(Model2Keys.model_2_key_one))  // model2StringDict: ["model_2_key_one": "firstValue"]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue"                            // model2StringDict: ["model_2_key_one": "firstValue", "model_2_key_two": "secondValue"]
model2StringDict[Model2Keys.model_2_key_one] = nil                                      // model2StringDict: ["model_2_key_two": "secondValue"]