Перечисления
Пользовательские типы моделей, определяющие список возможных значений.
Перечисление определяет общий тип для группы связанных значений и позволяет работать с этими значениями безопасным для типов способом в вашем коде.
Если вы знакомы с C, вы знаете, что перечисления C присваивают связанные имена набору целочисленных значений. Перечисления в Swift гораздо более гибкие и не обязаны предоставлять значение для каждого случая перечисления. Если для каждого случая перечисления предоставляется значение (известное как сырое значение), оно может быть строкой, символом или значением любого целочисленного или с плавающей точкой типа.
В качестве альтернативы, случаи перечисления могут указывать связанные значения любого типа для хранения вместе с каждым разным значением случая, аналогично тому, как работают объединения или варианты в других языках. Вы можете определить общий набор связанных случаев в рамках одного перечисления, каждый из которых имеет разный набор значений соответствующих типов.
Перечисления в Swift — это типы первого класса. Они поддерживают многие функции, традиционно поддерживаемые только классами, такие как вычисляемые свойства для предоставления дополнительной информации о текущем значении перечисления и методы экземпляров для предоставления функциональности, связанной со значениями, которые представляет перечисление. Перечисления также могут определять инициализаторы для предоставления начального значения случая; могут быть расширены для расширения их функциональности за пределами исходной реализации; и могут соответствовать протоколам для обеспечения стандартной функциональности.
Дополнительную информацию об этих возможностях см. в разделах Свойства, Методы, Инициализация, Расширения и Протоколы.
Синтаксис перечисления
Вы объявляете перечисления с помощью ключевого слова enum и помещаете их полное определение в пару фигурных скобок:
enum SomeEnumeration {
// enumeration definition goes here
} Вот пример для четырех основных направлений компаса:
enum CompassPoint {
case north
case south
case east
case west
} Значения, определённые в перечислении (например, north, south, east и west), являются его случаями перечисления. Вы используете ключевое слово case для ввода новых случаев перечисления.
Примечание: Случаи перечисления Swift не имеют заданного целочисленного значения по умолчанию, в отличие от языков, таких как C и Objective-C. В примере
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")
}
// Prints "Watch out for penguins" Вы можете прочитать этот код как:
«Рассмотрим значение directionToHead. В случае, если оно равно .north, выведите "Lots of planets have a north". В случае, если оно равно .south, выведите "Watch out for penguins"».
…и так далее.
Как описано в ControlFlow, оператор switch должен быть исчерпывающим при рассмотрении случаев перечисления. Если case для .west пропущен, этот код не скомпилируется, поскольку он не учитывает полный список CompassPoint случаев. Требование исчерпывающего анализа гарантирует, что случаи перечисления не будут случайно пропущены.
Когда нецелесообразно предоставлять case для каждого случая перечисления, вы можете предоставить случай default для охвата любых случаев, которые не обрабатываются явно:
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// Prints "Mostly harmless" Итерация по случаям перечисления
Для некоторых перечислений полезно иметь коллекцию всех случаев этого перечисления. Вы активируете это, написав : CaseIterable после имени перечисления. Swift предоставляет коллекцию всех случаев в качестве свойства allCases типа перечисления. Вот пример:
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available" В приведенном выше примере вы пишете Beverage.allCases, чтобы получить доступ к коллекции, содержащей все случаи перечисления Beverage. Вы можете использовать allCases как любую другую коллекцию — элементы коллекции являются экземплярами типа перечисления, поэтому в данном случае они являются значениями Beverage. В приведенном выше примере подсчитывается количество случаев, а в следующем примере используется цикл for-in для итерации по всем случаям.
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice Синтаксис, используемый в приведенных выше примерах, отмечает перечисление как соответствующее протоколу CaseIterable. Сведения о протоколах см. в разделе Протоколы.
Связанные значения
Примеры в предыдущем разделе показывают, как случаи перечисления являются определёнными (и типизированными) значениями сами по себе. Вы можете установить константу или переменную на Planet.earth и проверить это значение позже. Однако иногда бывает полезно иметь возможность хранить значения других типов вместе с этими значениями случая. Эта дополнительная информация называется связанным значением, и она меняется каждый раз, когда вы используете этот случай в качестве значения в вашем коде.
Вы можете определять перечисления Swift для хранения связанных значений любого заданного типа, и типы значений могут быть разными для каждого случая перечисления, если это необходимо. Перечисления, аналогичные этим, известны как дискриминированные объединения, размеченные объединения или варианты в других языках программирования.
Например, предположим, что система отслеживания инвентаря должна отслеживать продукты по двум различным типам штрих-кодов. Некоторые продукты маркируются 1D штрих-кодами в формате UPC, который использует числа 0 до 9. Каждый штрих-код имеет цифру системы, за которой следуют пять цифр кода производителя и пять цифр кода продукта. За ними следует проверочная цифра для проверки того, что код был отсканирован правильно:
Другие продукты маркируются 2D штрих-кодами в формате QR-кода, которые могут использовать любой символ ISO 8859-1 и могут кодировать строку длиной до 2953 символов:
Для системы отслеживания инвентаря удобно хранить штрих-коды UPC в виде кортежа из четырёх целых чисел, а штрих-коды 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, аналогичного примеру в <doc:Enumerations#Matching-Enumeration-Values-with-a-Switch-Statement>. Однако на этот раз связанные значения извлекаются в рамках оператора 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).")
}
// Prints "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).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP." Сырые значения
Пример штрих-кода в <doc:Enumerations#Associated-Values> показывает, как случаи перечисления могут объявлять, что они хранят связанные значения разных типов. В качестве альтернативы связанным значениям, случаи перечисления могут быть заполнены значениями по умолчанию (называемыми сырыми значениями), которые все имеют один и тот же тип.
Вот пример, который хранит сырые значения ASCII вместе с именованными случаями перечисления:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
} Здесь сырые значения для перечисления, называемого ASCIIControlCharacter, определены как типа Character и установлены на некоторые из наиболее распространённых управляющих символов ASCII. Значения Character описаны в StringsAndCharacters.
Сырые значения могут быть строками, символами или любыми целочисленными или типами с плавающей точкой. Каждое сырое значение должно быть уникальным в определении перечисления.
Примечание: Сырые значения — это не то же самое, что и связанные значения. Сырые значения устанавливаются в предопределённые значения, когда вы впервые определяете перечисление в своём коде, как три кода ASCII выше. Сырое значение для конкретного случая перечисления всегда одинаково. Связанные значения устанавливаются, когда вы создаёте новую константу или переменную на основе одного из случаев перечисления, и они могут быть разными каждый раз, когда вы это делаете.
Неявные присвоенные сырые значения
При работе с перечислениями, хранящими целочисленные или строковые значения, вам не нужно явно назначать значение для каждого случая. Если вы этого не сделаете, Swift автоматически присвоит значения.
Например, когда для значений используются целые числа, неявное значение для каждого случая — на единицу больше, чем у предыдущего. Если первому случаю не задано значение, его значением будет 0.
Перечисление ниже — уточнение предыдущего перечисления Planet, в котором целочисленные значения используются для представления порядка планет от Солнца:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
} В приведенном примере Planet.mercury имеет явное значение 1, Planet.venus имеет неявное значение 2 и так далее.
Когда для значений используются строки, неявное значение для каждого случая — текст имени этого случая.
Перечисление ниже — уточнение предыдущего перечисления CompassPoint, в котором строковые значения используются для представления названий направлений:
enum CompassPoint: String {
case north, south, east, west
} В приведенном примере CompassPoint.south имеет неявное значение "south" и так далее.
Вы получаете доступ к значению перечисления с помощью свойства rawValue:
let earthsOrder = Planet.earth.rawValue // earthsOrder is 3 let sunsetDirection = CompassPoint.west.rawValue // sunsetDirection is "west"
Инициализация по значению
Если вы определяете перечисление с типом значения, перечисление автоматически получает инициализатор, который принимает значение типа значения (в качестве параметра, названного rawValue) и возвращает либо случай перечисления, либо nil. Вы можете использовать этот инициализатор, чтобы попытаться создать новый экземпляр перечисления.
В этом примере определяется Уран по его значению 7:
let possiblePlanet = Planet(rawValue: 7) // possiblePlanet is of type Planet? and equals Planet.uranus
Однако не все возможные значения Int найдут соответствующую планету. По этой причине инициализатор по значению всегда возвращает необязательный случай перечисления. В приведенном примере possiblePlanet имеет тип Planet? или «необязательный Planet».
Примечание: Инициализатор по значению — это инициализатор с ошибками, так как не каждое значение вернет случай перечисления. Дополнительная информация — в <doc:Declarations#Failable-Initializers>.
Если вы попытаетесь найти планету с положением 11, необязательное значение Planet, возвращаемое инициализатором по значению, будет nil:
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)")
}
// Prints "There isn't a planet at position 11" В этом примере используется привязка к необязательным значениям для попытки доступа к планете со значением 11. Выражение if let somePlanet = Planet(rawValue: 11) создает необязательный Planet и устанавливает somePlanet в значение этого необязательного Planet, если его можно получить. В данном случае планету с положением 11 получить невозможно, поэтому выполняется ветка else.
Рекурсивные перечисления
Рекурсивное перечисление — это перечисление, в котором другой экземпляр перечисления выступает в качестве связанного значения для одного или нескольких случаев перечисления. Вы указываете, что случай перечисления является рекурсивным, написав 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))
// Prints "18" Эта функция вычисляет простое число, просто вернув связанное значение. Она вычисляет сумму или произведение, вычислив выражение слева, вычислив выражение справа, а затем сложив их или перемножив.
This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception