【问题标题】:Using an enum's default value as a dictionary key without explicitly casting to String使用枚举的默认值作为字典键而不显式转换为字符串
【发布时间】:2016-06-13 06:46:47
【问题描述】:

如果我像这样声明enum

enum Keys {
    case key_one
    case key_two
}

我可以打印它,它会自动转换为String

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

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

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 函数,但我无法实现工作。有什么诀窍?

解决方案更新

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

【问题讨论】:

    标签: swift dictionary enums key


    【解决方案1】:

    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 而不是赋值运算符,则需要使用.stringValue 指定键的String 值。否则,存储的密钥的确切类型将不会明确地是String,如果您尝试通过将nil 分配给该密钥来删除密钥下的值,那么稍后它会变得混乱。对于专门键入键为String 的字典,那么updateValue 函数将出现编译时错误,指出键需要是String,所以你不能这样搞砸。

    斯威夫特 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 时提供更好的保护,但我不确定它是否需要,因为我知道我的枚举始终可以通过 @987654340 转换为有效的 Key @演员。

    改进回答

    更好的是,因为我将它用于 API 密钥,所以我创建了一个名为 APIKeys 的空白协议,每个模型都将实现自己的符合 APIKeys 协议的 Keys 枚举。并且字典的下标被更新为将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"]
    

    【讨论】:

      【解决方案2】:

      或者你可以这样做:

      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]
      

      【讨论】:

      • 谢谢,但那不行。我还不如做String(Keys.key_one)。到处添加rawValue 并不比到处添加String() 好。
      • @TimFuqua 最坏的情况,您可以使用static let key_one: String 替换case key_one。我知道有些人会这样做。
      • @TimFuqua 枚举和字符串是两种不能强制转换的不同类型,您应该期望一些样板代码能够互换使用它们。
      • 我目前正在将一堆静态 let 字符串放入一个结构中。但是应该有办法通过扩展字典并将样板代码放入该空间。
      • 哎呀,算了。没有看到您自己的答案。
      【解决方案3】:

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

      enum Keys {
          case key_one
          case key_two
      }
      
      var myDict = [Keys : String]()
      
      myDict[Keys.key_one] = "firstKey"
      print(myDict) // [Keys.key_one: "firstKey"]
      

      【讨论】:

      • 我的键是字符串非常重要。仅仅因为它们不是我的字典。这是一本现有的字典。应该有某种方法可以对 Dictionary 进行扩展。这就是我真正在寻找的解决方案。
      猜你喜欢
      • 2010-11-06
      • 1970-01-01
      • 2019-06-29
      • 2015-10-12
      • 2012-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多