Основы Swift / 10.1. Перечисления - основы


Видео


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

Перечисления в Swift - это типы первого класса. Они адаптируют многие возможности, традиционно поддерживаемые только классами, такие как: вычисляемые свойства для предоставления дополнительной информации о текущем значении перечисления; методы экземпляров для предоставления функциональности, связанной со значениями, представляемыми перечислением. Перечисления также могут определять конструкторы для предоставления изначального значения кейса; могут быть расширены для увеличения своей функциональностью по сравнению с их оригинальной имплементацией; и могут адаптировать протоколы для предоставления стандартизированной функциональности.

Вы можете начать перечисление с ключевого слова enum и разместить всё определение внутри пары фигурных скобок:
@{10.1\1}
enum SomeEnumeration {
    // здесь располагается определение перечисления
}
Отличие от классических перечислений
В отличие от C и Objective-C кейсам перечислений в Swift не присваиваются значения по-умолчанию, когда они создаются. В примере с CompassPoint ниже кейсы north, south, east и west не становятся неявно равными 0, 1, 2 и 3. Вместо этого различные кейсы перечислений полностью самостоятельные значения с явно заданным типом CompassPoint.
@{10.1\2}
enum CompassPoint {
    case north
    case south
    case east
    case west
}
Пример для четырёх направлений компаса.

Значения, определённые в перечислении (такие как north, south, east и west) являются кейсами этого перечисления. Вы можете использовать ключевое слово case, чтобы представить новые кейсы перечисления.
@{10.1\3\1}
На одной строке можно написать несколько кейсов, разделив их запятыми.
enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
@{10.1\3\2}
Каждое определение перечисления задаёт полностью новый тип. Как и все другие типы в Swift, их имена (как например CompassPoint или Planet) должны начинаться с заглавной буквы. Давайте типам-перечислениям одиночное название нежели множественное, чтобы обозначать именно тип.
var directionToHead = CompassPoint.west
@{10.1\3\3}
Тип directionToHead выводится, когда он инициализируется одним из возможных значений типа CompassPoint. Так как directionToHead декларируется как CompassPoint, то Вы можете задать ей другое значение типа CompassPoint, используя сокращённый точечный синтаксис.

Тип directionToHead уже известен, так что Вы можете отбросить название типа при задании его нового значения. Это сделано для хорошо читаемого кода, когда Вы работаете с явно-типизированными значениями перечислений.
directionToHead = .east
@{10.1\4}
Когда необязательно предоставлять кейс для каждого кейса перечисления, Вы можете предоставить кейс default, чтобы покрыть все кейсы, явно не заданные:
enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("Сильно загрязнена")
default:
    print("Небезопасное место для людей")
}
// Выведет "Сильно загрязнена"
Проверка значений-перечислений в инструкции switch
Вы можете проверить отдельное значение перечисления с помощью инструкции switch:
@{10.1\3\5}
directionToHead = .south
switch directionToHead {
case .north:
    print("У многих планет есть север")
case .south:
    print("Остерегайтесь пингвинов")
case .east:
    print("Место, где заходит солнце")
case .west:
    print("Там, где небо голубое")
}
// Выведет "Остерегайтесь пингвинов"
Вы можете прочитать этот код так:

"Рассмотрим значение directionToHead. В случае, когда оно равно .north, вывести "У многих планет есть север". В случае, когда он равен .south, печатает "Остерегайтесь пингвинов"."

…и так далее.

Инструкция switch должна быть исчерпывающей при определении кейсов перечисления. Если кейс для .west будет опущен, то код не будет скомпилирован, так как не определён полный список для кейсов CompassPoint. Требование исчерпываемости приводит к тому, что кейсы перечисления не будут опущены по ошибке.
Ассоциированные значения
Пример в предыдущей секции демонстрирует, как кейсы перечисления определяются и типизируются полноценно. Вы можете установить константу или переменную в Planet.earth и проверить её значение позже. Однако бывает иногда полезно биметь возможность хранить ассоциированные значения других типов с этими значениями кейсов. Это позволяет Вам хранить дополнительную информацию вместе со значением кейса и запрещает этой информации меняться всякий раз при использовании кейса в Вашем коде.

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

Например, пример, что система отслеживания инвентаря должна отслеживать продукты двумя разными типами штрихкодов. Некоторые продукты маркированы 1D-штрихкодами в формате UPC, который использует числа от 0 до 9. Каждый штрихкод имеет цифру "система числа", сопровождаемую пятью знаками "код производителя" и пятью цифрами "код продукта". Они сопровождаются знаком "проверки" для подтверждения того, что код был отсканирован правильно:
Рисунок 10.1.1
QR
Другие продукты маркируются 2D штрихкодами в формате QR-кодов, который может использовать любой символ ISO 8859-1 и может кодировать строку длиной вплоть до 2953 символов:
Рисунок 10.1.2
Реализация модели
Было бы удобно для системы отслеживания имущества быть способной хранить UPC-штрихкода в кортеже из четырёх целых чисел, а QR-штрихкоды в качестве строки произвольной длины.

В Swift перечисление, которое определяет штрихкоды обоих типов может выглядеть примерно так:
@{10.1\5\1}
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
Это может быть прочитано как:

"Определить перечисление с типом под названием Barcode, которое может принимать как значение upc с ассоциированным значением типа (Int, Int, Int, Int) или значение типа qrCode с ассоциированным значением типа String."

Определение не предоставляет никаких конкретных значений Int или String - оно просто определяет тип связанного значения, которое переменные или константы типа Barcode могут хранить, когда они равны Barcode.upc или Barcode.qrCode.
@{10.1\5\2}
Новые штрихкоды могут быть созданы с использованием какого-то из этих типов:
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
В этом примере создаётся новая переменная под названием productBarcode и ей присваивается значение Barcode.upc с ассоциированным кортежем значением (8, 85909, 51226, 3).
@{10.1\5\3}
Этот же продукт может быть присвоен и другой тип штрихкода:
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
В этот момент оригинальный Barcode.upc и его целочисленное значение будут заменены новым Barcode.qrCode и его строковым значением. Константы и переменные типа Barcode могут хранить или .upc, или .qrCode (вместе с их ассоциированными значениями), но они могут хранить лишь один из них в любой заданный момент времени.
Проверка с помощью инструкции switch
Различные типы штрихкодов могут быть проверены с использованием инструкции switch как и ранее. В этот раз, однако, ассоциированные значения могут быть извлечены как часть инструкции (switch0. Вы можете извлечь каждое ассоциированное значение в качестве константы (с префиксом let) или переменной (с префиксом var) для использования в теле кейса switch:
@{10.1\5\4}
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check)")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// Выведет "QR code: ABCDEFGHIJKLMNOP."
Одно ключевое слово для всего извлечения
Если все ассоциированные значения для кейса перечисления извлечены в качестве констант или если все они извлечены в качестве переменных, Вы можете поставить одну единственную аннотацию let или var перед именем кейса для краткости:
@{10.1\5\5}
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check)")
case let .qrCode(productCode):
    print("QR code: \(productCode).")
}
// Выведет "QR code: ABCDEFGHIJKLMNOP."