$ sudo teach IT

Зачем нужен switch

Когда вариантов больше двух-трёх, цепочка if / else if начинает разваливаться: условия повторяются, легко забыть один из вариантов, а код растягивается вширь. Представьте турникет в метро, который должен по-разному реагировать на цвет карты — зелёный, жёлтый, красный, синий, — и ещё как-то обработать карту неизвестного цвета. Писать это через if можно, но неудобно читать: непонятно, все ли варианты учтены. Для таких задач в Swift есть switch — оператор ветвления, который сравнивает одно значение с набором образцов и сразу показывает, что вариантов учтено ровно столько, сколько нужно.

Официальный термин: сопоставление с образцом

switch в Swift — это не просто «переключатель по значению», как в некоторых языках. Каждый case — это образец (pattern), и Swift проверяет, подходит ли под этот образец значение в switch. Образцом может быть конкретное значение, диапазон, кортеж или значение с дополнительным условием. Из-за этого механизм называют сопоставлением с образцом (pattern matching) — он гораздо мощнее, чем «сравнить с числом».

let signal = "жёлтый"

switch signal {
case "красный":
    print("Стоять")
case "жёлтый":
    print("Приготовиться")
case "зелёный":
    print("Идти")
default:
    print("Неизвестный сигнал")
}

Swift сравнивает signal по очереди с каждым case сверху вниз и выполняет код первого совпавшего варианта. Здесь совпадёт "жёлтый", напечатается Приготовиться, и на этом работа switch закончится.

Исчерпывающий switch и роль default

Swift требует, чтобы switch был исчерпывающим: обязан быть учтён каждый возможный вариант значения. Для строки заранее неизвестно, какие символы введёт пользователь, поэтому компилятор не может проверить все строки — и здесь нужен default, который ловит всё, что не подошло под предыдущие case. Без него код выше просто не скомпилируется.

Для некоторых типов Swift сам видит, что варианты уже покрыты полностью, и default не нужен — например, если case с диапазонами вместе покрывают весь Int без исключений. Но в подавляющем большинстве уроков про базовые типы проще и надёжнее всегда добавлять default: он делает исчерпываемость видимой и защищает от случая, который вы не предусмотрели.

switch не проваливается сам, в отличие от C

Если раньше вы видели код на C, C++ или Java, там после совпавшего case выполнение по умолчанию «проваливается» в следующий case, если не поставить break. В Swift всё наоборот: как только совпал один case, выполняется только его код, а затем switch завершается сам, без всякого break. Именно поэтому пример с турникетом выше и работает: совпадение с "жёлтый" не потащило за собой ветку "зелёный".

Иногда провал в следующий case всё-таки нужен — это редкий случай, но для него есть отдельное ключевое слово fallthrough, которое явно говорит: «выполни ещё и код следующего case, без проверки его образца».

let day = 6

switch day {
case 6:
    print("Суббота — выходной")
    fallthrough
case 7:
    print("Можно поспать подольше")
default:
    print("Обычный день")
}

Здесь при day == 6 напечатаются обе строки: сначала код для субботы, а затем — из-за fallthrough — код случая 7, без проверки, что day действительно равен 7. Используйте fallthrough только когда логика правда требует «и это тоже», иначе он делает код менее предсказуемым.

Несколько значений в одном case

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

let letter: Character = "a"

switch letter {
case "a", "e", "i", "o", "u":
    print("гласная")
default:
    print("согласная")
}

Интервалы в case

Диапазоны, которые вы уже видели у операторов (..< и ...), прекрасно работают как образцы: case совпадёт, если значение попадает в диапазон.

let score = 73

switch score {
case 90...100:
    print("пятёрка")
case 75..<90:
    print("четвёрка")
case 60..<75:
    print("тройка")
default:
    print("нужно подтянуть тему")
}

score равен 73, это входит в интервал 60..<75, поэтому напечатается тройка. Порядок case здесь важен: проверка идёт сверху вниз, и если бы диапазоны пересекались, выиграл бы тот, что стоит выше.

Кортежи, подстановочный знак и привязка значения

В case можно сопоставлять сразу кортеж — это удобно, когда результат зависит от нескольких значений вместе, а не по отдельности. Подстановочный знак _ в позиции кортежа означает «здесь подойдёт что угодно, значение не важно».

let point = (0, 5)

switch point {
case (0, 0):
    print("это начало координат")
case (_, 0):
    print("точка на оси X")
case (0, _):
    print("точка на оси Y")
default:
    print("точка вне осей")
}

Здесь point — это (0, 5), первый элемент кортежа равен нулю, поэтому совпадёт case (0, _), и напечатается точка на оси Y.

Кроме подстановочного знака, значение из кортежа можно привязать к временной константе прямо в образце с помощью let — тогда внутри тела case можно им пользоваться.

let another = (3, 0)

switch another {
case (0, 0):
    print("начало координат")
case (let x, 0):
    print("точка (\(x), 0) лежит на оси X")
default:
    print("другая точка")
}

Второй элемент кортежа — ноль, а первый образец привязки let x захватывает значение 3 в константу x, которую можно использовать в теле case.

Условие where: сужаем образец

Иногда самого образца не хватает — нужно ещё и дополнительное условие поверх привязанного значения. Для этого после образца пишут where с обычным булевым выражением. case сработает, только если совпал и образец, и условие where.

let temperature = -5

switch temperature {
case let t where t < 0:
    print("мороз, \(t) градусов")
case 0:
    print("ровно ноль")
case let t where t > 30:
    print("жара, \(t) градусов")
default:
    print("обычная погода")
}

where особенно полезен вместе с привязкой из кортежа, когда результат зависит от связи между двумя значениями, а не от каждого отдельно.

let pair = (4, 4)

switch pair {
case let (a, b) where a == b:
    print("значения равны")
case let (a, b) where a > b:
    print("первое больше")
default:
    print("первое меньше")
}

Сопоставление строк

Строки в case сравниваются целиком и с учётом регистра, точно так же, как оператор == у строк. Никакой магии с частичным совпадением здесь нет — если нужно проверить, например, что строка лишь начинается с чего-то, для этого по-прежнему используют методы строк, а не switch.

let command = "старт"

switch command {
case "старт", "пуск":
    print("Запускаем")
case "стоп":
    print("Останавливаем")
default:
    print("Команда не распознана")
}

switch как выражение

Начиная со Swift 5.9, switch можно использовать не только как оператор с блоками кода, а как выражение, которое сразу возвращает значение — тогда каждый case вместо инструкций содержит одно значение соответствующего типа.

let age = 10
let category = switch age {
case 0..<18: "ребёнок"
case 18..<65: "взрослый"
default: "пенсионер"
}
print(category)

Это просто более короткая запись той же идеи: получить одно из нескольких значений в зависимости от условия, без промежуточной переменной и return в отдельной функции.

Частые ошибки

  • Забыть default там, где Swift не может сам доказать исчерпываемость (почти всегда для String и часто для Int) — компилятор сразу укажет на ошибку, это не страшно, но замедляет работу, если не знать причину.
  • Ждать автоматического «провала» в следующий case, как в C — в Swift этого не происходит, а неожиданный провал случится только если сами написали fallthrough.
  • Перепутать порядок диапазонов или образцов — раз проверка идёт сверху вниз, слишком широкий образец наверху «съест» те случаи, которые должны были попасть в более узкий образец ниже.
  • Забыть, что привязанная через let переменная существует только внутри своего case — использовать её в другой ветке или после switch нельзя.

Резюме

  • switch сравнивает значение с набором образцов и выполняет код первого совпавшего case.
  • switch обязан быть исчерпывающим; default ловит всё, что не подошло под явные case.
  • В отличие от C, выполнение автоматически не проваливается в следующий case; для явного провала есть fallthrough.
  • Несколько значений можно перечислить через запятую в одном case, а интервалы задаются через ..< и ....
  • Кортежи сопоставляются целиком, _ означает «что угодно», а let в образце привязывает значение к константе.
  • where добавляет к образцу дополнительное условие.
  • Начиная со Swift 5.9 switch можно использовать как выражение, сразу возвращающее значение.

Проверьте себя

4 вопроса

Светофор по цвету

Напишите функцию trafficLightAction(_:), которая принимает строку с названием цвета сигнала светофора и возвращает подсказку водителю.

Правила:

  • "красный" или "бордовый" — вернуть "Стоять";
  • "жёлтый" — вернуть "Приготовиться";
  • "зелёный" — вернуть "Идти";
  • "Неизвестный сигнал".

Используйте switch по строке, а для первого правила укажите оба значения в одном case через запятую.

Возрастная категория

Напишите функцию ageCategory(_:), которая принимает возраст человека (Int, всегда неотрицательный) и возвращает его категорию по следующим границам:

  • от 0 до 2 лет включительно — "малыш";
  • от 3 до 6 лет включительно — "дошкольник";
  • от 7 до 17 лет включительно — "школьник";
  • от 18 до 64 лет включительно — "взрослый";
  • 65 лет и старше — "пенсионер".

Используйте switch с диапазонами в качестве образцов case, а последний вариант оформите через default.

Четверть координатной плоскости

Напишите функцию quadrant(_:_:) с двумя параметрами x и y типа Int, которая определяет положение точки (x, y) на координатной плоскости и возвращает строку:

  • (0, 0) — "начало координат";
  • "ось X";
  • "ось Y";
  • "первая четверть";
  • "вторая четверть";
  • "третья четверть";
  • "четвёртая четверть".

Используйте один switch по кортежу (x, y), привязку значений через let и условие where там, где одного образца недостаточно.