Сопоставление с образцом в Swift
Что такое pattern matching
Сопоставление с образцом — способ проверить значение и одновременно извлечь из него части — связать переменные, сравнить с константой, отфильтровать диапазон. В Swift это поведение switch, if, guard, for-in и присваивания.
Главная польза для прикладного кода:
- меньше вложенных
ifи принудительных!; - компилятор требует исчерпывающие ветки для enum и многих
switch; - единый стиль для опционалов и кортежей и перечислений.
Базовый switch уже разобран в управляющих конструкциях; здесь — расширенные приёмы.
switch без провала между case
В Swift нет неявного fallthrough между case (если не указан fallthrough явно). Каждый case завершается сам по себе — это убирает класс ошибок из C/Java.
Исчерпывающность: для enum без default компилятор проверяет, что обработаны все случаи. При добавлении нового case в enum проект не соберётся, пока вы не обновите switch — защита при эволюции API.
enum Status {
case idle, loading, ready, failed
}
func label(for status: Status) -> String {
switch status {
case .idle: return "Ожидание"
case .loading: return "Загрузка"
case .ready: return "Готово"
case .failed: return "Ошибка"
}
}
Разбор:
enum Statusзадаёт фиксированный набор допустимых состояний.- Функция
label(for:)возвращает строку для каждого case черезswitch. - Отсутствие
defaultзаставляет компилятор проверить полноту обработки всех вариантов. - Такой подход делает изменения enum безопасными: новый case потребует обновить обработчик.
Связка значений и where
В case можно привязать части значения к локальным константам:
let point = (x: 3, y: 4)
switch point {
case (0, 0):
print("начало координат")
case let (x, 0):
print("на оси X: \(x)")
case let (0, y):
print("на оси Y: \(y)")
case let (x, y) where x == y:
print("диагональ (\(x), \(y))")
default:
print("обычная точка")
}
Разбор:
point— кортеж с именованными полямиxиy.- В
switchиспользуются шаблоны — точное совпадение, захват значений черезlet, иdefault. - Конструкция
where x == yдобавляет дополнительное условие после успешного сопоставления. - Пример показывает, как pattern matching одновременно проверяет форму данных и извлекает полезные части.
where добавляет произвольное условие к уже сопоставленному шаблону.
Enum с associated values
Перечисление может нести данные в каждом случае — удобная модель "результат операции":
Код ITЗагрузка примера кода…
Разбор:
LoadStateхранит не только статус, но и связанные данные (progress,data,message).- В
switchзначения associated values извлекаются черезlet. String(format: ...)форматирует прогресс в проценты, аdata.countдаёт размер полученных данных.- Модель состояния в виде enum помогает избежать разрозненных флагов и противоречивых комбинаций.
Один switch заменяет цепочку if let + проверок типа.
Опционал как enum
Optional — это enum с .none и .some(Wrapped). Поэтому к опционалам применимы те же приёмы.
if let / guard let
Самый частый паттерн — распаковка с узкой областью видимости (if let) или ранний выход (guard let). См. типы данных.
Код ITЗагрузка примера кода…
let response: LoadState = .loaded(data: Data())
if case .loaded(let data) = response {
process(data)
}
Разбор:
if caseпроверяет один конкретный шаблон без полногоswitch.- При совпадении case
.loadedпеременнаяdataавтоматически извлекается из associated value. - Такой синтаксис компактен для точечных проверок конкретного состояния.
Аналог "если статус именно loaded — достать data".
guard case let
Тот же смысл с ранним выходом:
func requireData(from state: LoadState) throws -> Data {
guard case .loaded(let data) = state else {
throw AppError.notReady
}
return data
}
Разбор:
guard caseпроверяет нужный шаблон и обеспечивает ранний выход при несоответствии.- В ветке
elseвыбрасывается доменная ошибкаAppError.notReady. - После
guardкомпилятор знает, чтоdataдоступна, и функция возвращает её без дополнительных проверок.
switch по опционалу
let code: Int? = 404
switch code {
case .none:
print("код не задан")
case .some(200):
print("OK")
case .some(let c) where c >= 400:
print("ошибка \(c)")
default:
print("другой код")
}
Разбор:
Int?сопоставляется как enumOptional:.noneили.some(...).- Конкретный статус
200выделен отдельной веткой для приоритетной обработки. where c >= 400фильтрует диапазон ошибок, одновременно сохраняя код в переменнойc.- Пример полезен, когда нужно выразить несколько бизнес-веток для optional-значения.
На практике для опционалов чаще if let / ?? / ?.; switch уместен, когда вариантов много.
Сопоставление в цикле for-in
Фильтрация с распаковкой:
let states: [LoadState] = [.idle, .loading(progress: 0.5), .failed(message: "timeout")]
for case .loading(let p) in states {
print("прогресс \(p)")
}
Разбор:
for caseобъединяет фильтрацию и итерацию: цикл заходит только в элементы, подходящие под шаблон.loading.- Значение
progressизвлекается вpи сразу используется в теле цикла. - Такой стиль убирает лишние
if caseвнутри обычногоfor.
Обрабатываются только элементы, подходящие под шаблон.
Result и цепочка ошибок
Result<Success, Failure> — enum для успеха/неудачи. Его разбирают тем же switch:
func handle(_ result: Result<User, NetworkError>) {
switch result {
case .success(let user):
showProfile(user)
case .failure(let error):
showAlert(error.localizedDescription)
}
}
Разбор:
Result<User, NetworkError>явно кодирует два исхода: успех сUserили ошибкаNetworkError.- В
switchдля.successи.failureизвлекаются значения и передаются в соответствующие UI-функции. - Это типобезопасная альтернатива неявным флагам или nullable-данным для обработки ответа.
Связка с обработкой ошибок: throws + do/catch для исключений, Result — для явного значения в типе возврата.
Когда что выбирать
| Задача | Инструмент |
|---|---|
| Все варианты enum | switch без default |
| Один конкретный case | if case let / guard case let |
| Есть / нет значения | if let, guard let, ?? |
| Фильтр в коллекции | for case, compactMap |
| Ошибка как значение | Result + switch |
Типичные ошибки
defaultтам, где enum фиксирован — скрывает забытый новыйcaseпри рефакторинге.- Принудительный
!вместо паттерна — краш вместо исчерпывающей ветки. - Слишком общий
case let x— теряется смысл; лучше именованные associated values. - Дублирование логики в
if caseиswitch— вынести в одну функцию сswitch.
Связанные материалы
Практика применения pattern matching
Наибольшая польза этой техники проявляется в коде, где много состояний и переходов — загрузки данных, навигация, обработка команд.
Где использовать в первую очередь
enumсостояния экрана (idle/loading/success/failure).- Разбор
Resultиз сети или базы. - Фильтрация массивов enum-значений через
for case.
Рекомендации по стилю
- Для фиксированного
enumиспользуйтеswitchбезdefault. - Давайте осмысленные имена associated values (
case failed(message: String)). - Большие
case-ветки выносите в отдельные функции.
Связанные темы
Сквозной мини-кейс — экран оплаты с enum состояния
Модель состояния экрана и одна функция отображения:
enum CheckoutState {
case idle
case validating
case ready(total: Decimal, currency: String)
case processing
case success(orderID: String)
case failure(message: String)
}
@MainActor
func uiMessage(for state: CheckoutState) -> String {
switch state {
case .idle:
return "Введите данные карты"
case .validating:
return "Проверяем форму…"
case let .ready(total, currency):
return "К оплате: \(total) \(currency)"
case .processing:
return "Обрабатываем платёж…"
case let .success(orderID):
return "Заказ \(orderID) оформлен"
case let .failure(message):
return message
}
}
Разбор:
CheckoutStateмоделирует полный жизненный цикл оплаты в одном enum.- Функция
uiMessage(for:)на@MainActorгарантирует безопасное обновление UI на главном потоке. switchпокрывает все состояния — ввод, проверка, готовность, обработка, успех и ошибка.- Ветви с
letизвлекают полезные данные (total,currency,orderID,message) для пользовательских сообщений. - Такой подход упрощает сопровождение экрана: вместо множества булевых флагов есть один источник истины — текущее состояние.
Компилятор заставит обработать каждый case. При добавлении .cancelled проект не соберётся, пока ветку не допишут — это защита при эволюции API.
Для одного частного случая вместо полного switch уместен if case let .failure(message) = state.
Анти-паттерны и типичные ошибки
| Ошибка | Что происходит | Как лучше |
|---|---|---|
default в switch по enum | новый case молча попадает в default | убрать default, обработать все cases |
Цепочка if let из 5 уровней | "пирамида" вложенности | guard let или один switch |
value! после if case | краш при ошибке модели | guard case let с throw |
Одинаковая логика в 3 if case | дублирование | общий switch в функции |
switch по String вместо enum | опечатки в runtime | enum + associated values |
Забытый break в C-стиле | в Swift не нужен; путаница у новичков | каждый case — отдельный блок |
Расширяет раздел "Типичные ошибки" в начале этой статьи.