Обработка ошибок в Swift
Зачем все это нужно?
Обработка ошибок во время выполнения программы и самое главное — реакция приложения на эти ошибки — это хорошая практика для любого программного продукта. Согласитесь, если вы пользуетесь приложением и в какой-то момент оно без каких либо видимых проявлений «падает», — что вы подумаете про разработчика? Думается — ничего хорошего. А ведь можно в коде добавить обработчики ошибок, и в читабельном формате сообщать пользователю что именно пошло не так. С одной стороны — это значительно повышает юзабилити приложения, с другой облегчает отладку при разработке.
Однако, ошибки не всегда нужно обрабатывать. Языковые функции Swift позволяют избежать некоторых ошибок в целом. Как правило, если вы можете избежать возникновения ошибки, возьмите именно этот путь проектирования и разработки мобильного приложения iOS. Но если вы не можете избежать потенциального условия появления ошибки, то явная обработка — лучший вариант.
В этой статье я составлю памятку по работе с ошибками на языке программирования Swift 3
Избегаем ошибок nil
Так как Swift обладает элегантными опциональными возможностями обработки, вы можете полностью избежать ошибки при попытке извлечь значение из опционала, когда значение в этом самом опционале не представлено.
Быстрый пример — получение элемента из словаря по ключу возвращает опциональное значение. Т.е. если в словаре есть запрошенный ключ — то вернется значение, а если нет — то вернется nil.
Как умный программист, вы можете манипулировать этой функцией, чтобы преднамеренно перевести nil в состояние ошибки. Такой подход выгоден в случае если нужно не вызвать падение программы и при этом ничего не делать если ошибка возникла.
ва типичных примера исключения ошибок Swift с использованием nil — это проваливающиеся инициализаторы и инструкция guard.
Проваливающиеся инициализаторы
Проваливающиеся инициализаторы предотвращают создание объекта, если для их создания не предоставлена достаточная информация. До появления этих инициализаторов в Swift, да и в других языках программирования этот функционал обычно достигался с помощью шаблона Factory Method. Old-school пример создания станции технического обслуживания (СТО) выглядит как метод класса:
// // proSwift.ru // // Swift 3 import Foundation enum MotorOil: String { case valvoline = "Valvoline" case mobil = "Mobil" case castrol = "Castrol" case shell = "Shell" } struct Stantion { var oilBrand: MotorOil = .valvoline static func create(with motorOliBrand: String) -> Stantion? { if let oil = MotorOil(rawValue: motorOliBrand) { // проверяем если ли масляный бренд, который соответсвует строке, переданной в функцию var stantion = Stantion() stantion.oilBrand = oil return stantion } else { return nil } } } let audiStation = Stantion.create(with: "Castrol") let kiaStation = Stantion.create(with: "Zic")
Две последних строки нам показывают следующее — станция Audi была создана, т.к. в наших брендах масел мы смоги найти Castrol, а вот kiaStation будет равна nil, т.к. попытка найти масло Zic обернулось неудачей.
То же метод создания мы можем написать при помощи проваливающегося инициализатора. Давайте ЗАМЕНИМ метод create(motorOilBrand:) на код инициализатора:
// // proSwift.ru // // Swift 3 init?(brand: String) { if let oil = MotorOil(rawValue: brand) { self.oilBrand = oil } else { return nil } }
Также следует изменить строки создания станций.
// // proSwift.ru // // Swift 3 //let audiStation = Stantion.create(with: "Castrol") //let kiaStation = Stantion.create(with: "Zic") let audiStation = Stantion(brand: "Castrol") let kiaStation = Stantion(brand: "Zic")
То же самое: Ауди — есть объект, Киа — nil.
Инструкция guard
guard — это быстрый способ утверждать, что что-то истинно. Например, если значение > 0 или если условное выражение может быть развернуто то нужно что-то сделать (выполнить код). Затем вы можете выполнить блок кода, если проверка завершилась неудачей.
Без долгих рассуждений — сразу к примеру: изменим наш инициализатор при помощи инструкции guard
// // proSwift.ru // // Swift 3 import Foundation enum MotorOil: String { case valvoline = "Valvoline" case mobil = "Mobil" case castrol = "Castrol" case shell = "Shell" } struct Stantion { var oilBrand: MotorOil = .valvoline init?(brand: String) { guard let oil = MotorOil(rawValue: brand) else { return nil } self.oilBrand = oil } } let audiStation = Stantion(brand: "Castrol") let kiaStation = Stantion(brand: "Zic")
При этом изменении нет необходимости в переносе else на отдельную строку, и случай сбоя становится более очевидным, так как он теперь находится наверху инициализатора. Более того, код стал более читабелниый и понятный с точки зрения направления чтения (сверху вниз): то что задумывалось разработчиком и должно произойти, если не возникнут ошибки, — описано по ходу текста программы.
Устранение ошибок с помощью пользовательской обработки
Примерчик немного кривоват, но суть от этого не меняется
//: Playground - noun: a place where people can play // // proSwift.ru // // Swift 3 import Foundation enum MotorOil: String { case valvoline = "Valvoline" case mobil = "Mobil" case castrol = "Castrol" case shell = "Shell" case unknown } struct Stantion { var oilBrand: MotorOil = .valvoline init?(brand: String) { guard let oil = MotorOil(rawValue: brand) else { return nil } self.oilBrand = oil } init?(motorOil: MotorOil) { self.oilBrand = motorOil } } let audiStation = Stantion(brand: "Castrol") let kiaStation = Stantion(brand: "Zic") protocol Duty { var boxSpec: Bool? { get } var job: Stantion? { get set } func isOnDuty() -> Bool } enum CarGearBoxTypes { case automatic case manual } struct Employee: Duty { var name: String var boxSpec: Bool? var job: Stantion? var gearBox: CarGearBoxTypes? func isOnDuty() -> Bool { guard let job = self.job else { return false } return true } func fixManualGearBoxAndReturnMotorOil() -> MotorOil { if let job = job { // у работника есть ссылка на работу? if isOnDuty() { // работник вообще принят на работу? if let spec = boxSpec { // есть ли опыт работы с коробками if let gearBox = gearBox { // может машину уже разобрали и коробку сняли if gearBox == .manual { // если коробка мехарническая return job.oilBrand } } } } } return .unknown } }
У нас есть работник станции техобслуживания, который приписан к станции (job), нанят на работу (isOnDuty), имеет или не имеет опыт работы с трансмиссией (boxSpec) и автомобиль с коробкой или без нее пока неизвестного типа. Соответсвенно, чтобы выполнить работу по починке механической коробки передач и выдать бренд масла, которое было использовано при ремонте нужно сделать следующее:
- Уточнить если ли вообще у работника ссылка его работу?
- Выяснить работник вообще принят на работу?
- Понять есть ли опыт работы с коробками у работника
- Удостовериться, что коробка еще на месте и ее можно ремонтировать
- Быть уверенным, что коробка механическая а не автоматическая
Спасибо за хорошую и понятную статью. Могли бы вы так же написать про enums, struct. Про то, где их использовать, какая между ними разница и тд.