Перечисления (enum) в Swift: ассоциированные и чистые значения

С помощью перечислений вы можете организовывать группы связанных значений. Они идеально подходят для управления состоянием в вашем приложении и позволяют сделать ваш код более удобным и безопасным.

Что такое перечисление (enum)?

Перечисление представляет собой группу значений, которые связаны между собой.

Хорошим примером являются направления на компасе:

enum Compass {
    case north
    case east
    case south
    case west
}

Перечисления помогают нам работать со связанными значениями наиболее безопасным способом.

Проще всего рассматривать перечисления как структурированные списки связанных элементов. К примеру:

  • Цвета: красный, зеленый, синий, фиолетовый, желтый.
  • Вкусы мороженого: ваниль, шоколад, клубника.
  • Состояния аутентификации: .authenticated, .unauthenticated, .undetermined.
  • Состояния воспроизведения: .playing, .paused, .stoppped.

Синтаксис перечислений довольно прост:

  • Мы используем ключевое слово enum для объявления перечисления с используем конструкцию enum имя { тело }.
  • В теле перечислений мы включаем ряд элементов перечисления с помощью ключевого слова case. Варианты перечисления — это разные значения, которые у него есть, например, вышеупомянутые ароматы мороженого.
  • Перечисления могут иметь ассоциированные значения (associated types), а также чистые значения (raw value).

Мы можем определить все перечисления в одной строке:

case Weekday {
    case monday, tuesday, wednesday, thursday, friday, saturday, sunday
}

Использовать перечисление довольно просто:

let today: Weekday = Weekday.monday

Приведенный выше код можно сократить:

let today: Weekday = .monday

Также можно использовать:

let today = Weekday.monday

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

var direction: Compass = .north
 
direction = .south

Перечисления нельзя изменить после установки.

enum AuthenticationState {
    case authenticated
    case unauthenticated
    case undetermined
}

Значения перечисления являются исчерпывающими, и вы можете быть уверены, что любое значение типа AuthenticationState является любым из трех значений. Это обеспечивает безопасность в вашем коде и приводит к меньшему количеству ошибок.

Перечисления, состояния и switch

Перечисления эффективно комбинировать с оператором switch. Возможно, вы слышали о понятии «состояние» раньше. В разработке для iOS мы часто управляем состоянием различных параметров и взаимодействий в нашем приложении.

Представьте, что мы кодируем робота. У робота есть эмоции. Мы моделируем эти эмоции или состояния с помощью перечисления:

enum Emotion {
    case happy, sad, angry, scared, surprised
}

Теперь мы можем использовать оператор switch, чтобы реагировать на эти эмоции:

switch robot.mood {
case .angry:
    robot.destroyAllHumans()
case .sad:
    robot.rust() // Робот плачет и ржавеет.
case .happy:
    robot.play("happy.mp3")
case default:
    print("Неизвестная эмоция.")
}

Каждый case в блоке switch соответствует отдельному case в перечислении.

Ассоциированные значения (associated values) в перечислениях

Перечисления позволяют вам присвоить одно из предопределенных значений переменной или константе. Также мы можем связать значения с отдельными случаями перечисления.

Представьте, что мы создаем приложение для ролевой игры (RPG). Игрок может добавить в свой инвентарь несколько типов предметов. Каждый предмет имеет разные свойства. Например:

  • У оружия есть очки урона и вес.
  • У еды есть очки здоровья.
  • У брони есть очки защиты, вес и состояние.

Вот как это выглядит с ассоциированными значениями:

enum Item {
    case weapon(Int, Int)
    case food(Int)
    case armor(Int, Int, Double)
}
 
var sword = Item.weapon(10, 5)

В каждом case перечисления мы указываем дополнительные типы в круглых скобках, которые называются кортежами. Вы можете присвоить им любое значение.

Далее мы можем написать функцию для использования определенного предмета из инвентаря игрока:

func use(item: Item) {
    switch item {
    case .weapon(let hitPoints, _):
        player.attack(hitPoints)
    case .food(let health):
        player.health += health
    case .armor(let damageThreshold, let weight, let condition):
        player.damageThreshold = Double(damageThreshold) * condition
    }
}

В приведенном выше коде мы разбиваем значения кортежей на отдельные константы. К примеру, константа hitPoints соответствует первому значению Int для case weapon(Int, Int).

Если вы хотите получить доступ ко всем ассоциированным значениям, вы можете использовать одиночный let:

switch item {
    case let .armor(damageThreshold, weight, condition):
        player.damageThreshold = ···
}

Вы также можете сопоставлять ассоциированные значения:

let item = Item.armor(15, 10, 0.75)
 
switch item {
case let .armor(damageThreshold, weight, condition) where weight < 10:
    print("DT = \(Double(damageThreshold) * condition)")
case .armor:
    print("Броня слишком тяжелая!")
default:
    print("Это не броня.")
}
 
// Броня слишком тяжелая!

В приведенном выше коде мы используем ключевое слово where для сопоставления .armor случаев, когда weight меньше 10. Во втором случае мы сопоставляем любой .armor случай, который не соответствует первому, то есть любой, для которого weight больше или равен 10.

В Swift часто встречаются перечисления с ассоциированными значениями. Возьмем, к примеру, перечисление Result для Alamofire, популярной сетевой библиотеки.

public enum Result<Value> {
    case success(Value)
    case failure(Error)
 
    ···
 
    public var value: Value? {
        switch self {
        case .success(let value):
            return value
        case .failure:
            return nil
        }
    }
}

В перечислении Result используется дженерик Value и протокол Error для представления двух возможных состояний: результат успешного или неудачного запроса. Когда запрос выполнен успешно, мы можем получить Value, а когда он потерпел неудачу, мы можем получить Error.

Вычисляемое свойство value перечисления Result возвращает опциональное значение Value?. Мы используем оператор switch для возврата значения или nil.

Использование чистых значений (raw values) для перечислений

Вместо связанных значений перечисления могут иметь чистые значения:

enum Flavor: String {
    case vanilla = "vanilla"
    case strawberry = "strawberry"
    case chocolate = "chocolate"
}

Каждый случай перечисления Flavor использует значения типа String.

Посмотрим, как можно работать с чистыми значениями:

let icecream = Flavor.vanilla
print(icecream.rawValue)
// vanilla

Мы также можем получить значение Flavor прямо из чистого значения. При этом инициализатор чистого значения возвращает опциональный параметр:

let icecream = Flavor(rawValue: "vanilla")
print(icecream)
// Optional(Flavor.vanilla)

Чистые значения также могут назначаться неявно:

enum Weekday: Int {
    case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
}

В приведенном выше примере исходное значение .sunday равно 7.

Для значений типа String это работает аналогично. Чистые значения будут представлять собой строковые значения элементов перечисления:

enum Compass: String {
    case north, east, south, west
}
 
let direction = Compass.west
print(direction.rawValue == "west")
// true
Читайте также:
Комментарии (1)
Добавить комментарий

Ваш адрес email не будет опубликован.