Сырые значения
В прошлом уроке варианты перечисления были только именами из меню. Но в настоящем меню у каждого блюда есть ещё и номер: суп — первый, салат — второй, паста — третья. Официанту можно сказать «мне номер три», и он поймёт, что речь о пасте. Номер напечатан в меню один раз, и он одинаков для всех, кто заказывает пасту.
Такие номера можно дать и вариантам перечисления:
enum Dish: Int {
case soup = 1
case salad
case pasta
}
print(Dish.soup.rawValue) // 1
print(Dish.pasta.rawValue) // 3Давайте разберём программу по строкам:
: Intпосле имениDishговорит, что у каждого варианта есть номер типаInt.case soup = 1задаёт номер супа явно.- У
saladиpastaномера не написаны, и Swift считает дальше сам: салат получает2, паста —3. 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 вариант без явного значения получает своё имя:
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 в меню может не оказаться, поэтому результат — опционал:
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Давайте пройдём по программе:
Dish(rawValue: 2)находит вариант с номером2и возвращает коробкуDish?с салатом внутри.if letоткрывает её, и программа печатаетsalad.- Варианта с номером
7нет, поэтомуDish(rawValue: 7)возвращаетnil. ??подставляет значение по умолчанию,.soup, когда коробка пуста.
Swift добавляет такой способ создать значение только перечислениям с сырыми значениями. В документации его записывают как init?(rawValue:), и знак вопроса там значит: результат — опционал.
Этот вопрос часто задают на собеседовании: почему init?(rawValue:) возвращает опционал? Ответ: вариант есть не для каждого сырого значения. В Dish(rawValue:) можно передать любое Int, а вариантов всего три, поэтому для остальных чисел результат — nil, а не аварийное завершение программы.
Здесь картинка с меню неточна. В настоящем меню номер могут напечатать с ошибкой, и у двух блюд окажется один номер. Swift такого не допустит: два одинаковых сырых значения дают ошибку компиляции raw value for enum case is not unique.
Частая ошибка
Результат Dish(rawValue:) положили в константу типа Dish:
let dish: Dish = Dish(rawValue: 2) // ✗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)ищет вариант по сырому значению и возвращает опционал: такого значения может не оказаться.