1. Проблема «значений по договорённости»
Когда программа маленькая, хочется хранить «состояния» и «режимы» как строки или числа: "admin", "guest", "exit", 1, 2, 3. Это кажется быстрым и простым… ровно до момента, пока где-то в коде не появится "admni" (опечатка) или 4 (никто уже не помнит, что это значит), и программа начнёт вести себя как кот, который делает вид, что вас не знает.
enum (enumeration, перечисление) — это способ сказать компилятору: «Смотри, допустимые варианты строго вот эти, ничего другого быть не может». И тогда компилятор становится вашим самым занудным, но полезным другом: он не даст присвоить «левое» значение и будет заставлять вас обрабатывать варианты явно.
Сравним подходы в одной маленькой таблице:
| Подход | Что хранится в переменной | Плюсы | Минусы |
|---|---|---|---|
| «строки по договорённости» | String (например "exit") | быстро начать | опечатки, нет контроля вариантов, трудно расширять безопасно |
| Int «коды» | Int (например 1 = exit) | быстро и компактно | магические числа, забывается смысл, легко перепутать |
| enum | один из заранее перечисленных case | типобезопасность, читаемость, помощь компилятора | надо один раз объявить тип (но это «плата за порядок») |
2. Что такое enum в Swift: фиксированный набор вариантов
Сейчас введём определение максимально по-человечески. enum — это тип, значения которого могут быть только одним из перечисленных вариантов (case). Это как меню в кафе: вы можете выбрать «чай» или «кофе», но не «криптовалюту» — даже если очень уверенно попросите.
В рамках этой лекции мы рассматриваем самые простые enum: без привязки к строкам/числам и без «дополнительных данных» внутри case. Наша цель — уверенно писать синтаксис enum, создавать значения, хранить их и передавать в функции.
Простейший пример:
import Foundation
enum Direction {
case north
case south
case east
case west
}
let d: Direction = .north
print(d) // north
Здесь важное: d не может стать “north-east” или "north" строкой. Только один из четырёх case — и точка.
3. Синтаксис и использование enum в коде
Синтаксис объявления: enum и case
Прежде чем вы начнёте использовать enum в задачах и проектах, важно привыкнуть к его «внешности». Объявление enum выглядит похоже на struct: сначала ключевое слово, затем имя типа, затем тело в фигурных скобках. Внутри — список case, то есть вариантов.
В Swift есть две популярные формы записи: по одному case на строку (очень читаемо) и компактная запись через запятую (короче). По смыслу они одинаковые.
Полная форма:
import Foundation
enum UserRole {
case guest
case member
case admin
}
Компактная форма:
import Foundation
enum UserRole {
case guest, member, admin
}
Рекомендации по именованию тут такие же, как у типов и переменных. Имя enum — это тип, поэтому UpperCamelCase. Имена cases — это варианты, поэтому lowerCamelCase. Так код читается естественно, и Swift-стиль получается «родным».
Создание значения: UserRole.admin и синтаксис .admin
Когда вы впервые видите .admin, возникает вопрос: «Почему точка? Это что, доступ к свойству?». В некотором смысле — да: case действительно является членом типа, и это можно воспринимать как «статический член».
В Swift принят удобный стиль: если компилятор уже знает тип, можно писать case через leading dot (.admin) вместо UserRole.admin. Эта идея тесно связана с тем, что case — член типа, а не «просто слово».
Посмотрим на оба варианта:
import Foundation
enum UserRole { case guest, member, admin }
let a = UserRole.admin
let b: UserRole = .admin
print(a) // admin
print(b) // admin
Почему во втором случае можно .admin? Потому что мы явно указали тип переменной b: UserRole. У переменной есть контекст, и компилятор понимает, что .admin относится именно к UserRole.
А вот так — нельзя, потому что нет контекста:
import Foundation
enum UserRole { case guest, member, admin }
// let x = .admin // ❌ ошибка: компилятор не знает, чей это case
И это справедливо: .admin может существовать в десятке разных enum’ов, и компилятор не обязан угадывать ваши мысли (иначе это был бы уже психологический сервис, а не компилятор).
enum как обычное значение: переменные, массивы и словари
Новичкам иногда кажется, что enum — это что-то «для switch» и больше ни для чего. На деле enum — это обычное значение языка: вы можете хранить его в переменной, класть в массив, передавать в словарь, возвращать из функции. Это такой же «кусочек данных», как Int или String, просто с ограниченным набором возможных значений.
Пример с массивом:
import Foundation
enum PaymentMethod { case cash, card }
let methods: [PaymentMethod] = [.cash, .card, .cash]
print(methods.count) // 3
Пример со словарём (ключ — String, значение — enum):
import Foundation
enum ThemeMode { case light, dark }
let userSettings: [String: ThemeMode] = [
"alex": .dark,
"kate": .light
]
print(userSettings["alex"]!) // dark
Да, тут стоит !, и в реальном коде мы бы так не делали без проверки. Но идея примера простая: enum хранится как значение, и словарь спокойно его принимает.
Передача enum в функцию: читаемость и меньше ошибок
Функции — идеальное место, чтобы почувствовать пользу enum. Когда параметр функции имеет тип enum, вы больше не принимаете «абстрактную строку», в которой может быть что угодно. Вы принимаете один из допустимых вариантов. Это резко уменьшает число проверок, опечаток и «непонятных состояний».
Пример: печатаем приветствие по роли.
import Foundation
enum UserRole { case guest, member, admin }
func printGreeting(for role: UserRole) {
print("Hello, \(role)!")
}
printGreeting(for: .admin) // Hello, admin!
Обратите внимание: в вызове мы пишем .admin, потому что тип параметра role: UserRole задаёт контекст, и это читается красиво. Именно такой leading dot синтаксис стал стандартным способом упоминать enum cases в местах, где тип уже известен.
4. Мини-пример: команды CLI через enum
Сейчас будет небольшой, но очень практичный мостик к учебному CLI-приложению. Представьте, что пользователь вводит команду: help, list, exit. Пока что (наивный подход) мы могли бы сравнивать строки. Но уже на этом этапе полезно сделать шаг к типобезопасности: описать набор команд через enum.
Важно: в этой лекции мы не привязываем команды к строкам автоматически и не используем «продвинутые» техники. Мы просто моделируем допустимые варианты и вручную переводим строку в case.
Сначала объявим enum:
import Foundation
enum Command {
case help
case list
case exit
case unknown
}
Теперь сделаем функцию, которая получает строку и решает, что это за команда:
import Foundation
enum Command { case help, list, exit, unknown }
func parseCommand(_ text: String) -> Command {
switch text {
case "help": return .help
case "list": return .list
case "exit": return .exit
default: return .unknown
}
}
И используем это в мини-цикле:
import Foundation
enum Command { case help, list, exit, unknown }
let input = "help"
let cmd = parseCommand(input)
print(cmd) // help
Здесь unknown — это наш способ сохранить факт «непонятная команда» как легальный вариант состояния. Не идеальный, но на раннем этапе обучения — честный и понятный.
Чтобы увидеть процесс целиком, вот маленькая схема (она полезна, когда начинаете проектировать CLI):
flowchart TD
A["Строка от пользователя (readLine)"] --> B["parseCommand(text)"]
B --> C["Command enum"]
C --> D["switch cmd { ... }"]
D --> E["Действие программы (help/list/exit)"]
Когда вы используете enum, вы переносите часть ответственности с «памяти программиста» на компилятор. Раньше нужно было помнить, какие строки допустимы, и следить, чтобы они совпадали везде. Теперь компилятор знает, сколько вариантов существует, и не даст вам «случайно придумать новый».
Ещё один важный эффект: enum отлично работает в паре с switch. Когда вы начнёте писать switch по enum (подробно будет в следующих лекциях), Swift будет подталкивать вас к исчерпывающей обработке вариантов. В целом философия языка такая: «лучше поймать ошибку при компиляции, чем пользователю поймать её в проде».
А leading dot синтаксис (.caseName) — это часть того же подхода: сделать код короче, но только там, где он остаётся однозначным. Это подчёркивает мысль «это case, а не произвольное слово».
5. Типичные ошибки при использовании enum и cases
Ошибка №1: продолжать хранить режимы как String, потому что «так проще».
Обычно это происходит из лучших побуждений: хочется быстрее увидеть результат. Но затем в коде появляются проверки "Exit", "exit", "EXIT", и вы внезапно изобрели три разные команды, сами того не планируя. enum решает эту проблему в корне: вариантов конечное число, и вы обязаны выбрать один из них.
Ошибка №2: писать .someCase без контекста типа и не понимать, почему компилятор ругается.
Leading dot синтаксис работает только тогда, когда компилятор уже знает, какой тип ожидается. Если вы пишете let x = .admin, то тип неизвестен, и Swift не должен угадывать. Лечение простое: либо указывать тип переменной (let x: UserRole = .admin), либо писать полное имя (UserRole.admin).
Ошибка №3: путать разные enum, у которых совпадают имена cases.
Например, у вас есть UserRole.admin и ещё где-то AccessLevel.admin. Если типы разные, то и смысл может быть разный. Обычно проблема проявляется, когда вы держите переменные «где-то рядом» и начинаете копировать код. Спасает привычка смотреть на тип переменной и не злоупотреблять слишком общими именами.
Ошибка №4: превращать enum в «контейнер для всего на свете» уже на первом шаге.
Иногда хочется сразу засунуть в enum и команды, и аргументы, и ошибки, и состояния, и настроение кота в офисе. На раннем этапе обучения лучше держать enum простым: фиксированный набор вариантов. Мы ещё дойдём до более мощных вариантов, но сейчас важнее почувствовать, как enum дисциплинирует код, а не усложняет его.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ