Unetway

Swift - Перечисление

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

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

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

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

Для получения дополнительной информации об этих возможностях см. Свойства , Методы , Инициализация , Расширения и Протоколы .

Синтаксис перечисления

Вы вводите перечисления с enumключевым словом и помещаете их полное определение в пару фигурных скобок:

enum SomeEnumeration {

// enumeration definition goes here

}

Вот пример для четырех основных пунктов компаса:

enum CompassPoint {

case north

case south

case east

case west

}

Значения , определенные в перечислении (таких , как northsoutheast, и west) являются его перечисление случаев . Вы используете caseключевое слово, чтобы ввести новые случаи перечисления.

ЗАМЕТКА

Случаи перечисления Swift не имеют целочисленного значения, установленного по умолчанию, в отличие от языков, таких как C и Objective-C. В CompassPointприведенном выше примере, northsoutheastи westне неявно равны 012и 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оператором:

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"

…и так далее.

Как описано в Потоке управления , switchоператор должен быть исчерпывающим при рассмотрении случаев перечисления. Если casefor .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"

Перебирая случаи перечисления

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

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цикл для итерации по всем случаям.

for beverage in Beverage.allCases {

print(beverage)

}

// coffee

// tea

// juice

Синтаксис, использованный в приведенных выше примерах, помечает перечисление как соответствующее CaseIterableпротоколу. Для получения информации о протоколах см. Протоколы .

Связанные ценности

Примеры в предыдущем разделе показывают, как случаи перечисления являются определенным (и типизированным) значением сами по себе. Вы можете установить константу или переменную в Planet.earthи проверить это значение позже. Однако иногда полезно иметь возможность хранить значения других типов вместе с этими значениями регистра. Эта дополнительная информация называется связанным значением , и она меняется каждый раз, когда вы используете этот случай в качестве значения в своем коде.

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

Например, предположим, что системе отслеживания запасов необходимо отслеживать продукты по двум различным типам штрих-кода. Некоторые продукты помечены 1D штрих - коды в формате UPC, который использует номера 0для 9. Каждый штрих-код имеет цифру системы счисления, за которой следуют пять цифр кода производителя и пять цифр кода продукта. За ними следует контрольная цифра, чтобы убедиться, что код был отсканирован правильно:

 

Другие продукты помечены двумерными штрих-кодами в формате QR-кода, которые могут использовать любой символ ISO 8859-1 и могут кодировать строку длиной до 2953 символов:

В системе отслеживания запасов удобно хранить штрих-коды UPC в виде кортежа из четырех целых чисел, а штрих-коды QR-кода в виде строки любой длины.

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

enum Barcode {

case upc(Int, Int, Int, Int)

case qrCode(String)

}

Это можно прочитать как:

«Определить тип перечисления под названием Barcode, которое может принимать либо значение upcс соответствующим значением типа ( IntIntIntInt), или значение 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могут хранить либо a, .upcлибо a .qrCode(вместе со связанными с ними значениями), но они могут хранить только один из них в любой момент времени.

Вы можете проверить различные типы штрих-кодов с помощью оператора switch, аналогично примеру в разделе « Сопоставление значений перечисления с оператором 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).")

}

// Prints "QR code: ABCDEFGHIJKLMNOP."

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

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."

Сырые ценности

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

Вот пример, который хранит необработанные значения ASCII вместе с именованными случаями перечисления:

enum ASCIIControlCharacter: Character {

case tab = "\t"

case lineFeed = "\n"

case carriageReturn = "\r"

}

Здесь необработанные значения для вызываемого перечисления ASCIIControlCharacterопределены как имеющие тип Character, и для них установлены некоторые из наиболее распространенных управляющих символов ASCII. Characterзначения описаны в строках и символах .

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

ЗАМЕТКА

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

Неявно присвоенные необработанные значения

Когда вы работаете с перечислениями, которые хранят целочисленные или строковые необработанные значения, вам не нужно явно назначать необработанное значение для каждого случая. Если вы этого не сделаете, Swift автоматически назначит значения для вас.

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

Перечисление ниже является уточнением предыдущего Planetперечисления с целочисленными необработанными значениями для представления порядка каждой планеты от Солнца:

enum Planet: Int {

case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune

}

В приведенном выше примере Planet.mercuryимеет явное необработанное значение 1Planet.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».

ЗАМЕТКА

Инициализатор необработанного значения является неисправным инициализатором, поскольку не каждое необработанное значение будет возвращать регистр перечисления. Для получения дополнительной информации см. Failable Initializer .

Если вы попытаетесь найти планету с положением 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)PlanetsomePlanetPlanet11else

Рекурсивные перечисления

Рекурсивное перечисление является перечислением , которое имеет еще один экземпляр перечисления в качестве связанного значения для одного или нескольких случаев перечисления. Вы указываете, что регистр перечисления является рекурсивным, записывая 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) * 2ArithmeticExpression(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"

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