Я еще не встречал программиста, который любит обрабатывать ошибки. Нравится вам это или нет, но надежное приложение должно обрабатывать ошибки таким образом, чтобы приложение оставалось функциональным и информировало пользователя при необходимости. Как и тестирование, это часть работы.
1. Цель-C
В Objective-C было слишком легко игнорировать обработку ошибок. Взгляните на следующий пример, в котором я игнорирую любые ошибки, которые могут возникнуть в результате выполнения запроса на выборку.
1
2
3
4
5
6
7
|
// Execute Fetch Request
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
if (results) {
// Process Results
…
}
|
Приведенный выше пример показывает, что обработка ошибок в Objective-C — это то, что разработчик должен выбрать. Если вы хотите узнать, что пошло не так, если что-то пошло не так, то вы сообщите об этом API, передав ему указатель NSError
. Пример ниже иллюстрирует, как это работает в Objective-C.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
NSError *error = nil;
// Execute Fetch Request
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
// Handle Error
…
} else {
// Process Results
…
}
|
В то время как более ранние версии Swift не предлагали хорошего решения для обработки ошибок, Swift 2 дал нам то, о чем мы просили, и это стоило ожидания. В Swift 2 обработка ошибок включена по умолчанию. В отличие от Objective-C, разработчики должны явно указать компилятору, если они решат игнорировать обработку ошибок. Хотя это не заставит разработчиков принимать обработку ошибок, оно делает решение явным.
Если бы мы перевели приведенный выше пример на Swift, мы бы получили такое же количество строк. Несмотря на то, что объем кода, который вам нужно написать, остается неизменным, синтаксис делает его очень четким, что вы пытаетесь сделать.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
do {
// Execute Fetch Request
let results = try managedObjectContext.executeFetchRequest(fetchRequest)
// Process Results
…
} catch {
let fetchError = error as NSError
// Handle Error
…
}
|
В конце этого урока вы поймете приведенный выше фрагмент кода и узнаете все, что вам нужно знать для обработки ошибок в Swift 2.
2. Метательные функции
throws
Основой обработки ошибок в Swift является способность функций и методов генерировать ошибки. На языке Swift функция, которая может генерировать ошибки, называется функцией выброса. Определение функции бросающей функции очень ясно показывает эту способность, как показано в следующем примере.
1
|
init(contentsOfURL url: NSURL, options readOptionsMask: NSDataReadingOptions) throws
|
Ключевое слово throws
указывает, что init(contentsOfURL:options:)
может выдать ошибку, если что-то пойдет не так. Если вы вызываете функцию throwing, компилятор выдаст ошибку, говоря об иронии. Это почему?
1
|
let data = NSData(contentsOfURL: URL, options: [])
|
try
Создатели Swift уделили много внимания тому, чтобы сделать язык выразительным, и обработка ошибок именно такова, выразительна. Если вы попытаетесь вызвать функцию, которая может выдать ошибку, вызову функции должно предшествовать ключевое слово try
. Ключевое слово try
не является волшебным. Все, что он делает, это заставляет разработчика осознавать способность функции кидать.
Подожди секунду. Компилятор продолжает жаловаться, хотя мы предшествовали вызову функции с ключевым словом try
. Чего нам не хватает?
Компилятор видит, что мы используем ключевое слово try
, но правильно указывает на то, что у нас нет возможности отследить любые ошибки, которые могут быть сгенерированы. Чтобы отлавливать ошибки, мы используем новое заявление Свифта do-catch
.
do-catch
Если функция выброса выдает ошибку, ошибка будет автоматически распространяться за пределы текущей области, пока не будет обнаружена. Это похоже на исключения в Objective-C и других языках. Идея состоит в том, что ошибка должна быть поймана и обработана в некоторый момент. Более конкретно, ошибка распространяется до тех пор, пока она не будет catch
предложением do-catch
оператора do-catch
.
В обновленном примере ниже мы вызываем методы init(contentsOfURL:options:)
в операторе do-catch
. В предложении do
мы вызываем функцию, используя ключевое слово try
. В предложении catch
мы обрабатываем все ошибки, возникшие при выполнении функции. Это образец, который очень распространен в Swift 2.
1
2
3
4
5
|
do {
let data = try NSData(contentsOfURL: URL, options: [])
} catch {
print(«\(error)»)
}
|
В предложении catch
вас есть доступ к ошибке, которая возникла из-за локальной постоянной error
. Предложение catch
гораздо более мощное, чем показано в приведенном выше примере. Мы рассмотрим более интересный пример чуть позже.
3. Бросать ошибки
В Objective-C вы обычно используете NSError
, определенный в платформе Foundation, для обработки ошибок. Поскольку язык не определяет, как следует реализовывать обработку ошибок, вы можете сами определять свой класс или структуру для создания ошибок.
Это не правда в Swift. Хотя любой класс или структура могут выступать в качестве ошибки, они должны соответствовать протоколу ErrorType
. Протокол, однако, довольно легко реализовать, так как он не объявляет какие-либо методы или свойства.
Перечисления являются мощными в Swift, и они хорошо подходят для обработки ошибок. Перечисления отлично подходят для функциональности сопоставления с образцом в предложении do-catch
оператора do-catch
. Проще проиллюстрировать это на примере. Начнем с определения перечисления, соответствующего протоколу ErrorType
.
1
2
3
4
5
6
|
enum PrinterError: ErrorType {
case NoToner
case NoPaper
case NotResponding
case MaintenanceRequired
}
|
Мы определяем enum, PrinterError
, который соответствует протоколу ErrorType
. Перечисление имеет четыре переменных-члена. Теперь мы можем определить функцию для печати документа. Мы передаем функции экземпляр NSData
и сообщаем компилятору, что она может генерировать ошибки, используя ключевое слово throws
.
1
2
3
|
func printDocumentWithData(data: NSData) throws {
…
}
|
Чтобы напечатать документ, мы вызываем printDocumentWithData(_:)
. Как мы видели ранее, нам нужно использовать ключевое слово try
и заключить вызов функции в оператор do-catch
. В приведенном ниже примере мы обрабатываем любые ошибки в предложении catch
.
1
2
3
4
5
|
do {
try printDocumentWithData(data)
} catch {
// Handle Errors
}
|
Мы можем улучшить пример, проверив выдавшуюся ошибку. Предложение catch
похоже на оператор switch
в том смысле, что оно допускает сопоставление с образцом. Посмотрите на обновленный пример ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
do {
try printDocumentWithData(data)
} catch PrinterError.NoToner {
// Notify User
} catch PrinterError.NoPaper {
// Notify User
} catch PrinterError.NotResponding {
// Schedule New Attempt
}
|
Это выглядит намного лучше. Но есть одна проблема. Компилятор уведомляет нас, что мы не обрабатываем каждую возможную ошибку, которую может printDocumentWithData(_:)
метод printDocumentWithData(_:)
.
Компилятор прав конечно. Предложение catch
похоже на оператор switch
в том смысле, что оно должно быть исчерпывающим и обрабатывать все возможные случаи. Мы можем добавить еще одно предложение catch для PrinterError.MaintenanceRequired
или добавить в конце предложение catch-all. При добавлении предложения catch
умолчанию ошибка компилятора должна исчезнуть.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
do {
try printDocumentWithData(data)
} catch PrinterError.NoToner {
// Notify User
} catch PrinterError.NoPaper {
// Notify User
} catch PrinterError.NotResponding {
// Schedule New Attempt
} catch {
// Handle Any Other Errors
}
|
4. Уборка после себя
Чем больше я узнаю о языке Swift, тем больше ценю его. defer
заявление — еще одно замечательное дополнение к языку. Название довольно хорошо подводит итог, но позвольте мне показать вам пример, чтобы объяснить концепцию.
01
02
03
04
05
06
07
08
09
10
11
|
func printDocumentWithData(data: NSData) throws {
if canPrintData(data) {
powerOnPrinter()
try printData(data)
defer {
powerOffPrinter()
}
}
}
|
Пример немного надуманный, но он иллюстрирует использование defer
. Блок оператора defer
выполняется до того, как выполнение выходит из области, в которой появляется оператор defer. Вы можете прочитать это предложение еще раз.
Это означает, что powerOffPrinter()
вызывается, даже если printData(_:)
выдает ошибку. Я уверен, что вы можете видеть, что это работает очень хорошо с обработкой ошибок Swift.
Положение оператора defer
операторе if
не имеет значения. Следующий обновленный пример идентичен в том, что касается компилятора.
01
02
03
04
05
06
07
08
09
10
11
|
func printDocumentWithData(data: NSData) throws {
if canPrintData(data) {
defer {
powerOffPrinter()
}
powerOnPrinter()
try printData(data)
}
}
|
Вы можете иметь несколько операторов defer
если помните, что они выполняются в обратном порядке, в котором они появляются.
5. Распространение
Вполне возможно, что вы не хотите обрабатывать ошибку, а вместо этого позволите ей всплыть на объект, который способен или отвечает за обработку ошибки. Это хорошо. Не каждое выражение try
должно быть заключено в оператор do-catch
. Однако есть одно условие: функция, которая вызывает функцию выброса, должна быть самой функцией выброса. Посмотрите на следующие два примера.
1
2
3
4
5
6
7
8
|
func printTestDocument() {
// Load Document Data
let dataForDocument = NSData(contentsOfFile: «pathtodocument»)
if let data = dataForDocument {
try printDocumentWithData(data)
}
}
|
1
2
3
4
5
6
7
8
|
func printTestDocument() throws {
// Load Document Data
let dataForDocument = NSData(contentsOfFile: «pathtodocument»)
if let data = dataForDocument {
try printDocumentWithData(data)
}
}
|
Первый пример приводит к ошибке компилятора, потому что мы не обрабатываем ошибки, которые может printDocumentWithData(_:)
. Мы решили эту проблему во втором примере, отметив printTestDocument()
как throwing. Если printDocumentWithData(_:)
выдает ошибку, то ошибка передается вызывающей стороне функции printTestDocument()
.
6. Обход ошибок
В начале этой статьи я писал, что Swift хочет, чтобы вы приняли обработку ошибок, сделав ее простой и интуитивно понятной. Могут быть ситуации, когда вы не хотите или должны обрабатывать возникающие ошибки. Вы решаете остановить распространение ошибок. Это возможно при использовании варианта ключевого слова try!
, try!
,
В Swift восклицательный знак всегда служит предупреждением. Восклицательный знак в основном говорит разработчику, что Swift больше не несет ответственности, если что-то пойдет не так. И это то, что try!
Ключевое слово говорит вам. Если вы предшествуете вызову функции throw с помощью try!
Ключевое слово, также известное как выражение принудительной попытки, распространение ошибок отключено.
Хотя это может звучать фантастически для некоторых из вас, я должен предупредить вас, что это не то, что вы думаете. Если функция throwing выдает ошибку и вы отключили распространение ошибки, вы столкнетесь с ошибкой во время выполнения. Это в основном означает, что ваше приложение будет зависать. Вы были предупреждены.
7. Objective-C API
Команда Swift в Apple приложила немало усилий, чтобы сделать обработку ошибок максимально прозрачной для API Objective-C. Например, вы заметили, что первым примером Swift этого руководства является API Objective-C. Несмотря на то, что API написан на Objective-C, метод не принимает указатель NSError
качестве последнего аргумента. Для компилятора это обычный метод броска. Вот как выглядит определение метода в Objective-C.
1
|
— (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error;
|
И это то, как выглядит определение метода в Swift.
1
|
public func executeFetchRequest(request: NSFetchRequest) throws -> [AnyObject]
|
Ошибки, которые executeFetchRequest(request: NSFetchRequest)
являются экземплярами NSError
. Это возможно только потому, что NSError
соответствует протоколу ErrorType
, как мы обсуждали ранее. Посмотрите на колонку « Соответствует» ниже.
Узнайте больше на нашем курсе программирования Swift 2
Swift 2 имеет много новых функций и возможностей. Пройдите наш курс по разработке Swift 2, чтобы научиться быстро. Обработка ошибок — лишь малая часть возможностей Swift 2.
Вывод
Вывод этой статьи — ошибка обработки ошибок в Swift. Если вы обратили внимание, то вы также поняли, что вам нужно будет принять обработку ошибок, если вы решите разрабатывать в Swift. Используя try!
Ключевое слово не выведет вас из обработки ошибок. Напротив, слишком частое использование может привести к неприятностям. Попробуйте, и я уверен, что вам это понравится, если вы уделите ему немного времени.