1. Іноді варто сховати логіку всередині enum
Коли ви починаєте писати програми, дуже легко скотитися до стилю «усе в одному місці»: прочитали введення, написали величезний switch, у кожній гілці щось вивели, десь змінили змінну, десь ще раз перевірили умову… Працює? Працює. Але через кілька днів такий код починає нагадувати коробку з дротами: потрібний дріт начебто є, але спробуйте знайти його — і бажано без іскор та удару струмом.
enum зручний тим, що не лише обмежує варіанти, а й підказує спосіб організації коду: якщо в нас є варіанти, то в кожного з них може бути свій зміст. І дуже зручно зберігати цей зміст поруч із варіантом. Тоді замість десяти switch-ів по всьому проєкту у вас буде один — усередині enum. А якщо ви додасте новий case, компілятор «підсвітить» місця, де потрібно оновити логіку.
Уявіть enum не як «табличку з написами», а як «перемикач режимів» у пристрої. Перемикач не лише перелічує режими, а й визначає, що робить кожен із них: як виглядає, як називається, що дозволяє, який наступний режим тощо.
Обчислювана властивість у enum: значення «на льоту»
Обчислювана властивість (computed property) — це властивість, яка не має окремого місця в пам’яті для значення. Натомість під час звернення до неї виконується код, який обчислює результат. По суті, це «функція без дужок», але з дуже важливою домовленістю: властивість має виглядати як просте читання даних і не дивувати вас побічними ефектами.
Синтаксис ви вже бачили в struct:
var name: String {
return ...
}
У enum усе точно так само. Найчастіше так роблять, коли потрібно обчислити щось залежно від поточного case. Наприклад, ми робимо невеликий консольний застосунок, який поступово вдосконалюватимемо далі в курсі, де користувач вводить команду. Команда — це enum, а в кожної команди є «зрозуміла для людини» назва.
import Foundation
enum LibraryCommand {
case help, list, add, exit
var title: String {
switch self {
case .help: return "Довідка"
case .list: return "Список книжок"
case .add: return "Додати книжку"
case .exit: return "Вихід"
}
}
}
Зверніть увагу на приємний ефект: ззовні вам не потрібно пам’ятати, як саме красиво назвати команду. Ви пишете cmd.title і отримуєте акуратний текст. А найголовніше — якщо завтра з’явиться .remove, компілятор змусить вас оновити title, оскільки switch має бути вичерпним.
2. Головний прийом: switch self як таблиця відповідностей
Коли ви вперше бачите switch self, він може виглядати трохи дивно: «Я що, роблю switch по самому собі?». Так. Саме так. self усередині enum — це поточне значення, тобто один із case-ів. І це перетворює обчислювану властивість на дуже зрозумілу «табличку правил»: case → результат.
Давайте зробимо властивість, яка повертає рядок-підказку, як користуватися командою. У нашому навчальному CLI це буде коротка довідка.
import Foundation
enum LibraryCommand {
case help, list, add, exit
var usage: String {
switch self {
case .help: return "help — показати довідку"
case .list: return "list — вивести список книжок"
case .add: return "add — додати книжку (поки без параметрів)"
case .exit: return "exit — вийти з програми"
}
}
}
Чому це зручно саме як обчислювана властивість, а не як окрема функція usage(for:) десь зовні? Тому що тоді usage «належить» команді. Команда сама знає, як вона виглядає для користувача.
Тут важливо відчути стиль Swift: «дані й поведінка поруч». Ми вже робили це в struct, а enum анітрохи не гірший — це такий самий повноцінний тип.
4. Методи в enum: коли обчислюваних властивостей уже замало
Обчислювана властивість добра, коли ви просто повертаєте значення і вам не потрібні аргументи. Але щойно з’являється потреба прийняти параметр, наприклад стан застосунку, зробити дію або повернути результат обчислення на основі вхідних даних, вам потрібен метод.
Метод усередині enum оголошують так само, як і в struct:
func імʼя(параметри) -> Повернення { ... }
Повернімося до нашого LibraryCommand. Уявімо, що ми хочемо виконати команду й отримати текст, який потрібно вивести користувачеві. Стан програми поки що простий: список книжок і прапорець, чи треба продовжувати цикл.
Спочатку оголосімо стан:
import Foundation
struct LibraryState {
var books: [String] = []
var isRunning: Bool = true
}
Тепер додамо метод run(state:) всередину enum:
import Foundation
enum LibraryCommand {
case help, list, add, exit
func run(state: inout LibraryState) -> String {
switch self {
case .help:
return "Доступні команди: help, list, add, exit"
case .list:
return state.books.isEmpty ? "Список порожній." : state.books.joined(separator: ", ")
case .add:
state.books.append("Нова книжка")
return "Додано: Нова книжка"
case .exit:
state.isRunning = false
return "Бувай!"
}
}
}
Тут відбувається кілька важливих речей, які варто озвучити.
По-перше, enum не просто визначає варіанти. Він стає «об’єктом поведінки»: команда сама вміє виконуватися.
По-друге, метод приймає inout-параметр state, бо він змінює стан: додає книжку, завершує цикл. Ми не трактуємо це як «передавання за посиланням» у містичному сенсі, але розуміємо практично: функція отримує право змінити змінну, яку ви передали через &.
По-третє, switch по self знову стає центром логіки. І якщо додасться новий case, компілятор знову змусить оновити run.
5. mutating методи в enum: коли змінюється сам enum
Дуже часто enum моделює не лише «команду», а й «стан». Наприклад, рівень гучності, статус замовлення, режим застосунку. У таких моделях зручно змінювати саме значення enum: було .mute, стало .low.
У Swift це робиться через mutating-метод. Причина проста: enum — це тип-значення. Щоб змінити значення, ви фактично присвоюєте нове значення в self.
Приклад із рівнем гучності — невеликий, але дуже показовий:
import Foundation
enum VolumeLevel {
case mute, low, high
mutating func increase() {
switch self {
case .mute: self = .low
case .low: self = .high
case .high: self = .high
}
}
}
І використання:
import Foundation
var volume: VolumeLevel = .mute
volume.increase()
print(volume) // low
Чому це корисно саме для початківців? Тому що воно дуже наочно пояснює: enum — це значення. Щоб змінити значення, ми замінюємо його цілком на інше допустиме значення. Приблизно як перемикач режимів: ви не «підкручуєте» .mute, ви переводите ручку в .low.
6. Мінізастосунок: прибираємо великий switch із main
У реальному коді початківця це часто виглядає так: у main величезний switch, який робить усе. Давайте акуратно зберемо цикл команд так, щоб main був диспетчером, а не водночас кухнею, бухгалтерією й відділом кадрів.
Зробімо LibraryCommand з raw values і CaseIterable, щоб можна було розбирати введення і показувати довідку. Raw values ви вже знаєте з попередньої лекції, сьогодні ми використаємо їх як зручний «рядок команди».
import Foundation
enum LibraryCommand: String, CaseIterable {
case help, list, add, exit
}
Додамо обчислювану властивість, яка красиво друкує команди в підказці:
import Foundation
enum LibraryCommand: String, CaseIterable {
case help, list, add, exit
static var helpText: String {
allCases.map { $0.rawValue }.joined(separator: ", ")
}
}
Тут важливий момент: helpText — це static обчислювана властивість. Це не про конкретну команду, а про тип загалом, тобто про весь набір команд. Ми використовуємо allCases, отже працюємо з переліком варіантів.
Тепер додамо той самий метод run(state:):
import Foundation
enum LibraryCommand: String, CaseIterable {
case help, list, add, exit
func run(state: inout LibraryState) -> String {
switch self {
case .help:
return "Команди: \(Self.helpText)"
case .list:
return state.books.isEmpty ? "Список порожній." : state.books.joined(separator: ", ")
case .add:
state.books.append("Нова книжка")
return "Гаразд."
case .exit:
state.isRunning = false
return "Виходимо."
}
}
}
Зверніть увагу: тут ми рознесли щільні однорядкові гілки на кілька рядків — так код простіше читати й розширювати, навіть якщо приклад навчальний.
І тепер main стає дуже компактним і зрозумілим:
import Foundation
var state = LibraryState()
while state.isRunning {
let input = readLine() ?? ""
let cmd = LibraryCommand(rawValue: input) ?? .help
print(cmd.run(state: &state))
}
Оце відчуття «магії порядку» — не магія, а проста дисципліна: логіка команд живе в enum, а цикл просто читає введення, парсить і передає виконання далі.
7. Корисні нюанси: виведення в рядок і вибір між властивістю та методом
CustomStringConvertible: робимо print(enum) по-людськи
Коли ви робите print(someEnum), Swift має перетворити значення на рядок. У простих випадках він друкує назву типу та кейсу, але іноді хочеться контролювати виведення. Для цього є протокол CustomStringConvertible і властивість description.
Важливо розуміти: print і взагалі перетворення на рядок спираються на ідею «тип може сам дати текстове представлення», і одним зі стандартних шляхів тут є якраз CustomStringConvertible.
Зробімо так, щоб команди друкувалися як їхній raw value, а не як щось технічне:
import Foundation
enum LibraryCommand: String, CaseIterable, CustomStringConvertible {
case help, list, add, exit
var description: String {
rawValue
}
}
Тепер:
import Foundation
let cmd: LibraryCommand = .list
print(cmd) // list
Чому це зручно? Тому що ви можете друкувати команду в повідомленнях користувачеві, логах або підказках, і вона виглядатиме однаково всюди. І це знову «логіка поруч із типом»: команда сама визначає, як вона перетворюється на текст.
Що обрати: обчислювану властивість чи метод
Коли у вас з’являється можливість «додати поведінку в enum», перша реакція інколи така: «О, тепер усе зроблю properties!». А потім з’являється властивість, яка читає файл, робить мережевий запит і ще заодно змінює стан… і ви раптово розумієте, що властивості були не для цього.
Зафіксуймо просте правило: обчислювана властивість добре підходить, коли вона виглядає як «характеристика» і не потребує вхідних даних. Метод кращий, коли є параметри, дія або сенс ближчий до дієслова.
Невелика таблиця — очі скажуть вам дякую:
| Ситуація | Краще обчислювана властивість | Краще метод |
|---|---|---|
Потрібно повернути текст або число залежно від поточного |
|
можливо, але надмірно |
Потрібно прийняти аргумент, наприклад |
незручно або неможливо | |
| Потрібен побічний ефект (змінити стан) | краще уникати | метод — природніший вибір |
Потрібно змінити і перемкнути стан |
зазвичай не годиться | |
Властивості — це про «який ти є». Методи — це про «що ти вмієш робити». Якщо тримати це в голові, код виходить передбачуваним навіть для людини, яка бачить його вперше, наприклад для вас через тиждень.
8. Типові помилки
Помилка №1: робити обчислювану властивість із «сюрпризами».
Початківці інколи пишуть var message: String { ... }, а всередині змінюють глобальні змінні, друкують у консоль, додають елементи в масив і взагалі поводяться як метод. Це ламає очікування: властивість виглядає як «просто читання», а фактично робить дію. Краще чесно назвати це методом (func makeMessage() -> String або func run(...) -> String) і не вводити читача в оману.
Помилка №2: ховати пропущені cases за default.
Здається зручним: «та гаразд, зроблю default: return "???"». Але ви тим самим вимикаєте допомогу компілятора. Уся сила enum у тому, що під час додавання нового case компілятор змушує пройтися по важливих місцях. Якщо ви пишете default, ви кажете: «компіляторе, не заважай мені помилятися». Зазвичай це невдала угода.
Помилка №3: забути mutating, коли змінюєте self.
Якщо всередині методу ви робите self = .something, Swift справедливо вимагає mutating. Це не прискіпування: так мова підкреслює, що value type змінюється. В enum-станах (гучність, статус, режим) це трапляється постійно.
Помилка №4: розносити «таблицю відповідностей» по всьому проєкту.
Класика: в одному місці switch cmd { case .help: ... }, в іншому — ще один switch cmd, і в третьому — ще один. Через тиждень ви додаєте новий case і забуваєте оновити один зі switch-ів. Якщо ви вже доросли до ідеї «команда — це enum», логічно дійти до ідеї «команда сама знає, як виконуватися» і тримати основну логіку всередині enum.
Помилка №5: плутати raw value і «людськочитабельний текст».
Raw value — це найчастіше формат, тобто те, що користувач вводить, або те, що зберігається як стабільний ідентифікатор. А красивий текст — це те, що ви показуєте людині. Іноді вони збігаються, іноді ні. Якщо ви почнете використовувати rawValue як UI-текст, то потім буде боляче, коли захочете локалізацію або дружніші підписи. Добра звичка: raw value залишати «для машини», а для людини робити окрему обчислювану властивість на кшталт title або usage.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ