Перечисления
Перечисления определяют общий тип для группы связанных значений и позволяют работать с этими значениями в типобезопасном режиме в вашем коде.
Если вы знакомы с C, то вы знаете, что перечисления в C присваивают соответствующие имена набору целочисленных значений. Перечисления в Swift более гибкий инструмент и не должны предоставлять значения для каждого члена перечисления. Если значение (известное как “сырое” значение) предоставляется каждому члену перечисления, то это значение может быть строкой, символом или целочисленным значением, числом с плавающей точкой.
Кроме того, членам перечисления можно задать соответствующие значения любого типа, которые должны быть сохранены вместе с каждым другим значением члена. Вы можете определить общий набор соответствующих значений как часть одного перечисления, каждый из которых будет иметь разные наборы значений соответствующих типов связанных с ними.
Перечисления в Swift — типы “первого класса”. Они обладают особенностями, которые обычно поддерживаются классами, например, вычисляемые свойства, для предоставления дополнительной информации о текущем значении перечисления, методы экземпляра для дополнительной функциональности, относящейся к значениям, которые предоставляет перечисление.
Перечисления так же могут объявлять инициализаторы для предоставления начального значения элементам. Они так же могут быть расширены для наращивания своей функциональности над её начальной реализацией. Могут соответствовать протоколам для обеспечения стандартной функциональности.
Больше про эти возможности можно прочитать в разделах Свойства, Методы, Инициализация, Расширения и Протоколы.
Синтаксис перечислений
Перечисления начинаются с ключевого слова enum, после которого идет имя перечисления и полное его определение в фигурных скобках:
enum SomeEnumeration {
//здесь будет объявление перечисления
}
Ниже пример с четырьмя сторонами света:
enum CompassPoint {
case north
case south
case east
case west
}
Значения, объявленные в перечислении (north, south, east, и west), называются кейсами перечисления. Используйте ключевое слово case для включения нового кейса перечисления.
Заметка
В отличии от C и Objective-C в Swift кейсам перечисления не присваиваются целочисленные значения по умолчанию при их создании. В примере выше CompassPoint, значения членов north, south, east и west неявно не равны 0, 1, 2, 3. Вместо этого различные члены перечисления по праву полностью самостоятельны, с явно объявленным типом CompassPoint.
Множественные значения члена перечисления могут записываться в одну строку, разделяясь между собой запятой:
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
Каждое объявление перечисления объявляет и новый тип. Как и остальные типы в Swift, их имена (к примеру CompassPoint и Planet) должны начинаться с заглавной буквы. Имена перечислениям лучше давать особенные, а не те, которые вы можете использовать в нескольких местах, так чтобы они читались как само собой разумеющиеся:
var directionToHead = CompassPoint.west
Тип directionToHead выведен при инициализации одного из возможных значений CompassPoint. Если directionToHead объявлена как CompassPoint, то можем использовать различные значения CompassPoint через сокращенный точечный синтаксис:
directionToHead = .east
Тип directionToHead уже известен, так что вы можете не указывать тип, присваивая значения. Так делается для хорошо читаемого кода, когда работаете с явно указанными типами значений перечисления.
Использование перечислений с инструкцией switch
Вы можете сочетать индивидуальные значения перечисления с инструкцией switch:
directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// Выводит "Watch out for penguins"
Вы можете прочитать этот код как:
“Рассмотрим значение directionToHead. В случае, когда directionToHead равняется .north, выводится сообщение ”Lots of planets have a north“. В кейсе, где оно равняется .south, выводится сообщение ”Watch out for penguins«.
…и так далее…
Как сказано в главе “Управление потоком”, оператор switch должен быть исчерпывающим, когда рассматриваются члены перечисления. Если мы пропустим case .west, то код не скомпилируется, так как не рассматривается полный перечень членов CompassPoint. Требования к конструкции быть исчерпывающей, помогает случайно не пропустить член перечисления.
Если не удобно описывать кейс для каждого члена перечисления, то вы можете использовать кейс default, для закрытия всех остальных вариантов перечисления:
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// Выведет "Mostly harmless"
Связанные значения
Примеры в предыдущей секции показывают, как кейсы перечисления определены (и типизированы) как самостоятельные значения. Вы можете установить Planet.earth как константу или переменную и посмотреть какое значение она содержит. Однако бывает удобно хранить связанные значения других типов вместе с этими значениями кейсов перечисления. Это позволяет вам хранить дополнительную пользовательскую информацию вместе со значением кейса и разрешает изменять эту информацию каждый раз как вы используете этот кейс перечисления в вашем коде.
Вы можете объявить перечисления Swift для хранения связанных значений любого необходимого типа, и типы значений могут отличаться для каждого члена перечисления, если это необходимо. Перечисления такого типа так же известны как размеченные объединения, маркированные объединения или варианты в других языках программирования.
Для примера, предположим систему инвентаризации, которая должна отслеживать товар двумя различными типами штрих-кодов. Одни товары имеют коды типа 1D формата UPC-A, которые используют цифры от 0 до 9. Каждый штрих-код имеет свою “систему цифр”, где идут пять цифр “кода производителя” и пять цифр “кода продукта”. Затем идет “проверочная” цифра, которая проверяет, что код был отсканирован корректно:
Другие продукты имеют маркировку штрих-кодом 2D формата QR, который может использовать любой символ из ISO 8859–1 и может закодировать строку длиною 2953 символа:
Было бы удобно, если бы система контроля и учета товара могла бы хранить штрих-коды формата UPC-A, как кортеж из четырех целых чисел и QR код, как строку любой длины.
В Swift перечисления для определения штрих-кода продукта одного из двух типов может выглядеть следующим образом:
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.
Новые штрих-коды могут быть созданы с помощью любого типа:
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
В этом примере мы создаем новую переменную productBarcode и присваиваем ей значение Barcode.upc со связанным кортежем значений (8, 85909, 51226, 3).
Этому же продукту может быть присвоено другое значение кода:
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
Здесь исходный Barcode.upc и его целочисленные значения заменены новым Barcode.qrCode и его строковым значением. Константы и переменные типа Barcode могут хранить или .upc или .qrCode (вместе со связанными значениями), но они могут хранить только один из них в любой момент времени.
Различные типы штрих-кодов могут быть проверены инструкцией switch как и раньше. В этот раз связанные значения могут быть извлечены как часть инструкции switch. Вы извлекаете каждое связанное значение как константу (с префиксом let) или как переменную (префикс var) для использования внутри тела оператора switch:
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 перед именем члена:
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."