Перечисления с ассоциированными значениями

Перечисление описывает значение, которое может быть одним из заранее известных вариантов. В Swift enum заметно мощнее, чем в большинстве языков.
Суть урока: enum в Swift умеет нести данные внутри каждого случая (ассоциированные значения). Это превращает его в идеальный инструмент для моделирования состояний экрана: загрузка, успех, ошибка.

Базовое перечисление — список вариантов:

enum Direction {
    case north, south, east, west
}
let move = Direction.north

switch move {
case .north: print("Вверх")
case .south: print("Вниз")
default: print("В сторону")
}

Случаям можно задать сырое значение (raw value) одного типа — удобно для кодов и идентификаторов:

enum Planet: Int {
    case mercury = 1, venus, earth, mars
}
print(Planet.earth.rawValue)        // 3
let p = Planet(rawValue: 4)         // Optional(.mars)

Но настоящая суперсила — ассоциированные значения: каждый случай может нести свои данные. Это позволяет точно описать состояние:

enum LoadingState {
    case idle
    case loading
    case success(data: [String])
    case failure(message: String)
}

let state = LoadingState.success(data: ["A", "B"])

switch state {
case .idle:            print("Ожидание")
case .loading:         print("Загрузка...")
case .success(let d):  print("Готово: \(d.count) элементов")
case .failure(let m):  print("Ошибка: \(m)")
}

Такой enum делает невозможные состояния невыразимыми: нельзя одновременно «грузиться» и «иметь ошибку». Компилятор гарантирует, что вы обработаете каждый случай.

Попробуй сам ▶ — запусти код прямо в браузере (Pyodide). Здесь нет Swift, но логика та же, что под капотом мобильного кода:

# Имитируем enum с ассоциированными значениями через кортеж (тег, данные).
def render(state):
    tag = state[0]
    if tag == 'idle':
        return 'Ожидание'
    elif tag == 'loading':
        return 'Загрузка...'
    elif tag == 'success':
        return f'Готово: {len(state[1])} элементов'
    elif tag == 'failure':
        return f'Ошибка: {state[1]}'

print(render(('idle',)))
print(render(('success', ['A', 'B'])))
print(render(('failure', 'нет сети')))

Как работает под капотом

Под капотом enum хранит компактный тег (какой случай выбран) и, при наличии, ассоциированные данные. Optional, который мы изучали, — это и есть enum с двумя случаями: .none и .some(Wrapped). Исчерпывающий switch заставляет обработать каждый вариант, а при добавлении нового случая компилятор подсветит все места, которые надо обновить — мощная страховка при рефакторинге.

Частые ошибки

  • Путать сырые и ассоциированные значения. Raw value — одинаковый тип для всех случаев; ассоциированные данные — свои у каждого случая.
  • Забыть let при извлечении данных. case .success(let d) — let связывает данные с переменной.
  • Добавлять default «на всякий случай». Это лишает вас подсказок компилятора при добавлении нового случая.

Best practices

  • Моделируйте состояния экрана через enum — это исключает невозможные комбинации.
  • Избегайте default для своих enum, чтобы компилятор напоминал о новых случаях.
  • Сырые значения удобны для сопоставления с кодами API и сериализации.

Итоги. Перечисления Swift с ассоциированными значениями — выразительный способ моделировать ограниченный набор состояний с данными. Они делают невозможные состояния непредставимыми и в паре с switch дают строгую, безопасную логику.

Шире контекста

Перечисления с ассоциированными значениями — это инструмент, который меняет сам подход к проектированию. Вместо того чтобы держать несколько отдельных булевых флагов и опционалов (isLoading, errorMessage, data) и молиться, чтобы они не разошлись, вы описываете одно перечисление, где каждое состояние самодостаточно и несёт ровно те данные, что ему нужны. Так невозможные комбинации (одновременно «грузится» и «ошибка») становятся буквально невыразимыми в типе — компилятор не даст их создать. Этот приём называют «делать недопустимые состояния непредставимыми», и он лежит в основе надёжной архитектуры. В сочетании с исчерпывающим switch вы получаете двойную страховку: и данные структурированы корректно, и каждый случай гарантированно обработан. Перечисления Swift по выразительности приближаются к алгебраическим типам данных из функциональных языков, оставаясь при этом понятными и практичными для повседневной мобильной разработки.

Проверьте себя
1. Что такое ассоциированное значение в enum?
AЧисловой код, одинаковый для всех случаев
BДополнительные данные, которые несёт конкретный случай
CИмя перечисления
DЗначение по умолчанию
2. Какой знакомый тип Swift на самом деле является enum?
AInt
BString
COptional
DArray