1. Іноді з case корисно витягувати значення
Якщо чесно, типова причина появи громіздких if/else не в тому, що люди не знають switch. Причина простіша: хочеться не лише вибрати гілку, а й одразу використати конкретне значення. Наприклад: «якщо число не дорівнює 0 і не дорівнює 1 — надрукуй, що це за число». І тут новачок часто повертається до if/else, бо здається, ніби switch «занадто жорсткий».
Насправді switch у Swift якраз уміє це робити — через pattern matching (зіставлення з патернами) і binding (прив’язування значення до імені). Ми вчитимемося діставати дані зі співпавшого case так, щоб код залишався читабельним, а не перетворювався на детектив «хто вкрав мою змінну і де її оголошено».
Pattern matching і case let
Слово «pattern» звучить так, ніби зараз почнеться лекція з дизайну інтер’єру: «патерни у вітальні», «патерни на килимі»… Але в програмуванні все значно простіше: патерн — це шаблон, із яким ми порівнюємо значення. Якщо значення підходить під шаблон, гілка case спрацьовує.
У Swift case — це не «довільна умова, як у if», а саме зіставлення з патерном. Патерном може бути літерал (case 0:), діапазон (case 0..<10:), «будь-який варіант» (case _:), або «будь-який варіант, але запам’ятай його в змінній» (case let x:). Синтаксис case let ... і if case let ... — це частина офіційного синтаксису Swift, і його давно використовують як стандартний спосіб витягування значень під час зіставлення.
Щоб мозку було простіше, уявіть трафарет. Значення — це предмет, який ми намагаємося прикласти до трафарета. Підійшло — чудово, виконуємо код. Не підійшло — переходимо до наступного трафарета (case) нижче.
Невелика «карта патернів», яку ми вже фактично використовуємо, але тепер назвемо на ім’я:
| Патерн | Приклад | Що означає |
|---|---|---|
| Літерал | |
«Точно дорівнює 0» |
| Кілька літералів | |
«Дорівнює одному з варіантів» |
| Діапазон | |
«Потрапило в межі» |
| Однобічний діапазон | |
«Менше за нуль» |
| Підстановка | |
«Будь-яке значення, неважливо яке» |
| Прив’язування | |
«Будь-яке значення, але зберегти його в x» |
2. case let: підходить усе — і мені потрібне значення
У попередніх прикладах default часто означав: «усе інше». Але проблема default у тому, що він не дає зручного імені для значення. Технічно ви завжди можете звернутися до початкової змінної, але якщо в switch передано вираз, хочеться прямо в гілці сказати: «Ось це значення назву other і виведу». Саме для цього й існує case let.
Найбазовіший приклад, і він справді часто трапляється в реальному коді:
import Foundation
let n = 42
switch n+1 {
case 0:
print("Нуль") // Нуль
case 1:
print("Один") // (не буде виведено)
case let other:
print("Інше число: \(other)") // Інше число: 43
}
Зверніть увагу на дві деталі. По-перше, case let other стоїть останнім — інакше він перехопить усе, і до case 0 ми ніколи не дійдемо. По-друге, other існує тільки всередині цього case; це локальна змінна гілки.
Ще один корисний прийом: case let x замість default, коли ви хочете явно показати намір: «Я обробив усі варіанти, а сюди потрапляє решта, і мені важливо знати, що саме»:
import Foundation
let command = "abracadabra"
switch command {
case "help":
print("Показати довідку") // (не буде виведено)
case "exit":
print("Бувай!") // (не буде виведено)
case let unknown:
print("Невідома команда: \(unknown)") // Невідома команда: abracadabra
}
Так, це дуже схоже на default, але психологічно читається інакше: не «усе інше», а «будь-який рядок, і я його зберігаю».
3. case let + where: коли одного збігу замало
Іноді самого патерна замало. Наприклад, ви хочете не просто зловити будь-яке число, а зловити число й додатково перевірити, що воно парне та додатне. У таких випадках ми поєднуємо binding і where.
where у switch — це додаткова умова: гілка спрацює лише тоді, коли збігся патерн і істинна умова після where. Усередині where ми часто використовуємо пов’язане імʼя (наприклад, v), тому спочатку робимо binding, а потім уточнюємо.
Приклад:
import Foundation
let x = 12
switch x {
case let v where v > 0 && v.isMultiple(of: 2):
print("Додатне парне: \(v)") // Додатне парне: 12
case let v where v > 0:
print("Додатне непарне: \(v)")
case 0:
print("Нуль")
default:
print("Від’ємне")
}
Тут ми використовуємо isMultiple(of:), щоб не писати v % 2 == 0. Виходить природніше: число кратне двом.
Корисна думка: case let v без where — це варіант, що спіймає все. case let v where ... — це варіант, що спіймає все, але лише якщо виконується правило.
4. case var: локальна змінювана копія
Іноді в самій гілці хочеться трохи підправити значення: додати бонус, застосувати знижку, збільшити його на 5 — і вже потім вивести. І тут виникає запитання: а чи можна всередині case зробити змінну змінюваною? Можна: case var.
Важливо розуміти: case var створює локальну копію значення всередині гілки. Ви змінюєте її — початкова змінна, за якою робили switch, не змінюється. Це не «посилання» і не зміна ззовні, а просто зручний спосіб уникнути окремого рядка var temp = value.
Приклад:
import Foundation
let points = 10
switch points {
case var x where x > 0:
x += 5
print("Бонусні бали: \(x)") // Бонусні бали: 15
default:
print("Немає балів")
}
print(points) // 10
Останній print(points) спеціально залишено: він показує, що початкове значення не змінилося. Це особливо важливо для новачків, бо мозок легко домальовує «магічну» зміну змінної, якої насправді немає.
5. Область видимості та точкові перевірки
Область видимості та порядок case
Коли ви вперше починаєте використовувати case let, є дві часті емоції. Перша: «О, як зручно!» Друга, через п’ять хвилин: «Чому я не можу використовувати v після switch? Я ж його оголосив!»
Відповідь проста: змінні, оголошені в case, живуть тільки всередині цього блока. Це захисний механізм: інакше switch перетворювався б на джерело напівініціалізованих змінних (в одній гілці змінна є, в іншій — ні).
Щоб це відчути, ось приклад, який новачок часто хоче написати, але він не спрацює (і це нормально):
import Foundation
let n = 7
switch n {
case let v:
print("Всередині case: \(v)") // Всередині case: 7
}
print(v) // ❌ Помилка: v тут не видно
І друга частина — порядок case. Swift перевіряє гілки зверху вниз і бере першу придатну. Тому будь-які загальні патерни, на кшталт case let x, мають бути нижче за більш конкретні. Це те саме правило, що й з діапазонами: спочатку вузьке, потім загальне. Якщо зробити навпаки, ви самі собі вимкнете половину логіки, а потім будете шукати, чому не працює case 0 (спойлер: бо case let x його вже перехопив).
if case: одна перевірка без повного switch
switch добрий, коли гілок кілька. Але іноді вам потрібно зробити лише одну перевірку на кшталт «чи потрапив рейтинг у діапазон 1...5», і писати заради цього switch здається занадто громіздко. Ось тут і з’являється if case.
Форма запам’ятовується як маленьке заклинання: if case ПАТЕРН = ЗНАЧЕННЯ. Зверніть увагу на порядок: спочатку патерн, потім значення. Це незвично після if x == 10, але логіка тут така: «чи підходить значення під цей патерн».
Приклад із діапазоном — дуже практичний для перевірки введення:
import Foundation
let rating = 4
if case 1...5 = rating {
print("Дійсний рейтинг") // Дійсний рейтинг
} else {
print("Недійсний рейтинг")
}
І приклад з однобічним діапазоном:
import Foundation
let temperature = -3
if case ..<0 = temperature {
print("Нижче нуля") // Нижче нуля
} else {
print("Не нижче нуля")
}
if case — це не іграшка. Це нормальний інструмент, який допомагає прибрати маленькі switch і зробити код коротшим, коли вам не потрібно багато гілок. Технічно код вище еквівалентний ось цьому:
import Foundation
let temperature = -3
switch temperature {
case ..<0:
print("Нижче нуля") // Нижче нуля
default:
print("Не нижче нуля")
}
6. Мінізастосунок: ConsoleBuddy
Тепер зберемо все в маленький цілісний сценарій, який ви зможете розширювати далі в курсі. Ми зробимо консольного помічника ConsoleBuddy: він читає команду ("score", "temp", "exit"), а потім виконує відповідну перевірку. Ідея проста, зате в ній природно використано case let для невідомих команд і if case для валідації чисел.
Каркас циклу команд
Цикл нам потрібен, щоб застосунок не завершувався після однієї команди. Ми це вже вміємо робити через while true і break.
import Foundation
while true {
print("Введіть команду (score/temp/exit): ", terminator: "")
let command = (readLine() ?? "").lowercased()
switch command {
case "exit":
print("Бувай!") // Бувай!
break
default:
print("Отримано команду: \(command)")
}
}
Цей код поки що логічно неправильний: break тут виходить лише зі switch, а не з while. Це якраз привід зробити все правильно на наступному кроці.
Правильний вихід із циклу і case let для невідомої команди
Зробімо вихід із циклу через break уже в while, а всередині switch будемо змінювати прапорець. Це старий, зрозумілий і чесний спосіб, який не вимагає просунутих тем.
import Foundation
var isRunning = true
while isRunning {
print("Введіть команду (score/temp/exit): ", terminator: "")
let command = (readLine() ?? "").lowercased()
switch command {
case "exit":
print("Бувай!") // Бувай!
isRunning = false
case "score", "temp":
print("Гаразд, працюймо з \(command)")
case let unknown:
print("Невідома команда: \(unknown)")
}
}
Ось тут case let unknown — саме те, заради чого ми сьогодні зібралися: ми спіймали «все інше» й одразу отримали рядок, який можна показати користувачеві.
Команда "score": читаємо число і перевіряємо if case
Тепер додамо обробку оцінки. Припустімо, оцінка має бути від 0 до 100. Ми читаємо рядок, перетворюємо його на Int і перевіряємо діапазон через if case.
import Foundation
print("Введіть оцінку 0...100: ", terminator: "")
let score = Int(readLine() ?? "") ?? -1
if case 0...100 = score {
print("Оцінка дійсна: \(score)") // наприклад: Оцінка дійсна: 97
} else {
print("Невірна оцінка") // якщо введено щось не те
}
Зверніть увагу на значення за замовчуванням -1: його спеціально вибрано так, щоб він точно не пройшов перевірку. Так, це трохи хитро, але на поточному рівні це простий і робочий прийом.
Класифікація score через switch і case let
Те саме можна красиво класифікувати через switch, і при цьому іноді нам потрібно вивести саме значення.
import Foundation
let score = 97
switch score {
case 100:
print("Ідеально") // (не буде виведено)
case 90..<100:
print("Чудово") // Чудово
case 60..<90:
print("Добре")
case 0..<60:
print("Незадовільно")
case let s:
print("Невірна оцінка: \(s)")
}
Фішка тут у тому, що замість сухого default: print("Невірно") ми повідомляємо користувачеві, що саме надійшло (наприклад, -1 або 120). Це дрібниця, але вже перший крок до налагодження прямо в UX.
Команда "temp": використовуємо case let v where ...
Температуру класифікуємо за допомогою where, щоб показати правило без вкладеного if.
import Foundation
let t = -5
switch t {
case let v where v < 0:
print("Нижче нуля: \(v)") // Нижче нуля: -5
case 0:
print("Точно нуль")
case let v where v <= 25:
print("Комфортно: \(v)")
default:
print("Спекотно")
}
Тут два case let v where ... — і це нормально: ми просто використовуємо однакове ім’я v в різних гілках, бо область видимості в них різна.
7. Типові помилки під час використання case let і if case
Помилка №1: поставити case let x занадто рано й перехопити все.
Такий case підходить під будь-яке значення, тому якщо він стоїть вище за конкретні випадки (case 0, case 1...5, case 90..<100), ці випадки стають недосяжними. Лікується це дисципліною розташування: спочатку вузькі патерни, потім широкі.
Помилка №2: очікувати, що змінна з case let доступна після switch.
Змінна, оголошена в case, живе лише всередині відповідної гілки. Це не «глобальна змінна, оголошена всередині switch», а локальна змінна блока. Якщо вам потрібне значення зовні, значить, ви проєктуєте логіку так, що потрібне інше рішення (але зараз достатньо пам’ятати: область видимості обмежена).
Помилка №3: думати, що case var змінює початкову змінну.
case var x дає змінювану копію, а не доступ до початкового значення «за посиланням». Якщо ви збільшили x всередині гілки, це не означає, що змінна, за якою робили switch, теж змінилася. Перевіряйте це окремим print — і мозок швидко перестане «домислювати» магію.
Помилка №4: переплутати порядок в if case і писати «як у математиці».
Правильна форма: if case ПАТЕРН = ЗНАЧЕННЯ. Новачки часто намагаються написати навпаки, бо так звичніше. Тут допоможе лише звичка: читайте це як фразу «якщо цей патерн підходить значенню».
Помилка №5: намагатися перетворити switch на if, записуючи умови замість патернів.
Конструкція на кшталт case x > 0: не працює, бо це не патерн. Якщо вам потрібна додаткова логіка, використовуйте where, а якщо логіки занадто багато — іноді чесніше повернутися до if (але свідомо, а не тому що «switch не вміє»).
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ