1.表示和拋出錯誤
在Swift中,錯誤由符合Error協(xié)議的類型的值表示。這個空協(xié)議表示類型可以用于錯誤處理。
Swift枚舉特別適合于對一組相關(guān)錯誤條件進行建模,還可以給相關(guān)值添加錯誤性質(zhì)的附加信息。
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
使用關(guān)鍵字throw表示,待執(zhí)行語句拋出的錯誤信息。
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
2.處理錯誤
(1).使用拋出函數(shù)傳播錯誤
- 使用關(guān)鍵字
throws表示函數(shù),方法,或構(gòu)造器可以拋出錯誤; - 將
throws添加在函數(shù)聲明的參數(shù)后面,返回箭頭->之前; - 拋出函數(shù)在執(zhí)行是,將其中拋出的錯誤傳播到調(diào)用它的范圍;
func canThrowErrors() throws -> String
注意:只有拋出函數(shù)才能傳播錯誤。非throwing函數(shù)拋出的任何錯誤,必須在函數(shù)內(nèi)部處理;
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:方法內(nèi)部并沒有處理錯誤,而是直接將拋出的錯誤傳播出去。其他調(diào)用該方法的代碼必需使用do-catch語句,try?,try!,或繼續(xù)傳播錯誤等方式處理這些錯誤。
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:)也是
一個拋出函數(shù),它內(nèi)部調(diào)用的vend(itemNamed:方法會拋出錯誤,因此在方法執(zhí)行前面添加關(guān)鍵字try來調(diào)用。
如拋出函數(shù)一樣,拋出構(gòu)造器也可以按同樣的方式傳播錯誤。
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
(2).使用 Do-Catch處理錯誤
通過運行代碼塊,可以使用do-catch語句來處理錯誤。如果do閉包中的代碼拋出了錯誤,它將與catch閉包匹配,以確定哪一個可以處理錯誤。
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
可以在catch之后編寫要處理的錯誤模式,以指示閉包可以處理哪些錯誤。如果catch閉包沒有相匹配的錯誤模式,則將錯誤交給最后的catch閉包來處理,并將錯誤綁定到名為error的本地常量中。
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:)函數(shù)在try表達式中調(diào)用,因為他會拋出錯誤。當(dāng)有錯誤拋出時,就立刻轉(zhuǎn)到相應(yīng)的catch閉包中處理錯誤。若沒有錯誤,則繼續(xù)執(zhí)行do閉包中的剩下語句。
這些catch閉包不必處理do閉包中的代碼可能拋出的所有錯誤。如果沒有catch閉包處理錯誤,則錯誤將傳播到周圍的范圍。但是,傳播的錯誤必須由周圍的某個范圍處理。在非拋出函數(shù)中,包含do-catch的閉包必須處理錯誤。在拋出函數(shù)中,要么包含do-catch閉包,要么調(diào)用方必須處理錯誤。如果錯誤傳播到頂級范圍而不被處理,將出現(xiàn)運行時錯誤。
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."
(3).將錯誤轉(zhuǎn)換為可選值
可以使用try?通過將錯誤轉(zhuǎn)換為可選值來處理錯誤。如果在執(zhí)行try?表達式時拋出錯誤,則表達式的值將為nil。
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
當(dāng)你想用同樣的方式來處理所有的錯誤時,使用try?可以讓你寫出簡潔的錯誤處理代碼。
若下例所示,如果所有方法都失敗,則返回nil:
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
(4).禁用錯誤傳播
有時你知道拋出函數(shù)或方法不會在運行時拋出錯誤。在這種情況下,你可以在表達式前面添加try!來禁止錯誤的傳播,并將調(diào)用包裝在運行時斷言中,斷言不會拋出錯誤。
如果實際拋出了一個錯誤,將得到一個運行時錯誤。
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
3.指定清理操作
- defer語句所在的代碼塊,無論是以何種方式結(jié)束執(zhí)行(如拋出錯誤,break或return語句), defer中的代碼始終都會被執(zhí)行,且最后執(zhí)行;
- 當(dāng)多個defer語句在同一代碼塊中時,它被執(zhí)行的順序與其在源代碼中寫入的順序相反(即源代碼順序中的最后一個defer語句首先執(zhí)行);
例如,使用defer語句來確保關(guān)閉了文件描述符并釋放手動分配的內(nèi)存。
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.
}
}
4.斷言和先決條件
- 斷言和先決條件是在運行時發(fā)生的檢查。
- 如果斷言或先決條件中的布爾值為true,則代碼將繼續(xù)正常執(zhí)行。如果為false,則程序的當(dāng)前狀態(tài)無效; 代碼執(zhí)行結(jié)束,應(yīng)用程序終止。
- 斷言可以在開發(fā)過程中發(fā)現(xiàn)錯誤和錯誤的假設(shè);先決條件可以檢測生產(chǎn)中的問題。
- 斷言和先決條件與上述錯誤處理中討論的錯誤條件不同,它們不用于可恢復(fù)的或預(yù)期的錯誤。因為失敗的斷言或先決條件表示無效的程序狀態(tài),所以無法捕獲失敗的斷言。
- 使用斷言和先決條件執(zhí)行有效的數(shù)據(jù)和狀態(tài),可以使應(yīng)用程序在出現(xiàn)無效狀態(tài)時更容易終止,易于調(diào)試問題。
- 斷言和先決條件之間的區(qū)別在于何時檢測它們:斷言只在調(diào)試版本中檢查,而先決條件在調(diào)試和生產(chǎn)版本中都檢查。在生產(chǎn)版本中,不進行評估斷言中的條件。這意味著可以在開發(fā)過程中使用任意數(shù)量的斷言,而不會影響生產(chǎn)中的性能。
(1).使用斷言調(diào)試
從Swift標(biāo)準(zhǔn)庫中調(diào)用assert(_:_:file:line:)函數(shù)來編寫斷言。向此函數(shù)傳遞給一個表達式,該表達式求值為true或false。如果條件的結(jié)果為false,則顯示一條消息。
例如:
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// This assertion fails because -3 is not >= 0.
可以省略斷言消息 - 例如:
assert(age >= 0)
若代碼已經(jīng)檢查了條件,則使用assertionFailure(_:file:line:)函數(shù)來指明斷言已失敗。例如:
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
print("You can ride the ferris wheel.")
} else {
assertionFailure("A person's age can't be less than zero.")
}
(2).執(zhí)行先決條件
當(dāng)條件可能為假時,但是為了能夠使代碼繼續(xù)執(zhí)行,條件必須為真,則使用先決條件。
可以通過調(diào)用precondition(_:_:file:line:)函數(shù)來編寫先決條件。向該函數(shù)傳遞一個計算結(jié)果為真或假的表達式,并在條件結(jié)果為假時顯示一條消息。例如:
// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")
還可以調(diào)用preconditionFailure(_:file:line:)函數(shù)來指明失敗發(fā)生時——例如,如果選擇了開關(guān)的默認(rèn)情況,但是所有有效的輸入數(shù)據(jù)應(yīng)該由開關(guān)的其中一種情況處理。
5.致命錯誤
- 無條件輸出給定的錯誤消息并停止執(zhí)行。
//message: 要打印的字符串
//file: 輸出“message”的文件名
//line:輸出“message”的行號
public func fatalError(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) -> Never