Если я объявлю 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"]

Поэтому я хочу сделать это без необходимости каждый раз заключать в свой enum String().

Я пробовал несколько вещей, выполнив extension вне Dictionary, используя ключевое слово where с Key и пытаясь реализовать новые функции subscript, но Я не могу заставить его работать. Что за хитрость?

Обновление для решения

Я нашел свое решение (см. Ниже).

5
Tim Fuqua 1 Мар 2016 в 04:13

3 ответа

Лучший ответ

Обновление и улучшение для 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]

Я специально изменил типы трех словарей, чтобы показать общие способы набора словаря ([AnyHashable : Any], [String : Any] и [String : String]), и показал, что это работает с каждым из типов.

Важно отметить, что если вы используете updateValue вместо оператора присваивания, когда ключом вашего словаря является AnyHashable, тогда вам необходимо указать значение ключа String с помощью {{X3 }}. В противном случае точный тип сохраняемого ключа явно не будет 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, но я не уверен, что это необходимо, так как я знаю, что мое перечисление всегда можно преобразовать в действительный Key состав String().

Улучшение ответа

Еще лучше, поскольку я использую это для ключей 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"]
7
Community 20 Июн 2020 в 09:12

Или вы можете сделать:

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]
1
J.Wang 2 Мар 2016 в 00:46

Насколько важно, чтобы ваши ключи были строками? Если это не важно, я предлагаю сделать что-нибудь вроде:

enum Keys {
    case key_one
    case key_two
}

var myDict = [Keys : String]()

myDict[Keys.key_one] = "firstKey"
print(myDict) // [Keys.key_one: "firstKey"]
0
JYeh 1 Мар 2016 в 01:23