CustomStringConvertible в перечислении

У меня есть следующее перечисление в классе.

enum Attributes: String, CustomStringConvertible {
    case eventDate
    case eventName
    case eventType
    case country

    var description: String {
        return self.rawValue
    }
}

Когда я пытаюсь получить следующий код, компилятор жалуется на следующую ошибку.

var attributesList: [String] {
    return [
        Attributes.eventDate, //<-- Compiler error on this row
        Attributes.eventName,
        Attributes.eventType,
        Attributes.country]
}

Невозможно преобразовать значение типа "Атрибуты" в ожидаемый тип элемента "Строка".

Разве протокол CustomStringConvertible не должен возвращать "описание"? Что не так в приведенном выше коде?

1 ответ

Решение

TL; DR - это не работает, потому что массив Attributes не может быть назначен массиву Strings, они оба являются несовпадающими типами, и Swift не выполняет автоматическое преобразование между типами, и необходимо указать явное преобразование.


В Swift при инициализации массива с использованием литерала массива происходит следующее:

let words = ["hello", "world"]
  • Компилятор распознает, что литерал массива назначается переменной с именем words, Поскольку мы не указали тип words, неявно предполагается массив. Тип элементов, лежащих в основе массива, определяется на основе содержимого литерала массива.
  • В этом случае литерал массива является коллекцией String типы; это легко понять компилятору
  • Поскольку тип LHS является массивом, структура RHS является литералом массива, а поскольку тип LHS (Array) соответствует заранее определенному протоколу, называемому ExpressibleByArrayLiteral который имеет связанное ограничение типа для соответствия Element, компилятор будет фактически преобразовывать нашу строку в следующую

Пример:

let words = [String].init(arrayLiteral: ["hello", "world"]) // we do not call this init directly

Вот как работает инициализация с массивами литералов. В приведенном выше примере, поскольку мы не указали тип массива, будет работать неявная настройка типа. Если мы указали несоответствующий тип, присвоение завершится неудачно, так как ExpressibleByArrayLiteral требует ассоциированного Element тип литерала массива и фактического массива, которому вы назначаете соответствие.

Таким образом, следующее терпит неудачу:

let words:[String] = [1, 2] // array literal has Element=Int, array has Element=String

Это также показывает, что нет неявного преобразования типов между Int а также String, даже если Int соответствовать CustomStringConvertible,

В вашем случае вы пытаетесь назначить литерал массива, состоящий из Attributes к массиву String, Это несоответствие типов. Это причина, почему это не удается.

Если вы заявите о соответствии протокола, будет работать следующая строка:

var attributesList: [CustomStringConvertible] {
    return [
        Attributes.eventDate,
        Attributes.eventName,
        Attributes.eventType,
        Attributes.country
    ]
}
// note that we have an array of CustomStringConvertible protocol,
// each element here is still of Attributes type
// a type conforming to a protocol can be cast to an instance
// of that protocol automatically
// The above initialisation works simply because the following
// also works without any further action required
// let a:CustomStringConvertible = Attributes.country

Если вы действительно хотите получить список строковых значений, вам нужно явно отобразить это в строку:

var attributesList1: [String] {
    return [
        Attributes.eventDate,
        Attributes.eventName,
        Attributes.eventType,
        Attributes.country
        ].map { $0.description }
}

var attributesList2: [String] {
    return [
        Attributes.eventDate.description,
        Attributes.eventName.description,
        Attributes.eventType.description,
        Attributes.country.description
        ]
}
Другие вопросы по тегам