Основы Swift / 10.2. Чистые значения перечислений


Видео


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

Приведём пример, где чистые ASCII значения хранятся в именованных кейсах перечислений:
@{10.2\1}
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
Здесь чистые значения для перечисления под названием ASCIIControlCharacter заданы в тип Character и установлены в наиболее общие управляющие символы ASCII.

Чистые значения могут быть строками, символами или любыми целыми или дробными типами чисел. Каждое чистое значение должно быть уникальным в декларации своего перечисления.
Отличие ассоциированных значений от чистых
Чистые значения - это не то же самое, что и ассоциированные значения. Чистые значения устанавливаются в заранее заданные значения, когда Вы впервые определяете перечисление в Вашем коде, как эти три ASCII-кода выше. Чистое значение для отдельного кейса перечисления всегда одно и то же. Ассоциированные значения устанавливаются, когда Вы создаёте новую константу или переменную, основываясь на одном из кейсов перечисления, и могут отличаться всякий раз, когда Вы делаете это.
Неявно присваиваемые чистые значения
Когда Вы работаете с перечислениями, которые хранят целое значение или строку в качестве чистого значения, Вам не нужно явно присваивать чистое значение каждому кейсу. Когда Вы этого не делаете, Swift автоматически присвоит значения за Вас.

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

Перечисление ниже - это переработанный вариант раннего перечисления Planet с чистыми целыми значениями для представления порядка каждой планеты относительно солнца:
@{10.2\2}
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
В примере Planet.mercury имеет явное чистое значение 1, а Planet.venus имеет неявное чистое значение 2 и так далее.
Строки в качестве чистых значений
Когда строки используются для чистых значений, то неявное значение для каждого кейса - это текстовое описание имени кейса.

Перечисление ниже - это обновление более раннего CompassPoint с чистыми строковыми значениями для репрезентации имени каждого направления:
@{10.2\3}
enum CompassPoint: String {
    case north, south, east, west
}
В примере выше CompassPoint.south имеет неявное значение "south" и так далее.
Получение доступа к чистым значениям
Вы можете получить доступ к чистому значению кейса перечисления с помощью его свойства rawValue:
@{10.2\4}
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let earthsOrder = Planet.earth.rawValue
// earthsOrder равен 3

enum CompassPoint: String {
    case north, south, east, west
}
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection равен "west"
Инициализация из чистого значения
Если Вы определяете перечисление с помощью чистого значения, то перечисление автоматически получает конструктор, который принимает значение чистого типа (в качестве параметра с названием rawValue) и возвращает либо соответствующий кейс перечисления, либо nil. Вы можете использовать этот конструктор для попытки создания нового экземпляра перечисления.
@{10.2\5}
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet имеет тип Planet? и равен Planet.uranus
В этом примере создаётся Уран из чистого значения 7.

Однако не все возможные значения типа Int имеют совпадающую планету. Ввиду этого конструктор из чистого значения всегда возвращает кейс перечисления в виде опционала. В примере выше possiblePlanet имеет тип Planet? или "опциональная Planet."

Если Вы попытаетесь найти планету с позицией 11, то опциональное значение Planet, возвращённое из конструктора чистого значения будет равно nil.
@{10.2\6}
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Сильно загрязнена")
    default:
        print("Небезопасное место для людей")
    }
} else {
    print("Не существует планет на позиции \(positionToFind)")
}
// Выводит "Не существует планет на позиции 11"
Этот пример использует опциональное связывание для попытки доступа к планете с чистым значением 11. Инструкция if let somePlanet = Planet(rawValue: positionToFind) создаёт опционал типа Planet и устанавливает somePlanet в значение этого опционала, если оно может быть получено. В этом случае не может быть платы с позицией 11, так что вместо этого будет выполнена ветвь else.
Рекурсивные перечисления
Рекурсивное перечисление - это перечисление, которое имеет другой экземпляр этого перечисления в качестве ассоциированного значения для одного или более кейсов перечисления. Вы указываете, что кейс перечисления рекурсивен, указывая ключевое слово indirect перед ним, что скажет компилятору вставить нужный уровень индирекции.

Например, здесь приведено перечисление, которое хранит простейшие арифметические выражения:
@{10.2\7}
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
indirect перед всем перечислением
Вы так же можете написать indirect перед началом всего перечисления, чтобы включить индирекцию для всех кейсов перечисления, для которых это нужно.

Перечисление может хранить три типа арифметических выражений: простое число, сложение двух выражений, и перемножение двух выражение. Кейсы addition и multiplication имеют ассоциированные с ними значения, которые тоже являются арифметическими выражениями - эти ассоциированные выражения позволяют вкладывать выражения. Например, выражения (5 + 4) * 2 имеет число на правой стороне умножения и другое выражение на левой стороне умножения. Так как данные вложены, то перечисления используется для хранения данных, также нужных для поддержания вложения - это означает, что перечислению нужно быть рекурсивным. Код ниже демонстрирует ArithmeticExpression - рекурсивное перечисление, созданное для (5 + 4) * 2.
@{10.2\8\1}
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
@{10.2\8\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))
Работа с рекурсивными перечислениями
Рекурсивная функция - это хороший способ для работы с данными, которые имеют рекурсивную структуру. Например, здесь есть функция, которая вычисляет арифметическое выражение:
@{10.2\8\3}
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))
// Выводит "18"
Эта функция вычисляет обычное число, просто возвращая ассоциированное значение. Оно вычисляет сложение или вычитание, вычисляя выражение на левой стороне, вычисляя выражение на правой стороне, а затем складывая или перемножая их.