Error
. Этот пустой протокол определяет тип, который может быть использован для обработки ошибок.
О протоколах мы расскажем в дальнейшем.
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
throw
для выбрасывания ошибки. Например, следующий код выбрасывает ошибки для указания того, что нужны 5 дополнительных монет для торгового автомата:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
try
- или его вариации try?
и try!
- перед той частью кода, который вызывает функцию, метод или инициализатор, который может бросить ошибку. Эти ключевые слова будут описаны ниже.
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:)
в примере ниже также является бросающей функцией, и любые ошибки, которые метод 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
, который может обработать ошибку.
catch
для указания на то, что ошибки могут быть обработаны этим блоком. Если блок catch
не имеет паттерна, то блок совместить и свяжет любые ошибки с локальной константой error
.
catch
не должен обрабатывать все возможные ошибки, которые может выбросить блок do
. Если ни один из блоков catch
не обработает ошибку, то она всплывёт в окружающий контекст. Однако ошибка должна быть обработка каким-угодно окружающим контекстом - или включающим блоком do-catch, который обрабатывает ошибку, или находясь внутри бросающей функции. Например, следующий код обрабатывает все три кейса перечисления VendingMachineError
, но все остальные ошибки должны быть обработаны окружающим пространством:
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} 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.")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."
В примере выше функция buyFavoriteSnack(person:vendingMachine:)
вызывается в выражении try
, так как она может выбросить ошибку. Если ошибка выброшена, то выполнение немедленно переходит в блок catch
, которые определены для всплытия. Если никакая ошибка не выброшена, то будут выполнены оставшиеся инструкции в блоке do
.
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
и инструкций, которые должны быть выполнены позднее. Отложенные инструкции не могут содержать никакого кода, который может передать управление за пределы инструкций, как например инструкции break
или return
, или выбрасывать ошибку. Отложенные действия выполняются в обратном порядке относительно их определения - что значит, что код в первой инструкции 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
, даже если в Вашем коде нет никакой обработки ошибок.