Перечисления (Enum)
В Swift перечисление (enum, сокращение от enumeration) — это определяемый пользователем тип данных, который имеет фиксированный набор связанных значений. Перечисления позволяют работать с этими значениями в типобезопасном режиме в коде.
Членам перечисления можно задать соответствующие значения любого типа, которые должны быть сохранены вместе с каждым кейсом перечисления. Можно определить общий набор соответствующих значений как часть одного перечисления, каждый из которых будет иметь разные наборы значений ассоциативных типов связанных с ними.
Перечисления в Swift - типы “первого класса” - обладают особенностями, которые обычно поддерживаются классами, например:
- могут иметь вычисляемые свойства, для предоставления дополнительной информации о текущем значении перечисления;
- могут иметь методы экземпляра для дополнительной функциональности, относящейся к значениям, которые предоставляет перечисление;
- могут иметь инициализаторы для предоставления начального значения элементам;
- могут быть расширены для наращивания своей функциональности над её начальной реализацией;
- могут соответствовать протоколам для обеспечения стандартной функциональности.
Больше про свойства, методы, расширения и протоколы можно прочитать в отдельных статьях. См. ссылки внизу.
Общая конструкция - синтаксис перечислений
Для того, чтоб определить перечисление, необходимо его объявить с помощью ключевого слова enum. Затем указать имя перечисления. Затем, в фигурных скобках указать добавляемый необходимый функционал:
enum EnumName {
// code
}
Здесь указано, что будет объявлено перечисление с именем EnumName
.
Так как перечисление - это тип данных, то имя перечисления должно начинаться с большой буквы. Используется так называемый “UpperCamelCase”.
Внутри перечисления используются значения перечисления, которые также называются кейсами перечисления или случаями перечисления (enum cases). Для объявления значений внутри перечисления используется ключевое слово case
.
enum EnumName {
case value1
case value2
}
Можно указывать несколько значений перечисления в одной строке. Для этого, эти значения указываются через запятую:
enum EnumName {
case value1, value2
}
Можно комбинировать оба вида объявления перечисления. Например:
enum EnumName {
case value1, value2
case value3, value4
}
Значения перечисления должны быть уникальными.
Пример перечисления:
enum Season {
case spring, summer, autumn, winter
}
В этом примере
Season
- название перечисления;spring, summer, autumn, winter
- значения, определенные внутри перечисления.
Создание переменных с типом данных перечисления
Поскольку перечисление является типом данных, можно создавать переменные с типом перечисления.
Например:
var currentSeason: Season
Здесь currentSeason
— это переменная типа Season
.
Эта переменная может хранить только значения (spring, summer, autumn, winter), которые присутствуют внутри перечисления Season
.
Присвоение значения переменным
Для присвоения переменной значения перечисления необходимо использовать имя перечисления и через точку .
(dot-notation) присваивается значение перечисления. Например:
// assign summer to currentSeason variable
var currentSeason = Season.summer
Здесь было присвоено переменной currentSeason
значение summer
из перечисления currentSeason
.
Тип данных переменной нельзя изменить.
Если у переменной уже указан тип данных перечисления, то значение этой переменной можно присваивать без указания имени перечисления. Например:
var currentSeason: Season
currentSeason = .summer
Подобным образом можно менять значение переменной. Пример:
var currentSeason = Season.spring
print(currentSeason)
// Output
// spring
currentSeason = .summer
print(currentSeason)
// Output
// summer
Пример использования перечисления:
// define enum
enum Season {
// define enum values
case spring, summer, autumn, winter
}
// create enum variable
var currentSeason: Season
// assign value to enum variable
currentSeason = Season.summer
print("Current Season:", currentSeason)
// Output
// Current Season: summer
В приведенном выше примере
Season
— перечисление с 4 значениями: spring, summer, autumn, winter;currentSeason
- переменная типа перечисления;currentSeason = Season.summer
— присваивается значение перечисленияcurrentSeason
.
Примечание. Подобно переменным, также можно создавать переменные перечисления и присваивать значение в одной строке. Например:
var lastSeason = Season.spring
Перечисления нельзя изменить после установки.
enum AuthenticationState {
case authenticated
case unauthenticated
case undetermined
}
Значения перечисления являются исчерпывающими, и можно быть уверены, что любое значение типа AuthenticationState
является любым из трех значений. Это обеспечивает безопасность в вашем коде и приводит к меньшему количеству ошибок.
Перечисление с оператором Switch
Для того, чтоб менять значения из перечисления для переменной в зависимости от условий - можно использовать перечисление с оператором switch
. Например:
enum PizzaSize {
case small, medium, large
}
var size = PizzaSize.medium
switch(size) {
case .small:
print("I ordered a small size pizza.")
case .medium:
print("I ordered a medium size pizza.")
case .large:
print("I ordered a large size pizza.");
}
// Output
// I ordered a medium size pizza.
В приведенном выше примере было создано перечисление с именем PizzaSize
со значениями: small
, medium
, large
.
Обратите внимание на строку:
var size = PizzaSize.medium
Здесь присваиваем значение media
для переменной size
с типом перечисления.
Также использовали значение переменной size
внутри оператора switch
. И значение size
сравнивается со значением каждого оператора case
.
Поскольку, значение size
совпадает с case .medium
, выполняется код внутри оператора case
.
Ещё пример. Представьте, что мы кодируем робота. У робота есть эмоции. Мы моделируем эти эмоции или состояния с помощью перечисления:
enum Emotion {
case happy, sad, angry, scared, surprised
}
Теперь можно использовать оператор switch
, чтобы реагировать на эти эмоции:
struct Robot {
var mood: Emotion
func destroyAllHumans() {
print("I kill you!!!")
}
func rust() {
print("I cry...")
}
func play(_ track: String) {}
}
var robot = Robot(mood: .angry)
switch robot.mood {
case .angry:
robot.destroyAllHumans()
case .sad:
robot.rust()
case .happy:
robot.play("happy.mp3")
default:
print("Неизвестная эмоция.")
}
// Output
// I kill you!!!
Каждый case
в блоке switch
соответствует отдельному case
в перечислении, и выполняет определённую инструкцию.
Так как перечисления являются исчерпывающими - то в операторе switch
не нужно указывать значения для default
, если указать в операторе switch
все варианты из перечисления.
enum AuthenticationState {
case authenticated
case unauthenticated
case undetermined
}
func checkAuth(_ state: AuthenticationState) {
switch state {
case .authenticated:
print("Ok")
case .unauthenticated:
print("Access denied!")
case .undetermined:
print("Ask admin")
}
}
var auth: AuthenticationState = .undetermined
checkAuth(auth)
// Output
// Ask admin
auth = .authenticated
checkAuth(auth)
// Output
// Ok
В этом примере в функции checkAuth()
были перебраны в switch
все варианты из перечисления AuthenticationState
. Поэтому указывать значения для default
не нужно.
Итерация по значениям перечисления
Для некоторых перечислений полезно иметь коллекцию всех случаев этого перечисления. В Swift используется протокол CaseIterable
для получения доступа ко всем значениям в перечислении и, в дальнейшем, для перебора перечисления с помощью цикла. Для этого необходимо подписать перечисление под протокол CaseIterable
. Например:
enum Season: CaseIterable {
...
}
В этом протоколе указано свойство allCases
, которое включает в себя все значения перечисления. Теперь можно использовать свойство allCases
, как обычный массив всех значений перечисления, в том же порядке, что и в перечислении
Теперь можно использовать свойство allCases
для просмотра каждого варианта перечисления с помощью цикла.
for currentSeason in Season.allCases {
...
}
Пример перебора перечисления циклом:
// conform Languages to caseIterable
enum Season: CaseIterable {
case spring, summer, autumn, winter
}
// for loop to iterate over all cases
for currentSeason in Season.allCases {
print(currentSeason)
}
// Output
// spring
// summer
// autumn
// winter
В приведенном выше примере перечисление Season
было согласовано с протоколом CaseIterable
- перечисление Season
было подписано под протокол CaseIterable
.
Затем используется свойство allCases
с циклом for-in
для перебора каждого варианта перечисления.
Связанные значения перечисления (Associated Values)
К значениям перечислений можно добавить дополнительную информацию с помощью связанных значений (ассоциированные значения, Associated Values).
Например:
enum Laptop {
// associate value
case name(String)
...
}
В перечислении Laptop
для значения name
можно прикрепить связанное значение по типу String
.
Теперь можно присвоить связанное значение для значения name
перечисления.
Laptop.name("Razer")
Здесь Razer
— это связанное значение для name
.
Пример перечисления со связанными значениями:
enum Laptop {
// associate string value
case name(String)
// associate integer value
case price(Int)
}
// pass string value to name
var brand = Laptop.name("Razer")
print(brand)
// pass integer value to price
var offer = Laptop.price(1599)
print(offer)
// Output
// name("Razer")
// price(1599)
В приведенном выше примере связанное значение
Razor
присваиваетсяname
;1599
присваиваетсяprice
.
Итак, при необходимости можно добавить дополнительную информацию к значениям перечисления. И эта дополнительная информация, прикрепленная к значениям перечисления, называется ассоциированными значениями (связанными значениями).
Давайте посмотрим пример:
enum Distance {
// associate value
case km(String)
...
}
Здесь (String)
- это дополнительная информация, которая связанная со значением km
. Это означает, что связанное значение km
может быть только строкой.
Теперь, вот так мы присваиваем связанное значение значению перечисления:
Distance.km("Metric System")
Здесь "Metric System"
— это значение, связанное со значением km
.
Пример: перечисление Swift со связанными значениями:
enum Distance {
// associate value
case km(String)
case miles(String)
}
// pass string value to km
var dist1 = Distance.km("Metric System")
print(dist1)
// pass string value to miles
var dist2 = Distance.miles("Imperial System")
print(dist2)
// Output
// km("Metric System")
// miles("Imperial System")
В приведенном выше примере было создано перечисление Distance
со следующими связанными значениями:
"Metric System"
присваивается значениюkm
;"Imperial System"
присваивается значениюmiles
.
Несколько связанных значений
В Swift также можно связать несколько значений с одним значением перечисления. Например:
enum Marks {
// associate multiple Double values
case gpa(Double, Double, Double)
// associate multiple String values
case grade(String, String, String)
}
// pass three Double values to gpa
var marks1 = Marks.gpa(3.6, 2.9, 3.8)
print(marks1)
// pass three string values to grade
var marks2 = Marks.grade("A", "B", "C")
print(marks2)
// Output
// gpa(3.6, 2.9, 3.8)
// grade("A", "B", "C")
Здесь мы создали перечисление с двумя значениями: gpa
и grade
. Обратите внимание на значения перечисления,
// associate multiple Double values
case gpa(Double, Double, Double)
// associate multiple String values
case grade(String, String, String)
Этот код указывает на то, что необходимо связать три значения Double
и String
с gpa
и grade
соответственно. Эти связанные значения также называются кортежами.
Итак, к gpa
было привязано 3.6, 2.9, 3.8
.
// pass three integer values to gpa
var marks1 = Marks.gpa(3.5, 2.9, 3.8)
И также было привязано "A", "B", "C"
к grade
.
// pass three string values to grade
var marks2 = Marks.grade("A", "B", "C")
Именованные связанные значения (Named Associated Values)
В Swift также можно предоставить имя связанному значению, чтобы сделать код более читабельным. Например:
enum Pizza {
case small(inches: Int)
}
Здесь предоставили имя inches
для связанного значения.
Это дает больше смысла при чтении именованного связанного значения вместо
case small(Int)
Пример именованного связанного значения:
enum Pizza {
// named associated value
case small(inches: Int)
case medium(inches: Int)
case large(inches: Int)
}
// pass value using provided names
var pizza1 = Pizza.medium(inches: 12)
print(pizza1)
// Output
// medium(inches: 12)
В приведенном выше примере было предоставлено имя inches
для связанного значения.
При передаче связанного значения в работу было использовано предоставленное имя:
Pizza.medium(inches: 12)
Связанные значения и оператор switch (Enum Associated Values and Switch Statement)
Можно использовать оператор switch
для сопоставления значений, связанных с перечислением. Например:
enum Mercedes {
case sedan(height: Double)
case suv(height: Double)
case roadster(height: Double)
}
var choice = Mercedes.suv(height: 5.4)
switch(choice) {
case let .sedan(height):
print("Height:", height)
case let .suv(height):
print("Height:", height)
case let .roadster(height):
print("Height:", height)
}
// Output
// Height: 5.4
В приведенном выше примере был использован оператор switch
для сопоставления значений, связанных с перечислением. Обратите внимание на объявления переменной,
var choice = Mercedes.suv(height: 5.4)
Здесь привязали значение 5.4
к внедорожнику и присвоили ему выбор переменной перечисления.
Затем использовали выбор внутри оператора switch
, чтобы сравнить регистр enum
со значением каждого оператора case
. Обратите внимание на оператор case
в switch
:
case let .suv(height):
Здесь let
связывает связанное значение с переменной height
, которую можно использовать в теле case
.
Поскольку значение совпадает с case let .suv
, оператор внутри case
выполняется.
Еще пример. Пусть перечисление представляет игрового персонажа:
enum Person{
case Human(String, Int)
case Elf(String)
case Gnom(String)
}
Здесь перечисление определяет три возможных значения - трёх игровых персонажей: человека (Human
), эльфа (Elf
) и гнома (Gnom
). Однако у человека можно задать два параметра: имя (String
) и количество жизней (Int
). А у эльфа и гнома нужен только один параметр - имя (String
).
То есть в данном случае значение Person.Human
будет ассоциировано с двумя значениями String
и Int
, а значение Person.Elf
и Person.Gnom
- с одним значением типа String
:
var heroOne = Person.Human("Trogvar", 5)
var heroTwo = Person.Elf("Feonor")
С помощью конструкции switch
также можно определить значение объекта:
switch(heroOne){
case .Human:
print("Вы играете человеком")
case .Elf:
print("Вы играете эльфом")
case .Gnom:
print("Вы играете гномом")
}
// Output
// Вы играете человеком
Но при необходимости также можем получить ассоциированные значения:
switch(heroOne){
case .Human(let name, let lives):
print("Вы играете человеком. Имя: \(name), количество жизней: \(lives)")
case .Elf(let name):
print("Вы играете эльфом. Имя: \(name)")
case .Gnom:
print("Вы играете гномом")
}
// Output
// Вы играете человеком. Имя: Trogvar, количество жизней: 5
Для получения значений можно определить константы или переменные, в которые будут передаваться ассоциированные значения.
Ещё пример. Представьте, что создаем приложение для ролевой игры (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("Это не броня.")
}
// Output
// Броня слишком тяжелая!
В приведенном выше коде используется ключевое слово 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 Value)
В Swift мы также можем присваивать значения каждому случаю перечисления. Например:
enum Size: Int {
case small = 10
case medium = 12
}
Здесь были присвоены значения 10
и 12
для значений small
и medium
в перечислении соответственно. Эти значения называются исходными значениями (raw value).
Внимание. После имени перечисления Size
через двоеточие указан тип данных Int
, чтоб указать, что для значений перечисления в качестве исходных значений можно указывать только целочисленные значения.
Доступ к исходным значениям перечисления
Чтобы получить доступ к исходному значению перечисления необходимо использовать свойство rawValue
у значения перечисления. Например:
// access raw value
Size.small.rawValue
Здесь получили доступ к исходному значению для small
.
Пример перечисления с исходными значениями:
enum Size : Int {
case small = 10
case medium = 12
case large = 14
}
// access raw value of case
var result = Size.small.rawValue
print(result)
// Output
// 10
В приведенном выше примере было создано перечисление с именем Size
, которое имеет исходные значение по типу Int
.
Здесь был получен доступ к значению small
, используя свойство rawValue
.
Примечание. Исходные значения могут быть строками, символами, целыми числами, числами с плавающей точкой. Каждое исходное значение должно быть уникальным при его объявлении.
Вот пример перечисления, члены которого хранят исходные значения ASCII:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
Исходные значения перечисления ASCIIControlCharacter
определены как тип Character
, и им присвоены распространенные контрольные символы ASCII.
Заметка. Исходные значения - это не то же самое, что ассоциативные значения. Исходные значения устанавливаются в качестве дефолтных значений (значений по умолчанию), когда в первый раз определяете перечисление в коде, как три ASCII кода выше. Исходное значение для конкретного кейса перечисления всегда одно и то же. Ассоциативные значения устанавливаются при создании новой константы или переменной, основываясь на одном из кейсов перечисления, и могут быть разными каждый раз, когда вы делаете это.
Неявно установленные исходные значения
Если работаете с перечислениями, которые хранят целочисленные или строковые исходные значения, то не нужно явно присваивать исходные значения для каждого конкретного кейса. Swift автоматически сделает это за вас.
Например, когда целые числа используются в качестве исходных значений, неявное значение для каждого кейса будет на единицу больше, чем в предыдущем кейсе. Если первый кейс не имеет заданного значения, его значение равно 0.
enum Planet: Int {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
var planetFirst = Planet.mercury
var planetFirstNum = planetFirst.rawValue
print(planetFirst)
print(planetFirstNum)
// Output
// mercury
// 0
Теперь уточним ранее указанное перечисление Planet
, с целочисленными исходными значениями для представления удаленности каждой планеты от солнца:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
var planetFirst = Planet.mercury
var planetFirstNum = planetFirst.rawValue
print(planetFirst)
print(planetFirstNum)
// Output
// mercury
// 1
В приведенном выше примере, Planet.mercury
имеет явное исходное значение 1
, Planet.venus
имеет неявное исходное значение 2
, и так далее.
Изменение в исходных значениях с указанного элемента перечисления и дальше, до конца перечисления.
enum Planet: Int {
case mercury = 1, venus, earth = 30, mars, jupiter, saturn, uranus, neptune
}
var planetThird = Planet.earth
var planetThirdNum = planetThird.rawValue
print(planetThird)
print(planetThirdNum)
// Output
// earth
// 30
var planetFour = Planet.mars
var planetFourNum = planetFour.rawValue
print(planetFour)
print(planetFourNum)
// Output
// mars
// 31
В этом примере было переопределено исходное значение для элемента earth
в перечислении Planet
. И исходные значения в последующих элементах перечисления тоже поменялись. Для элемента mars
исходное значение стало 31
, и для neptune
- 35
.
Когда строки используются в качестве исходных значений, неявное значение для каждого кейса является текстом имени этого кейса.
Ниже пример с четырьмя сторонами света:
enum CompassPoint: String {
case north
case south
case east
case west
}
var pathOne = CompassPoint.north
var pathOneName = pathOne.rawValue
print(pathOne)
print(pathOneName)
// Output
// north
// north
var pathTwo = CompassPoint.south
var pathTwoName = pathTwo.rawValue
print(pathTwo)
print(pathTwoName)
// Output
// south
// south
В приведенном выше примере, CompassPoint.north
имеет неявное исходное значение "north"
, CompassPoint.south
имеет неявное исходное значение "south"
и так далее. Заменим первое:
enum CompassPoint: String {
case north = "Север"
case south
case east
case west
}
var pathOne = CompassPoint.north
var pathOneName = pathOne.rawValue
print(pathOne)
print(pathOneName)
// Output
// north
// Север
var pathTwo = CompassPoint.south
var pathTwoName = pathTwo.rawValue
print(pathTwo)
print(pathTwoName)
// Output
// south
// south
Здесь явно указано исходное значение "Север"
для элемента north
в перечислении. Для остальных элементов в перечислении - исходные значения не поменялись и так и остались текстом имени этих кейсов.
Инициализация через исходное значение
Если объявить перечисление вместе с типом исходного значения, то перечисление автоматически получает инициализатор, который берет значение типа исходного значения (как параметр rawValue
) и возвращает либо член перечисления либо nil. Можно использовать этот инициализатор, чтобы попытаться создать новый экземпляр перечисления.
В этом примере Uranus
инициализируется через его исходное значение 7:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet имеет тип Planet? и его значение равно Planet.uranus
Так как не все возможные значения Int
будут в данном перечислении, то инициализаторы исходных значений всегда возвращают опциональный член перечисления
. В этом примере possiblePlanet
типа Planet?
или “опциональный Planet”.
Заметка. Инициализатор исходного значения - это проваливающийся инициализатор, потому что не каждое исходное значение будет возвращать кейс перечисления.
Больше про с инициализаторы можно прочитать в отдельных статьях. См. ссылки внизу.
Если попытаться найти планету с номером позиции 11
, то значение опциональной Planet
, которое будет возвращенным исходным значением инициализатора, будет nil
:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// Output
// There isn't a planet at position 11
Этот пример использует опциональное связывание для попытки добраться до Planet
с исходным значением 11
. Выражение if let somePlanet = Planet(rawValue: 11)
создает опциональную Planet
и устанавливает значение somePlanet
опциональной Planet
, если она может быть восстановлена.
В этом случае, невозможно добраться до планеты с позицией 11
. Таким образом срабатывает ветка else
в условной конструкции и выводится соответствующее сообщение.
Исходные значения или связанные значения
В Swift исходные значения — это предопределенные постоянные значения, предоставляемые каждому значению перечисления. Например,
enum Vehicle: String {
case car = "Four Wheeler"
case bike = "Two Wheeler"
}
Здесь мы предоставили исходные значения: "Four Wheeler"
(«Четырехколесный») и "Two Wheeler"
(«Двухколесный») для автомобиля и велосипеда соответственно.
Однако, связанные значения больше похожи на переменные, связанные со значениями перечисления. Например:
enum Marks {
case grade(String)
case gpa(Double)
}
Здесь grade
и gpa
имеют связанные значения типа String
и Double
соответственно.
И связанное значение присваивается значению перечисления при создании переменной.
var marks = Marks.grade("A")
Заметка. Перечисление не может иметь как исходные значения, так и связанные значения одновременно. Исходные значения перечисления должны иметь один и тот же тип данных. А связанные значения могут быть любого типа.
Рекурсивные перечисления
Рекурсивные перечисления - это такие перечисления, экземпляры которого являются ассоциативным значением одного или более кейсов перечисления. Вы обозначаете такие кейсы перечисления при помощи ключевого слова indirect перед кейсом, что сообщает компилятору о том, что нужен дополнительный слой индирекции.
Например, ниже объявлено перечисление, которое хранит простые арифметические выражения:
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
Вы так же можете написать indirect
прямо перед самим перечислением, что позволит обозначить то, что все члены перечисления поддерживают индиректность:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
В этом примере перечисление может хранить три вида арифметических выражений: простое число, сложение двух выражений, умножение двух выражений. Члены addition
и multiplication
имеют два ассоциативных значения, которые так же являются арифметическими выражениями.
Эти ассоциативные значения делают возможным вложение выражений. Например, выражение (5 + 4) * 2
имеет цифру справа от умножения и другое выражение слева от умножения. Поскольку данные вложены, перечисление использующееся для хранения данных, также должно поддерживать вложенность - это означает, что перечисление должно быть рекурсивными.
Приведенный ниже код показывает как работает рекурсивное перечисление ArithmeticExpression
для (5 + 4) * 2
:
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
Рекурсивные функции - самый простой путь работать с данными, которые имеют рекурсивную структуру. Например, ниже приведен пример, как функция вычисляет арифметическое выражение:
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// Output
// 18
Эта функция вычисляет простое число, просто возвращая ассоциативное значение. Она вычисляет сложение или умножение, вычисляя выражение по левую сторону, затем по правую сторону, затем складывает или умножает их.
Методы перечислений
Как классы и структуры, перечисления могут определять методы. Например:
enum DayOfWeek: Int {
case Monday = 1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
func getCurrentDay() -> String {
return DayOfWeek.getDay(number: rawValue)
}
static func getDay(number: Int) -> String {
switch number {
case 1:
return "Понедельник"
case 2:
return "Вторник"
case 3:
return "Среда"
case 4:
return "Четверг"
case 5:
return "Пятница"
case 6:
return "Суббота"
case 7:
return "Воскресенье"
default:
return "undefined"
}
}
}
var someDay: DayOfWeek = DayOfWeek.Sunday
print(someDay.getCurrentDay())
// Output
// Воскресенье
var secondDay = DayOfWeek.getDay(number: 2) // Вторник
print(secondDay)
// Output
// Вторник
В этом примере:
- в перечислении
DayOfWeek
определён метод типаgetDay(number: Int) -> String
, который возвращает строку с названием дня недели в зависимости от введённого номера; - в перечислении
DayOfWeek
определён методgetCurrentDay() -> String
, который возвращает строку с названием дня недели текущего экземпляра перечисления. Для этого, данный метод вызывает метод типаgetDay(number: Int) -> String
, в который передаёт в качестве входящего параметраnumber
исходное значение текущего экземпляра перечисления; - была создана переменная
someDay
со значениемDayOfWeek.Sunday
; - у переменной
someDay
вызван методgetCurrentDay()
и результат вызова был выведен в консоль; - была создана переменная
secondDay
по типуString
как результат вызова метода типаgetDay(number: 2)
перечисленияDayOfWeek
и получила значениеВторник
.
Ещё пример:
enum SwitchState {
case off, low, high
mutating func nextState() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
print("Switched to state: \(self)")
}
}
var state: SwitchState = .off
print(state)
// Output
// off
state.nextState()
print(state)
// Output
// Switched to state: low
// low
state.nextState()
print(state)
// Output
// Switched to state: high
// high
state.nextState()
print(state)
// Output
// Switched to state: off
// off
В этом примере, метод nextState()
в перечислении SwitchState
меняет значение экземпляра перечисления state
.
Следует обратить внимание на то, что данный метод меняет значение экземпляра перечисления. Для этого, в операторе switch
указано, что изменение было пременено для экземпляра - self
. И, если метод меняет значение экземпляра, то перед объявлением метода указано ключевое слово mutating
.
Свойства перечислений
Перечисления также могут иметь свойства. Но это не могут быть хранимые свойства, а только вычисляемые свойства:
enum DayOfWeek: Int{
case Monday = 1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
var label : String {
switch self {
case .Monday: return "Понедельник"
case .Tuesday: return "Вторник"
case .Wednesday: return "Среда"
case .Thursday: return "Четверг"
case .Friday: return "Пятница"
case .Saturday: return "Суббота"
case .Sunday: return "Воскресенье"
}
}
}
let day1 = DayOfWeek.Monday
print(day1.label)
// Output
// Понедельник
print(DayOfWeek.Friday.label)
// Output
// Пятница
В данном случае свойство label
автоматически вычисляется на основании значения текущего объекта перечисления. Текущий объект перечисления можно получить с помощью ключевого слова self
.
Инициализаторы в перечислениях
И перечисления также могут иметь иницализаторы. В перечислениях используются проваливающийся инициализатор:
enum DayOfWeek: Int{
case Monday = 1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
init?(_ val:String) {
switch val {
case "Понедельник" : self = .Monday
case "Вторник": self = .Tuesday
case "Среда": self = .Wednesday
case "Четверг": self = .Thursday
case "Пятница": self = .Friday
case "Суббота": self = .Saturday
case "Воскресенье": self = .Sunday
case _ : return nil
}
}
}
let day1 = DayOfWeek("Пятница")
print(day1!.rawValue)
// Output
// 5
В данном случае инициализатор принимает название дня недели и на его основании устанавливает значение текущего объекта. Если переданное название не распознано, то инициализатор возвращает nil
.
Примечание. Иногда бывает нужно определить класс, структуру или перечисление, инициализация которого может не сработать, провалиться. Такое неисполнение может быть вызвано некорректными значениями параметров или отсутствием требуемого внешнего источника данных или еще какое-нибудь обстоятельство, которое может не позволить завершить инициализацию успешно.
Для того чтобы справиться с условиями инициализации, которые могут провалиться, следует определить один или несколько проваливающихся инициализаторов как часть определения класса, структуры или перечисления. Можно написать проваливающийся инициализатор поместив вопросительный знак после ключевого слова init (
init?
).
Больше про инициализаторы можно прочитать в отдельной статье. См. ссылки внизу.
Еще полезные ссылки
Также информацию по перечислениям можно получить на странице официальной документации.
Ссылки на официальную документацию: