Enum в Swift: Перечисления Возможных Сценариев
Перечисления в Swift представляют собой мощный инструмент для создания собственных типов данных, которые ограничивают возможные значения. Они могут использоваться для улучшения читаемости кода, предотвращения ошибок и структурирования данных. Есть много замечательных статей по этой теме, но мне хочется рассмотреть эту тему более детально.
Начнем с самого простого, для создания перечисления мы используем ключевое слово enum:
enum DayOfWeek
Это определяет перечисление DayOfWeek , которое может принимать одно из значений: sunday , monday , tuesday и т.д
let currentDay = DayOfWeek.sunday
Поскольку набор определенных значений в перечислении конечен очень распространено использование оператора switch для перебора значений вместо использования нескольких операторов if else:
switch currentDay
Associated Values
Однако иногда нам может потребоваться хранить дополнительные данные для каждого случая перечисления. Эта дополнительная информация, прикрепленная к значениям перечисления, называется связанными значениями (Associated Values).
Давайте рассмотрим подробный пример, используя перечисление Result , которое представляет результат выполнения операции с ассоциированными значениями для успешного результата или ошибки:
enum Result
В этом примере, Result имеет два случая:
- .success(T) : представляет успешный результат операции и ассоциированное значение типа T
- .failure(String) : представляет ошибку и ассоциированное текстовое сообщение об ошибке
Теперь создадим функцию, которая выполняет деление двух чисел и возвращает Result с результатом или ошибкой:
func divide(_ dividend: Double, by divisor: Double) -> Result < if divisor == 0 < return .failure("Division by zero is not allowed.") >let result = dividend / divisor return .success(result) >
Теперь мы можем использовать эту функцию для выполнения деления и обработки результата с использованием ассоциированных значений:
let result1 = divide(10, by: 2) switch result1 < case .success(let value): print("Результат: \(value)") case .failure(let error): print("Ошибка: \(error)") >let result2 = divide(5, by: 0) switch result2
В первом случае result1 будет .success(5.0) , и мы получим «Результат: 5.0«. Во втором случае result2 будет .failure(«Division by zero is not allowed.») , и мы получим «Ошибка: Делить на ноль нельзя«
Таким образом, ассоциированные значения в перечислениях позволяют передавать дополнительные данные в каждом случае перечисления, что полезно для представления различных сценариев и обработки ошибок.
Raw Values
Рассмотрим пример с перечислением, использующим сырые значения (raw values — могут быть строками, символами или числами). Возьмем перечисление HTTPStatus , которое представляет стандартные HTTP статусы, и свяжем каждый статус с его числовым кодом:
enum HTTPStatus: Int
Здесь каждый статус HTTP связан с числовым значением, которое соответствует его коду ответа.
Теперь мы можем использовать это перечисление для представления статусов HTTP в нашем коде:
let responseCode = 404 if let httpStatus = HTTPStatus(rawValue: responseCode) < switch httpStatus < case .ok: print("Success: 200 OK") case .notFound: print("Error: 404 Not Found") case .internalServerError: print("Error: 500 Internal Server Error") default: print("Received an HTTP status with code \(httpStatus.rawValue)") >> else
В этом примере мы получаем код ответа от сервера (404), затем создаем экземпляр перечисления HTTPStatus с использованием rawValue , и в конечном итоге, на основе значения, выполняем соответствующие действия. Это помогает нам легко работать с HTTP статусами и улучшает читаемость кода.
Продвинутые концепции
Ассоциированные значения с функциями
Ассоциированные значения с функциями в перечислениях могут быть использованы для создания гибких и мощных структур данных, позволяющих представлять различные сценарии и действия.
Рассмотрим подробный пример, используя перечисление Operation , которое представляет различные математические операции с ассоциированными функциями:
enum Action < case printMessage(() ->Void) case calculateSum((Int, Int) -> Int) > // Создадим экземпляры перечисления с ассоциированными функциями let printAction = Action.printMessage(< print("Hello, World!") >) let sumAction = Action.calculateSum(< a, b in return a + b >) // Выполним ассоциированные функции switch printAction < case .printMessage(let action): action() case .calculateSum(let action): // Этот случай не будет выполнен в данном примере break >switch sumAction < case .printMessage(let action): // Этот случай не будет выполнен в данном примере break case .calculateSum(let action): let result = action(5, 3) print("Result is: \(result)") >//Вывод: //Hello, World! //Result is: 8
- Мы создаем перечисление Action , где каждый случай перечисления имеет ассоциированную функцию
- Мы создаем экземпляры перечисления Action , привязывая к ним соответствующие функции
- Мы используем оператор switch , чтобы выполнить ассоциированные функции в зависимости от типа действия
- В первом switch мы выполняем функцию для printAction , которая печатает «Hello, World!»
- Во втором switch мы выполняем функцию для sumAction , которая выполняет сложение двух чисел (5 и 3) и печатает результат
Таким образом, ассоциированные функции позволяют нам передавать и выполнять различные действия в зависимости от типа действия в перечислении.
Использование associatedtype с ассоциированными значениями
Использование associatedtype с ассоциированными значениями позволяет сделать перечисления в Swift более гибкими и адаптивными. Это особенно полезно, когда нам нужно, чтобы ассоциированные значения могли быть разных типов для разных случаев перечисления.
Предположим, у нас есть перечисление Shape , которое представляет разные геометрические фигуры, такие как круг, прямоугольник и треугольник. Мы хотим, чтобы каждая фигура имела разные ассоциированные значения, такие как радиус для круга, ширина и высота для прямоугольника и стороны для треугольника. Вот как это можно сделать с использованием associatedtype :
enum Shape
Теперь создадим протокол Drawable , который будет использовать associatedtype для определения ассоциированных значений для каждой фигуры:
protocol Drawable < associatedtype DrawingType func draw() ->DrawingType >
Расширим перечисление Shape , чтобы оно соответствовало этому протоколу и определит ассоциированный тип DrawingType для каждой фигуры:
extension Shape: Drawable < typealias DrawingType = String func draw() ->DrawingType < switch self < case .circle(let radius): return "Рисуем круг с радиусом \(radius)" case .rectangle(let width, let height): return "Рисуем прямоугольник с шириной \(width) и высотой \(height)" case .triangle(let side1, let side2, let side3): return "Рисуем треугольник со сторонами \(side1), \(side2), \(side3)" >> >
Теперь мы можем создавать экземпляры фигур и вызывать метод draw() , который возвращает строку, представляющую отрисовку фигуры:
let circle = Shape.circle(radius: 5.0) let rectangle = Shape.rectangle(width: 10.0, height: 8.0) let triangle = Shape.triangle(side1: 3.0, side2: 4.0, side3: 5.0) print(circle.draw()) // Вывод: Рисуем круг с радиусом 5.0 print(rectangle.draw()) // Вывод: Рисуем прямоугольник с шириной 10.0 и высотой 8.0 print(triangle.draw()) // Вывод: Рисуем треугольник со сторонами 3.0, 4.0, 5.0
Рекурсивные перечисления
indirect enum — это способ создания перечислений, в которых случаи могут содержать значения того же типа, что и само перечисление. Иными словами, это означает, что внутри перечисления могут быть элементы, которые по сути представляют собой другие экземпляры этого же перечисления.
Рассмотрим подробнее, создадим рекурсивное перечисление для представления простых арифметических выражений. Выражения могут быть либо целыми числами, либо бинарными операциями (сложение или умножение) между двумя выражениями:
indirect enum ArithmeticExpression
- ArithmeticExpression — это рекурсивное перечисление, которое может иметь три случая: .number , представляющий целое число, .addition , представляющий операцию сложения между двумя выражениями, и .multiplication , представляющий операцию умножения между двумя выражениями.
- Ключевое слово indirect используется перед случаями .addition и .multiplication , чтобы указать, что эти случаи могут содержать рекурсивные значения (другие выражения).
Теперь мы можем создавать и вычислять арифметические выражения:
let expression = ArithmeticExpression.addition( .number(5), .multiplication( .number(2), .number(3) ) ) func evaluate(_ expression: ArithmeticExpression) -> Int < switch expression < case .number(let value): return value case .addition(let left, let right): return evaluate(left) + evaluate(right) case .multiplication(let left, let right): return evaluate(left) * evaluate(right) >> let result = evaluate(expression) print("Результат вычисления: \(result)") // Вывод: Результат вычисления: 11
В этом примере мы создаем арифметическое выражение, которое представляет выражение 5 + (2 * 3) . Затем мы используем рекурсивную функцию evaluate , чтобы вычислить значение этого выражения.
Протоколы и перечисления
Использование протоколов с перечислениями может приносить множество пользы в разработке программного обеспечения, это делает ваш код более гибким, чистым и модульным, что улучшает его качество и поддерживаемость, позволяет легче управлять различными состояниями и типами данных в вашем приложении.
// Протокол Playable определяет объекты, которые можно воспроизводить protocol Playable < func play() >// Перечисление MediaType представляет различные типы медиафайлов enum MediaType < case audio(title: String, duration: TimeInterval) case video(title: String, duration: TimeInterval) case text(title: String, content: String) >// Реализация протокола Playable для перечисления MediaType extension MediaType: Playable < func play() < switch self < case .audio(let title, let duration): print("Воспроизведение аудио: \(title), Длительность: \(duration) секунд") case .video(let title, let duration): print("Воспроизведение видео: \(title), Длительность: \(duration) секунд") case .text(let title, let content): print("Отображение текста: \(title) - \(content)") >> > // Создаем медиафайлы с использованием перечисления MediaType let audioFile = MediaType.audio(title: "Music", duration: 180.0) let videoFile = MediaType.video(title: "Movie", duration: 1200.0) let textFile = MediaType.text(title: "Document", content: "Это образец документа") // Воспроизводим или отображаем медиафайлы audioFile.play() videoFile.play() textFile.play()
- Мы создали протокол Playable , который определяет единственное требование — метод play() , предназначенный для воспроизведения объекта.
- Создано перечисление MediaType , которое представляет различные типы медиафайлов: аудио, видео и текст. Каждый случай перечисления имеет связанные значения для хранения информации о медиафайле.
- Расширение MediaType реализует протокол Playable , предоставляя метод play() , который выводит информацию о воспроизведении или отображении медиафайла.
- Мы создали разные медиафайлы, используя случаи перечисления MediaType .
- Вызвали метод play() для каждого медиафайла, демонстрируя, как протокол Playable позволяет воспроизводить или отображать разные типы медиафайлов с использованием единого интерфейса.
Пользовательские операторы
Пользовательские операторы перечисления могут быть полезными для улучшения читаемости и удобства вашего кода. Давайте рассмотрим подробные примеры пользовательских операторов перечисления.
Оператор для сравнения приоритетов задач:
Допустим, у вас есть перечисление TaskPriority , которое представляет разные приоритеты задач:
enum TaskPriority
Вы можете добавить пользовательский оператор для сравнения приоритетов задач:
infix operator < func < (lhs: TaskPriority, rhs: TaskPriority) ->Bool < switch (lhs, rhs) < case (.low, .medium), (.low, .high), (.medium, .high): return true default: return false >> let lowPriority = TaskPriority.low let mediumPriority = TaskPriority.medium let highPriority = TaskPriority.high if lowPriority < mediumPriority < print("Low priority меньше чем Medium priority") >//Вывод: Low priority меньше чем Medium priority
Оператор для объединения имен файлов:
Предположим, у вас есть перечисление File , которое представляет файлы, и вы хотите объединить их имена с помощью оператора + :
enum File < case image(String) case document(String) >infix operator + func + (lhs: File, rhs: File) -> File < switch (lhs, rhs) < case let (.image(name1), .image(name2)): return .image(name1 + name2) case let (.document(name1), .document(name2)): return .document(name1 + name2) default: return .document("Invalid") >> let imageFile1 = File.image("image1.jpg") let imageFile2 = File.image("image2.jpg") let documentFile1 = File.document("document1.pdf") let combinedImageFiles = imageFile1 + imageFile2 let combinedDocumentFiles = documentFile1 + imageFile1 print("Combined Image Files: \(combinedImageFiles)") print("Combined Document Files: \(combinedDocumentFiles)")
В этом примере мы определили пользовательский оператор + , который объединяет имена файлов из случаев File . Мы также проверяем типы файлов и возвращаем «Invalid» , если пытаемся объединить файлы разных типов.
Заключение:
Перечисления — ключевая часть языка, которая помогает в создании чистого, понятного и безопасного кода, они удивительно полезны при проектировании архитектуры приложения, создании API и обработке состояний, так что держим этот инструмент в своем арсенале и используем его с умом!
- Программирование
- Совершенный код
- Разработка под iOS
- Разработка мобильных приложений
- Swift
Enumerated swift что это
Перечисление (enumeration) определяет общий тип для группы связанных значений. Причем сами объединенные в перечисление значения могут представлять любой тип — число, строку и так далее.
Для создания перечисления используется ключевое слово enum :
enum Season
Каждое отдельное значение в перечислении указывается после оператора case . В данном случае перечисление называется Season и представляет времена года и имеет четыре значения. То есть фактически Season представляет новый тип данных.
Также допустима сокращенная форма перечисления значений:
enum Season
После определения перечисления мы сможем его использовать в программе:
var currentSeason = Season.Spring
Здесь переменная currentSeason представляет тип Season . Впоследствии мы можем присвоить этой переменной другое значение из Season:
var currentSeason = .Summer
Либо мы можем сначала определить переменную / константу типа перечисления, а потом ее инициализировать:
let lastSeason: Season lastSeason = Season.Winter
С помощью конструкции switch можно узнать, какое значение содержит переменная / константа, представляющая перечисление:
enum Season < case Winter, Spring, Summer, Autumn >let currentSeason = Season.Spring switch(currentSeason)
Ассоциированные значения
С каждым значением в перечислении мы можем ассоциировать какое-либо другое значения, которое может представлять любой тип. При этом ассоциированные значения для разных значений одного и того же перечисления могут представлять различные типы.
Итак, пусть у нас перечисление представляет игрового персонажа:
enum Person
Здесь перечисление определяет два возможных значения — двух игровых персонажей: человека (Human) и эльфа (Elf). Однако у человека мы можем задать два парамета: имя (String) и количество жизней (Int). А у эльфа нам нужен только один параметр — имя (String).
То есть в данном случае значение Person.Human будет ассоциировано с двумя значениями String и Int, а значение Person.Elf — с одним значением типа String:
var hero = Person.Human("Trogvar", 5) hero = Person.Elf("Feonor")
С помощью конструкции switch мы также можем определить значение объекта:
switch(hero)
Но при необходимости мы также можем получить ассоциированные значения:
switch(hero)
Для получения значений мы можем определить константы или переменные, в которые будут передаваться ассоциированные значения.
Чистые значения перечислений
Кроме ассоциированных значений члены перечисления могут иметь чистые значения (raw values). Например, пусть у нас есть перечисление, представляющее флагманы различных производителей:
enum Flagman: String
При определении чистых значений нам надо указать их тип. В данном случае типом будет выступать тип String. Затем в программе мы сможем получить эти чистые значения с помощью свойства rawValue :
var myPhone = Flagman.Apple print(myPhone) // Apple print(myPhone.rawValue) // iPhone X
Если мы укажем тип прямых значений, но не укажем эти самые значения, то swift использует значения по умолчанию.
Если тип — String, то чистые значения будут представлять строковое представление элементов перечисления:
enum Flagman: String < case Samsung, Apple, Microsoft, Google >var myPhone = Flagman.Apple print(myPhone) // Apple print(myPhone.rawValue) // Apple
Фактически будет совпадение между значениями перечисления и их чистыми значениями.
Если типом для чистых значений является тип Int, то элементы перечисления получат значения по порядку:
enum DayOfWeek: Int < case Monday=1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday >var currentDay = DayOfWeek.Wednesday print(currentDay) // Wednesday print(currentDay.rawValue) // 3
Первый элемент перечисления Monday=1 задает начальное значение для элементов перечисления. Если же мы не укажем его, то начальным значением будет 0.
Используя чистое значение, мы можем получить элемент перечисления:
enum DayOfWeek: Int < case Monday=1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday >var currentDay = DayOfWeek(rawValue: 7) // Optional(DayOfWeek.Sunday) print(currentDay!)
В данном случае мы пытаемся получить элемент перечисления, который имеет чистое значение 7. Но данная операция возвращает не просто элемент перечисления, а объект Optional, то есть такой объект, который может иметь конкретное значение, а может иметь значение nil (отсутствие значения).
И если мы попытаемся получить, например, элемент с чистым значением 8, то мы получим nil. Поэтому мы можем применять условное выражение if для проверки полученного значения перед его использованием:
if let day = DayOfWeek(rawValue: 8)
Методы перечислений
Как классы и структуры, перечисления могут определять методы. Например:
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 someDay.getCurrentDay() // Воскресенье var secondDay = DayOfWeek.getDay(number: 2) // Вторник
Свойства перечислений
Перечисления также могут иметь свойства, но это не могут быть хранимые свойства. А вычисляемые свойства вполне будут работать:
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) // Понедельник print(DayOfWeek.Friday.label) // Пятница
В данном случае свойство 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) // 5
В данном случае инициализатор принимает название дня недели и на его основании устанавливает значение текущего объекта. Если переданное название не распознано, то инициализатор возвращает nil.
Перечисления (enum) в Swift: ассоциированные и чистые значения
Перечисление представляет собой группу значений, которые связаны между собой.
Хорошим примером являются направления на компасе:
enum Compass case north case east case south case west >
Перечисления помогают нам работать со связанными значениями наиболее безопасным способом.
Проще всего рассматривать перечисления как структурированные списки связанных элементов. К примеру:
- Цвета: красный, зеленый, синий, фиолетовый, желтый.
- Вкусы мороженого: ваниль, шоколад, клубника.
- Состояния аутентификации: .authenticated, .unauthenticated, .undetermined.
- Состояния воспроизведения: .playing, .paused, .stoppped.
Синтаксис перечислений довольно прост:
- Мы используем ключевое слово enum для объявления перечисления с используем конструкцию enum имя < тело >.
- В теле перечислений мы включаем ряд элементов перечисления с помощью ключевого слова case. Варианты перечисления – это разные значения, которые у него есть, например, вышеупомянутые ароматы мороженого.
- Перечисления могут иметь ассоциированные значения (associated types), а также чистые значения (raw value).
Мы можем определить все перечисления в одной строке:
case Weekday case monday, tuesday, wednesday, thursday, friday, saturday, sunday >
Использовать перечисление довольно просто:
let today: Weekday = Weekday.monday
Приведенный выше код можно сократить:
let today: Weekday = .monday
Также можно использовать:
let today = Weekday.monday
Благодаря выводу типа нам не нужно каждый раз явно объявлять тип переменной или константы. Как только тип перечисления был объявлен, мы можем использовать более короткий синтаксис:
var direction: Compass = .north direction = .south
Перечисления нельзя изменить после установки.
enum AuthenticationState case authenticated case unauthenticated case undetermined >
Значения перечисления являются исчерпывающими, и вы можете быть уверены, что любое значение типа AuthenticationState является любым из трех значений. Это обеспечивает безопасность в вашем коде и приводит к меньшему количеству ошибок.
Перечисления, состояния и switch
Перечисления эффективно комбинировать с оператором switch. Возможно, вы слышали о понятии «состояние» раньше. В разработке для iOS мы часто управляем состоянием различных параметров и взаимодействий в нашем приложении.
Представьте, что мы кодируем робота. У робота есть эмоции. Мы моделируем эти эмоции или состояния с помощью перечисления:
enum Emotion case happy, sad, angry, scared, surprised >
Теперь мы можем использовать оператор switch, чтобы реагировать на эти эмоции:
switch robot.mood case .angry: robot.destroyAllHumans() case .sad: robot.rust() // Робот плачет и ржавеет. case .happy: robot.play("happy.mp3") case default: print("Неизвестная эмоция.") >
Каждый case в блоке switch соответствует отдельному case в перечислении.
Ассоциированные значения (associated values) в перечислениях
Перечисления позволяют вам присвоить одно из предопределенных значений переменной или константе. Также мы можем связать значения с отдельными случаями перечисления.
Представьте, что мы создаем приложение для ролевой игры (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("Это не броня.") > // Броня слишком тяжелая!
В приведенном выше коде мы используем ключевое слово where для сопоставления .armor случаев, когда weight меньше 10. Во втором случае мы сопоставляем любой .armor случай, который не соответствует первому, то есть любой, для которого weight больше или равен 10.
В Swift часто встречаются перечисления с ассоциированными значениями. Возьмем, к примеру, перечисление Result для Alamofire, популярной сетевой библиотеки.
public enum ResultValue> 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 values) для перечислений
Вместо связанных значений перечисления могут иметь чистые значения:
enum Flavor: String case vanilla = "vanilla" case strawberry = "strawberry" case chocolate = "chocolate" >
Каждый случай перечисления Flavor использует значения типа String.
Посмотрим, как можно работать с чистыми значениями:
let icecream = Flavor.vanilla print(icecream.rawValue) // vanilla
Мы также можем получить значение Flavor прямо из чистого значения. При этом инициализатор чистого значения возвращает опциональный параметр:
let icecream = Flavor(rawValue: "vanilla") print(icecream) // Optional(Flavor.vanilla)
Чистые значения также могут назначаться неявно:
enum Weekday: Int case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday >
В приведенном выше примере исходное значение .sunday равно 7.
Для значений типа String это работает аналогично. Чистые значения будут представлять собой строковые значения элементов перечисления:
enum Compass: String case north, east, south, west > let direction = Compass.west print(direction.rawValue == "west") // true
Перечисления в SwiftСила перечислений (энумов) в Swift
Перечисления, или энумы, являются символическим способом представить тип “один из”. В этой статье мы изучим ту гибкость, которую перечисления в Swift привносят в таблицы, и как они могут упростить и сделать нагляднее наш код.
Думаю, что не будет ошибкой сказать, что птица может быть одной из следующих: Galah (розовый какаду), Kookaburra (Кукаберра) или какой-то другой.
В C мы могли бы представить концепцию следующим образом:
enum < Galah, Kookaburra, Other >; int main(int argc, char *argv[])
Нужно признать, что перечисления в C довольно “дырявая абстракция”. Так как они являются по существу только перечислениями ints , мы можем делать странные вещи, например добавлять их вместе. Что должно значить Galah + Kookaburra, если они равны 1?
В Swift также есть перечисления и они работают очень похоже с C:
enum Bird: Int < case Galah case Kookaburra case Other >print(Bird.Kookaburra.rawValue)
Для того, чтобы в Swift перечисления начали работать как в C, мы должны явно заявить, что они основаны на Int , а затем извлечь значения со свойством rawValue. Так происходит потому, что Swift “менее дырявый”, чем C и строже в соблюдении типа безопасности.
Так как ints не подходит для представления типов птиц, в данном случае мы вообще не будем упоминать основной тип хранения. Вместо этого мы просто напишем:
enum Bird < case Galah case Kookaburra case Other >print(Bird.Kookaburra.rawValue)
Но теперь наша программа просто выдает (Enum Value), а нам это не очень поможет. Как бы мы могли посмотреть фактическое значение?
Есть несколько вариантов. Мы можем использовать трюк с Int, как указано выше, но мы можем сделать и лучше. Мы можем сделать так, что наше перечисление будет основываться на другом типу, скажем, на Strings :
enum Bird: String < case Galah = "Galah" case Kookaburra = "Kookaburra" case Other = "Other" >print(Bird.Kookaburra.rawValue)
Еще более мощная альтернатива- это использовать протокол CustomStringConvertible:
enum Bird: CustomStringConvertible < case Galah case Kookaburra case Other var description: String < switch self < case Galah: return "Galah" case Kookaburra: return "Kookaburra" case Other: return "Other" >> > print(Bird.Kookaburra)
Протокол объявляет свойство, называемое description, которое возвращает строку, описывающую тип. Концептуально это похоже на метод description протокола NSObject в Objective-C.
Этот пример также показывает нам еще одну интересную особенность перечислений в Swift: они могут содержать свойства и функции, как классы.
Чего-чего?
В Swift перечисления могут также иметь связанные значения и именно с этого места происходит действительное расхождение с почтенным наследием C.
Мы можем добавить параметры кейсам наших перечислений. Давайте изменим наш кейс Other, чтобы он имел соответствующее имя:
case Other(name: String)
Теперь давайте исправим свойство description, для того чтобы вернуть это имя. Мы делаем это, связывая переменную с образцом в нашем выражении switch.
var description: String < switch self < //. case let Other(name): return name >>
Теперь мы можем дать имена всем птицам, не охваченным в нашем перечислении и разумно их зарегистрировать:
let bird = Bird.Other(name: "Cockatoo") print(bird)
Бинарные деревья
Скажем, мы хотим построить наше собственное бинарное дерево типа данных, который будет хранить отсортированные Int.
Если бы мы хотели использовать класс, мы могли бы сделать это так:
class Tree
Мы хотим выбросить в constructor:
init(value: Int, left: Tree?, right: Tree?)
Пока все идет нормально. Хороший, простой код. Обратите внимание, что мы используем опциональные типы для левого и правого поддеревьев (subtrees), так как некоторые узлы не будут иметь поддеревья (дерево должно остановиться где-нибудь).
Мы также хотим использовать функцию вставки, которая примет значение и возвратит новое дерево, которое она хранит в соответствующем месте:
func insert(newValue: Int) -> Tree < if newValue >value < if let theRight = right < return Tree(value: value, left: left, right: theRight.insert(newValue)) >else < return Tree(value: value, left: left, right: Tree(value: newValue, left: nil, right: nil)) >> else < if let theLeft = left < return Tree(value: value, left: theLeft.insert(newValue) right: right) >else < return Tree(value: value, left: Tree(value: newValue, left: nil, right: nil), right: right) >> >
Эта функция рекурсивно обходит дерево, чтобы найти точку, где она может вставить новое значение. Поскольку мы используем опциональные типы для поддеревьев, мы должны исказить код, чтобы обойти те случаи, когда они существуют или не существуют.
Этот код является функциональным, но трудно читаемым. Это может быть исправлено, путем извлечения функции и с помощью какого-либо другого рефакторинга, но давайте примем немного иной курс.
Бинарные деревья с перечислениями
Так как перечисления могут иметь связанные с ними значения, то мы можем просто рекурсивно обозначить дерево следующим образом:
indirect enum Tree
У нас все еще есть проблема, что поддеревья (subtrees) узла могут не существовать, но вместо использования опционалов, давайте смоделируем это с другим кейсом в нашем перечислении:
indirect enum Tree
Наши левые и правые поддеревья теперь гарантированно не будут nil. Вместо этого, они являются “одними из” узла или пустым деревом. Следовательно, наш метод вставки может быть упрощен, так как места, с которыми мы имеем дело с пустого дерева могут быть объединены.
Сначала мы должны добавить функцию insert() в наше перечисление и реализовать ее там:
func insert(newValue: Int) -> Tree < switch self < case .Empty: return Tree.Node(value: newValue, left: Tree.Empty, right: Tree.Empty) case let .Node(value, left, right): if newValue >value < return Tree.Node(value: value, left: left, right: right.insert(newValue)) >else < return Tree.Node(value: value, left: left.insert(newValue), right: right) >> >
В этой функции логика гораздо более читабельна, чем та, что есть в нашем дереве, основанном на классах. В этом случае меньше дублирования кода, и синтаксическая структура отражает алгоритмическую структуру, а не омрачается какими-то особыми случаями.
Преобразуя в код определенные знания о древовидной структуре данных в кейсы нашего энума, вместо использования универсального опционального типа «catch-all», мы можем написать более чистый и краткий код.
Подведем итог
Мы заканчиваем с полным перечислением древовидного типа, и давайте добавим свойтсво depth и сделаем перечисление CustomStringConvertible :
indirect enum Tree: CustomStringConvertible < case Empty case Node(value: Int, left: Tree, right: Tree) var depth: Int < switch self < case .Empty: return 0 case let .Node(_, left, right): return 1 + max(left.depth, right.depth) >> var description: String < switch self < case .Empty: return "." case let .Node(value, left, right): return "[\(left) \(value) \(right)]" >> func insert(newValue: Int) -> Tree < switch self < case .Empty: return Tree.Node(value: newValue, left: Tree.Empty, right: Tree.Empty) case let .Node(value, left, right): if newValue >value < return Tree.Node(value: value, left: left, right: right.insert(newValue)) >else < return Tree.Node(value: value, left: left.insert(newValue), right: right) >> > >
В этой статье, мы имели дело только с бинарными деревьями целых чисел. В Swift, однако, есть дженерики, которые позволяют сделать сделать наше бинарное дерево для любого типа значений, а не только для целых чисел.
Что дальше?
Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!
Урок подготовил: Акулов Иван