Перейти к содержимому
Теория · Перечисления · урок 2 из 8

Сырые значения

В прошлом уроке варианты перечисления были только именами из меню. Но в настоящем меню у каждого блюда есть ещё и номер: суп — первый, салат — второй, паста — третья. Официанту можно сказать «мне номер три», и он поймёт, что речь о пасте. Номер напечатан в меню один раз, и он одинаков для всех, кто заказывает пасту.

Такие номера можно дать и вариантам перечисления:

SWIFT
enum Dish: Int {
    case soup = 1
    case salad
    case pasta
}

print(Dish.soup.rawValue) // 1
print(Dish.pasta.rawValue) // 3

Давайте разберём программу по строкам:

  1. : Int после имени Dish говорит, что у каждого варианта есть номер типа Int.
  2. case soup = 1 задаёт номер супа явно.
  3. У salad и pasta номера не написаны, и Swift считает дальше сам: салат получает 2, паста — 3.
  4. Dish.pasta.rawValue читает номер пасты, и программа печатает 3.

Тип и rawValue

Сырое значение — постоянное значение, которое закреплено за вариантом прямо в объявлении перечисления. Его тип пишут после имени перечисления через двоеточие: enum Dish: Int. Это не то же, что CaseIterable из прошлого урока, но их можно совместить через запятую, тип первым: enum Size: String, CaseIterable. Читают сырое значение через rawValue.

Что важно знать о сырых значениях:

  • у всех вариантов одного перечисления сырые значения одного и того же типа;
  • у каждого варианта одно сырое значение, и оно не меняется: у любого Dish.pasta номер 3;
  • без типа после двоеточия сырых значений нет: у enum Drink { case tea, coffee } запись Drink.tea.rawValue не скомпилируется.

Как Swift подставляет значения

Если сырые значения — целые числа или строки, писать их у каждого варианта не обязательно: недостающие Swift подставит сам. Что именно он подставит, зависит от типа.

Целые числа

Для Int Swift считает по порядку:

  • если у первого варианта числа нет, он получает 0;
  • каждый следующий вариант без числа получает на единицу больше, чем предыдущий;
  • если у варианта посередине число написано явно, Swift считает следующие варианты уже от него: в case pasta = 10, cake торт получает 11.

Строки

Для String вариант без явного значения получает своё имя:

SWIFT
enum Size: String {
    case small
    case medium = "M"
    case large = "L"
}

print(Size.small.rawValue) // small
print(Size.large.rawValue) // L

У small строка не задана, поэтому сырое значение — имя варианта, "small". У large строка задана явно: "L", как размер на чеке.

Вариант по сырому значению

Официант по номеру находит блюдо. Swift тоже так умеет: по сырому значению находит вариант. Только номера 7 в меню может не оказаться, поэтому результат — опционал:

SWIFT
if let dish = Dish(rawValue: 2) {
    print(dish) // salad
}
let missing = Dish(rawValue: 7)
print(missing == nil) // true
let chosen = Dish(rawValue: 7) ?? .soup
print(chosen) // soup

Давайте пройдём по программе:

  1. Dish(rawValue: 2) находит вариант с номером 2 и возвращает коробку Dish? с салатом внутри. if let открывает её, и программа печатает salad.
  2. Варианта с номером 7 нет, поэтому Dish(rawValue: 7) возвращает nil.
  3. ?? подставляет значение по умолчанию, .soup, когда коробка пуста.

Swift добавляет такой способ создать значение только перечислениям с сырыми значениями. В документации его записывают как init?(rawValue:), и знак вопроса там значит: результат — опционал.

Этот вопрос часто задают на собеседовании: почему init?(rawValue:) возвращает опционал? Ответ: вариант есть не для каждого сырого значения. В Dish(rawValue:) можно передать любое Int, а вариантов всего три, поэтому для остальных чисел результат — nil, а не аварийное завершение программы.

Здесь картинка с меню неточна. В настоящем меню номер могут напечатать с ошибкой, и у двух блюд окажется один номер. Swift такого не допустит: два одинаковых сырых значения дают ошибку компиляции raw value for enum case is not unique.

Частая ошибка

Результат Dish(rawValue:) положили в константу типа Dish:

SWIFT
let dish: Dish = Dish(rawValue: 2) // ✗
TEXT
error: value of optional type 'Dish?' must be unwrapped to a value of type 'Dish'

Программа не компилируется: Dish(rawValue: 2) даёт Dish?, а не Dish. То, что вариант с номером 2 есть, компилятор не проверяет: он смотрит только на тип. Откройте коробку через if let или подставьте значение по умолчанию через ??.

Главное

  • Сырое значение закреплено за вариантом в объявлении: тип пишут после имени, enum Dish: Int, а читают значение через rawValue.
  • Swift сам считает целые сырые значения: от 0 или от явного числа, каждый следующий на единицу больше.
  • У String вариант без явного значения получает своё имя.
  • Dish(rawValue: 2) ищет вариант по сырому значению и возвращает опционал: такого значения может не оказаться.