Связанные значения
Вернёмся к меню кафе. Чай всегда подают одинаково, а кофе бывает разного размера. Официант пишет на чеке: «кофе, размер L». Строка в меню одна на всех, а детали на чеке у каждого заказа свои.
Сырое значение здесь не поможет: оно одно на вариант и записано в коде. А размер выбирает гость, когда делает заказ. Для таких деталей у вариантов есть связанные значения:
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")Давайте разберём программу по строкам:
case coffee(size: String)объявляет вариант с деталью: в скобках меткаsizeи типString.Order.coffee(size: "L")создаёт заказ кофе и записывает на его чек размер"L".secondтоже кофе, но с размером"S": вариант один, а детали разные.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: при каждом вызове в ней размер своего заказа.
1enum Order {2 case tea3 case coffee(size: String)4}5 6func label(_ order: Order) -> String {7 switch order {8 case .tea:9 return "tea"10 case .coffee(let size):11 return "coffee " + size12 }13}14 15print(label(.coffee(size: "L")))16print(label(.tea))17print(label(.coffee(size: "S")))
Программа вызывает label и передаёт заказ кофе размера L.
Что важно запомнить:
- константа
sizeесть только внутри своей ветки, а имя для неё можно выбрать любое:case .coffee(let s):; - шаблон
case .coffee:без скобок тоже подходит под любой кофе, но размер не достаёт; switchпо-прежнему исчерпывающий: если у каждого варианта есть ветка,defaultне нужен.
Шаблон с where
К шаблону со связанным значением можно добавить условие where. Большой кофе стоит дороже:
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Давайте пройдём по программе:
- У
firstразмер"L": шаблон.coffee(let size)подходит, условиеwhereистинно, и цена4. - У
secondразмер"S": условиеwhereложно, иswitchидёт к следующей ветке. - Ветка
case .coffee:принимает любой кофе, и ценаsecond—3.
Порядок веток здесь важен. switch выполняет первую подходящую ветку, и case .coffee: выше ветки с where забрал бы весь кофе.
Одна ветка: if case
Когда нужен только один вариант, писать switch со всеми ветками долго. Для этого есть if case:
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 у строки:
print(first.size) // ✗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проверяет один вариант и достаёт его значение.- Сырое значение одно на вариант и закреплено в объявлении, а связанное у каждого значения своё; в одном перечислении их не совмещают.