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

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

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

Сырое значение здесь не поможет: оно одно на вариант и записано в коде. А размер выбирает гость, когда делает заказ. Для таких деталей у вариантов есть связанные значения:

SWIFT
enum Order {
    case tea
    case coffee(size: String)
}

let first = Order.coffee(size: "L")
let second = Order.coffee(size: "S")
print(first) // coffee(size: "L")
print(second) // coffee(size: "S")

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

  1. case coffee(size: String) объявляет вариант с деталью: в скобках метка size и тип String.
  2. Order.coffee(size: "L") создаёт заказ кофе и записывает на его чек размер "L".
  3. second тоже кофе, но с размером "S": вариант один, а детали разные.
  4. print показывает вариант вместе с его деталями, а строку в деталях — в кавычках, как она записана в коде.

У tea скобок нет: чай заказывают без деталей.

Теперь у перечисления больше значений, чем вариантов: first и second — два разных значения одного варианта coffee.

Объявление и создание

Связанное значение — значение, которое хранится вместе с вариантом и задаётся заново при создании каждого значения перечисления. Что о нём важно знать:

  • его метку и тип пишут в скобках после имени варианта: coffee(size: String);
  • при создании его передают как аргумент функции: .coffee(size: "L");
  • у варианта может быть несколько связанных значений разных типов: juice(fruit: String, glasses: Int);
  • у одного варианта они есть, у другого их может не быть вовсе, как у tea.

Достаём значение в switch

Связанное значение достают шаблоном в switch: case .coffee(let size):. Шаблон подходит под любой кофе и кладёт размер с его чека в новую константу size. Это тот же шаблон let из темы об условиях, только записанный внутри скобок варианта.

Нажимайте «Следующий шаг» и следите за константой size: при каждом вызове в ней размер своего заказа.

ВживуюКак switch достаёт размершаг 1 из 11 · рамок: 1

Программа вызывает label и передаёт заказ кофе размера L.

Следите за size: она появляется только в ветке .coffee

Что важно запомнить:

  • константа size есть только внутри своей ветки, а имя для неё можно выбрать любое: case .coffee(let s):;
  • шаблон case .coffee: без скобок тоже подходит под любой кофе, но размер не достаёт;
  • switch по-прежнему исчерпывающий: если у каждого варианта есть ветка, default не нужен.

Шаблон с where

К шаблону со связанным значением можно добавить условие where. Большой кофе стоит дороже:

SWIFT
func price(_ order: Order) -> Int {
    switch order {
    case .tea:
        return 2
    case .coffee(let size) where size == "L":
        return 4
    case .coffee:
        return 3
    }
}

print(price(first), price(second)) // 4 3

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

  1. У first размер "L": шаблон .coffee(let size) подходит, условие where истинно, и цена 4.
  2. У second размер "S": условие where ложно, и switch идёт к следующей ветке.
  3. Ветка case .coffee: принимает любой кофе, и цена second — 3.

Порядок веток здесь важен. switch выполняет первую подходящую ветку, и case .coffee: выше ветки с where забрал бы весь кофе.

Одна ветка: if case

Когда нужен только один вариант, писать switch со всеми ветками долго. Для этого есть if case:

SWIFT
let order = Order.coffee(size: "M")
if case .coffee(let size) = order {
    print("size", size) // size M
}

Запись читается так: «если order подходит под шаблон .coffee(let size)». Блок выполняется, только когда в order кофе. Внутри блока есть константа size. Это похоже на if let, а ветку else здесь тоже можно добавить.

Сырое или связанное

Этот вопрос часто задают на собеседовании: чем сырое значение отличается от связанного? Ответ:

  • сырое значение одно на вариант и закреплено в объявлении, а связанное задают заново при создании каждого значения;
  • сырые значения у всех вариантов одного типа, а связанные у каждого варианта свои;
  • Dish(rawValue:) находит вариант по сырому значению, а связанное значение достают через switch или if case.

Совместить их нельзя. Объявление enum Dish: Int с вариантом pasta(size: Int) не скомпилируется: enum with raw type cannot have cases with arguments.

Здесь картинка с чеком неточна. На чек официант может дописать что угодно, хоть «без сахара». У связанного значения метка и тип заданы в объявлении: в size попадёт только строка, а к чаю размер не дописать.

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

Размер попробовали прочитать через точку, как count у строки:

SWIFT
print(first.size) // ✗
TEXT
error: value of type 'Order' has no member 'size'

Программа не компилируется. size — деталь варианта coffee, а не всего заказа: у чая её нет. Пока вы не проверите, что в first кофе, компилятор не даст прочитать размер. Достаньте его через switch или if case.

Главное

  • Связанное значение хранится вместе с вариантом и задаётся при создании значения: .coffee(size: "L").
  • Шаблон case .coffee(let size): в switch достаёт значение в константу, которая видна только в этой ветке.
  • if case .coffee(let size) = order проверяет один вариант и достаёт его значение.
  • Сырое значение одно на вариант и закреплено в объявлении, а связанное у каждого значения своё; в одном перечислении их не совмещают.