Перейти к основному содержимому

Сопоставление с образцом в Swift

Разработчику Архитектору

Что такое pattern matching

Сопоставление с образцом — способ проверить значение и одновременно извлечь из него части: связать переменные, сравнить с константой, отфильтровать диапазон. В Swift это не отдельный оператор, а поведение switch, if, guard, for-in и присваивания.

Главная польза для прикладного кода:

Базовый 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 "Ошибка"
}
}

Связка значений и 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("обычная точка")
}

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


Enum с associated values

Перечисление может нести данные в каждом случае — удобная модель «результат операции»:

enum LoadState {
case idle
case loading(progress: Double)
case loaded(data: Data)
case failed(message: String)
}

func describe(_ state: LoadState) -> String {
switch state {
case .idle:
return "нет данных"
case .loading(let progress):
return String(format: "%.0f%%", progress * 100)
case .loaded(let data):
return "получено \(data.count) байт"
case .failed(let message):
return message
}
}

Один switch заменяет цепочку if let + проверок типа.


Опционал как enum

Optional — это enum с .none и .some(Wrapped). Поэтому к опционалам применимы те же приёмы.

if let / guard let

Самый частый паттерн — распаковка с узкой областью видимости (if let) или ранний выход (guard let). См. типы данных.

if case let — один случай без полного switch

Когда интересен один вариант:

let response: LoadState = .loaded(data: Data())
if case .loaded(let data) = response {
process(data)
}

Аналог «если статус именно loaded — достать data».

guard case let

Тот же смысл с ранним выходом:

func requireData(from state: LoadState) throws -> Data {
guard case .loaded(let data) = state else {
throw AppError.notReady
}
return 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("другой код")
}

На практике для опционалов чаще 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)")
}

Обрабатываются только элементы, подходящие под шаблон.


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)
}
}

Связка с обработкой ошибок: throws + do/catch для исключений, Result — для явного значения в типе возврата.


Когда что выбирать

ЗадачаИнструмент
Все варианты enumswitch без default
Один конкретный caseif case let / guard case let
Есть / нет значенияif let, guard let, ??
Фильтр в коллекцииfor case, compactMap
Ошибка как значениеResult + switch

Типичные ошибки

  1. default там, где enum фиксирован — скрывает забытый новый case при рефакторинге.
  2. Принудительный ! вместо паттерна — краш вместо исчерпывающей ветки.
  3. Слишком общий case let x — теряется смысл; лучше именованные associated values.
  4. Дублирование логики в if case и switch — вынести в одну функцию с switch.

Связанные материалы


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).