Обработка ошибок - это процесс реагирования и исправления ошибок в вашей программе. Swift обеспечивает первоклассную поддержку для генерации, перехвата, распространения и обработки исправляемых ошибок во время выполнения.
Некоторые операции не всегда гарантируют завершение выполнения или получение полезного результата. Необязательные параметры используются для представления отсутствия значения, но когда происходит сбой операции, часто полезно понять, что вызвало сбой, чтобы ваш код мог реагировать соответствующим образом.
В качестве примера рассмотрим задачу чтения и обработки данных из файла на диске. Эта задача может быть выполнена несколькими способами, включая файл, не существующий по указанному пути, файл, не имеющий разрешений на чтение, или файл, не закодированный в совместимом формате. Различение между этими различными ситуациями позволяет программе устранять некоторые ошибки и сообщать пользователю о любых ошибках, которые она не может устранить.
ЗАМЕТКА
Обработка ошибок в Swift взаимодействует с шаблонами обработки ошибок, которые используют
NSError
класс в Какао и Objective-C.
Представление и бросание ошибок
В Swift ошибки представлены значениями типов, которые соответствуют Error
протоколу. Этот пустой протокол указывает, что тип может использоваться для обработки ошибок.
Перечисления Swift особенно хорошо подходят для моделирования группы связанных состояний ошибок с соответствующими значениями, позволяющими передавать дополнительную информацию о природе ошибки. Например, вот как вы можете представить условия ошибки работы торгового автомата в игре:
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
Выдача ошибки позволяет вам указать, что произошло что-то неожиданное, и нормальный поток выполнения не может продолжаться. Вы используете throw
заявление, чтобы выбросить ошибку. Например, следующий код выдает ошибку, чтобы указать, что торговому автомату необходимо пять дополнительных монет:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
Обработка ошибок
Когда выдается ошибка, некоторый окружающий фрагмент кода должен отвечать за обработку ошибки - например, путем исправления проблемы, попытки альтернативного подхода или информирования пользователя о сбое.
Существует четыре способа обработки ошибок в Swift. Вы можете распространять ошибку из функции в код, который вызывает эту функцию, обрабатывать ошибку с помощью оператора do
- catch
, обрабатывать ошибку как необязательное значение или утверждать, что ошибка не произойдет. Каждый подход описан в разделе ниже.
Когда функция выдает ошибку, она меняет поток вашей программы, поэтому важно, чтобы вы могли быстро определить места в вашем коде, которые могут выдавать ошибки. Чтобы определить эти места в вашем коде, напишите try
ключевое слово ( try?
или try!
вариант или) перед фрагментом кода, который вызывает функцию, метод или инициализатор, который может вызвать ошибку. Эти ключевые слова описаны в разделах ниже.
ЗАМЕТКА
Обработка ошибок в Swift напоминает обработку исключений в других языках, с использованием
try
,catch
иthrow
ключевых слов. В отличие от обработки исключений во многих языках, включая Objective-C, обработка ошибок в Swift не включает в себя разматывание стека вызовов, процесс, который может быть вычислительно дорогим. Таким образом, рабочие характеристикиthrow
оператора сопоставимы с характеристикамиreturn
оператора.
Распространение ошибок с использованием бросающих функций
Чтобы указать, что функция, метод или инициализатор могут выдать ошибку, вы пишете throws
ключевое слово в объявлении функции после ее параметров. Функция с пометкой throws
называется функцией броска . Если функция указывает тип возвращаемого значения, вы пишете throws
ключевое слово перед стрелкой возврата ( ->
).
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
Функция выброса распространяет ошибки, которые выбрасываются внутри нее, в область, из которой она вызывается.
ЗАМЕТКА
Только бросающие функции могут распространять ошибки. Любые ошибки, возникающие в неработающей функции, должны обрабатываться внутри функции.
В приведенном ниже примере VendingMachine
класс имеет vend(itemNamed:)
метод, который выбрасывает соответствующее значение, VendingMachineError
если запрошенный элемент недоступен, отсутствует на складе или имеет стоимость, превышающую текущую сумму депозита:
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.outOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("Dispensing \(name)")
}
}
Реализация vend(itemNamed:)
метода использует guard
операторы для раннего выхода из метода и выдачи соответствующих ошибок, если какое-либо из требований для покупки закуски не выполнено. Поскольку throw
оператор немедленно передает управление программой, элемент будет продаваться, только если все эти требования будут выполнены.
Поскольку vend(itemNamed:)
метод распространяется любые ошибки , он бросает, любой код , который вызывает этот метод должен либо обрабатывать ошибки, используя do
- catch
заявление, try?
или try!
-или продолжают их распространять. Например, buyFavoriteSnack(person:vendingMachine:)
в приведенном ниже примере также есть функция throwing, и любые ошибки, которые vend(itemNamed:)
генерирует метод, будут распространяться вплоть до точки, где buyFavoriteSnack(person:vendingMachine:)
вызывается функция.
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
В этом примере функция ищет любимую закуску данного человека и пытается купить ее, вызвав метод. Поскольку метод может выдать ошибку, он вызывается с ключевым словом перед ним.buyFavoriteSnack(person: vendingMachine:)
vend(itemNamed:)
vend(itemNamed:)
try
Бросающие инициализаторы могут распространять ошибки так же, как и бросающие функции. Например, инициализатор для PurchasedSnack
структуры в приведенном ниже листинге вызывает функцию выброса как часть процесса инициализации и обрабатывает любые возникающие ошибки, передавая их вызывающей стороне.
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
Обработка ошибок с помощью Do-Catch
Вы используете оператор do
- catch
для обработки ошибок, выполняя блок кода. Если код в do
предложении выдает ошибку , он сопоставляется с catch
предложениями, чтобы определить, какой из них может обработать ошибку.
Вот общая форма do
- catch
заявление:
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
Вы пишете шаблон после, catch
чтобы указать, какие ошибки может обработать это предложение. Если catch
предложение не имеет шаблона, оно сопоставляется с любой ошибкой и связывает ошибку с локальной константой с именем error
.
Например, следующий код соответствует всем трем случаям VendingMachineError
перечисления.
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
print("Unexpected error: \(error).")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."
В приведенном выше примере buyFavoriteSnack(person:vendingMachine:)
функция вызывается в try
выражении, поскольку она может выдать ошибку. Если выдается ошибка, выполнение немедленно переходит к catch
предложениям, которые решают, следует ли продолжить распространение. Если ни один шаблон не сопоставлен, ошибка попадает в заключительное catch
предложение и связывается с локальной error
константой. Если ошибка не выдана, остальные операторы в do
операторе выполняются.
Предложения catch
не должны обрабатывать каждую возможную ошибку, которую do
может выдать код в предложении. Если ни одно из catch
предложений не обрабатывает ошибку, ошибка распространяется на окружающую область. Однако распространяемая ошибка должна быть обработана некоторой окружающей областью. В функции nonthrowing, объемлющий do
- catch
пункт должен обработать ошибку. В функции метательного, либо вшита do
- catch
пункт или вызывающий должен обработать ошибку. Если ошибка распространяется на область верхнего уровня без обработки, вы получите ошибку во время выполнения.
Например, приведенный выше пример может быть написан так, что любая ошибка, которая не VendingMachineError
является, вместо этого перехватывается вызывающей функцией:
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError {
print("Invalid selection, out of stock, or not enough money.")
}
}
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
// Prints "Invalid selection, out of stock, or not enough money."
В nourish(with:)
функции, если vend(itemNamed:)
выдает ошибку, которая является одним из случаев VendingMachineError
перечисления, nourish(with:)
обрабатывает ошибку, печатая сообщение. В противном случае nourish(with:)
распространяет ошибку на свой сайт вызова. Ошибка тогда поймана общим catch
предложением.
Преобразование ошибок в необязательные значения
Вы используете try?
для обработки ошибки путем преобразования ее в необязательное значение. Если при вычислении try?
выражения выдается ошибка , значением выражения является nil
. Например, в следующем коде x
и y
имеют то же значение и поведение:
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
Если someThrowingFunction()
выдает ошибку, значение x
и y
есть nil
. В противном случае значение x
и y
является значением, которое вернула функция. Обратите внимание, что x
и y
являются необязательными для любого типа someThrowingFunction()
возврата. Здесь функция возвращает целое число, поэтому x
и y
являются необязательными целыми числами.
Использование try?
позволяет вам написать краткий код обработки ошибок, когда вы хотите обрабатывать все ошибки одинаково. Например, следующий код использует несколько подходов для извлечения данных или возвращает, nil
если все подходы дают сбой.
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
Отключение распространения ошибок
Иногда вы знаете, что бросающая функция или метод на самом деле не будут выдавать ошибку во время выполнения. В таких случаях вы можете написать try!
перед выражением, чтобы отключить распространение ошибок, и обернуть вызов в утверждение времени выполнения, что не будет выдано никакой ошибки. Если на самом деле выдается ошибка, вы получите ошибку во время выполнения.
Например, в следующем коде используется loadImage(atPath:)
функция, которая загружает ресурс изображения по заданному пути или выдает ошибку, если изображение не может быть загружено. В этом случае, поскольку образ поставляется с приложением, во время выполнения не будет выдано никаких ошибок, поэтому целесообразно отключить распространение ошибок.
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
Указание действий по очистке
Вы используете defer
оператор для выполнения набора операторов непосредственно перед тем, как выполнение кода покидает текущий блок кода. Этот оператор позволяет выполнять любую необходимую очистку, которая должна выполняться независимо от того, как выполнение покидает текущий блок кода - независимо от того, была ли вызвана ошибка или из-за оператора, такого как return
или break
. Например, вы можете использовать defer
инструкцию, чтобы гарантировать, что файловые дескрипторы закрыты, а выделенная вручную память освобождена.
defer
Заявление отсрочивает выполнение , пока текущая область не закрывается. Этот оператор состоит из defer
ключевого слова и операторов, которые будут выполнены позже. Отложенные операторы могут не содержать какой-либо код, который передавал бы управление из операторов, таких как оператор a break
или return
, или путем выдачи ошибки. Отложенные действия выполняются в порядке, обратном порядку их написания в исходном коде. То есть код в первом defer
операторе выполняется последним, код во втором defer
операторе выполняется со второго до последнего и так далее. Последний defer
оператор в порядке исходного кода выполняется первым.
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// Work with the file.
}
// close(file) is called here, at the end of the scope.
}
}
В приведенном выше примере используется defer
оператор, чтобы гарантировать, что open(_:)
функция имеет соответствующий вызов close(_:)
.
ЗАМЕТКА
Вы можете использовать
defer
оператор, даже если код обработки ошибок не используется.
0 комментариев