1.表示和拋出錯(cuò)誤
在Swift中速种,錯(cuò)誤由符合Error
協(xié)議的類型的值表示。這個(gè)空協(xié)議表示類型可以用于錯(cuò)誤處理低千。
Swift枚舉特別適合于對(duì)一組相關(guān)錯(cuò)誤條件進(jìn)行建模配阵,還可以給相關(guān)值添加錯(cuò)誤性質(zhì)的附加信息。
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
使用關(guān)鍵字throw
表示示血,待執(zhí)行語(yǔ)句拋出的錯(cuò)誤信息棋傍。
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
2.處理錯(cuò)誤
(1).使用拋出函數(shù)傳播錯(cuò)誤
- 使用關(guān)鍵字
throws
表示函數(shù),方法难审,或構(gòu)造器可以拋出錯(cuò)誤瘫拣; - 將
throws
添加在函數(shù)聲明的參數(shù)后面,返回箭頭->之前剔宪; - 拋出函數(shù)在執(zhí)行是拂铡,將其中拋出的錯(cuò)誤傳播到調(diào)用它的范圍壹无;
func canThrowErrors() throws -> String
注意:
只有拋出函數(shù)才能傳播錯(cuò)誤。非throwing函數(shù)拋出的任何錯(cuò)誤感帅,必須在函數(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)部并沒(méi)有處理錯(cuò)誤,而是直接將拋出的錯(cuò)誤傳播出去失球。其他調(diào)用該方法的代碼必需使用do-catch
語(yǔ)句岖是,try?
,try!
实苞,或繼續(xù)傳播錯(cuò)誤等方式處理這些錯(cuò)誤豺撑。
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:)
也是
一個(gè)拋出函數(shù),它內(nèi)部調(diào)用的vend(itemNamed:
方法會(huì)拋出錯(cuò)誤黔牵,因此在方法執(zhí)行前面添加關(guān)鍵字try
來(lái)調(diào)用聪轿。
如拋出函數(shù)一樣,拋出構(gòu)造器也可以按同樣的方式傳播錯(cuò)誤猾浦。
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
(2).使用 Do-Catch
處理錯(cuò)誤
通過(guò)運(yùn)行代碼塊陆错,可以使用do-catch語(yǔ)句來(lái)處理錯(cuò)誤。如果do閉包中的代碼拋出了錯(cuò)誤金赦,它將與catch閉包匹配音瓷,以確定哪一個(gè)可以處理錯(cuò)誤。
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
可以在catch之后編寫要處理的錯(cuò)誤模式夹抗,以指示閉包可以處理哪些錯(cuò)誤绳慎。如果catch閉包沒(méi)有相匹配的錯(cuò)誤模式,則將錯(cuò)誤交給最后的catch閉包來(lái)處理漠烧,并將錯(cuò)誤綁定到名為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
表達(dá)式中調(diào)用沽甥,因?yàn)樗麜?huì)拋出錯(cuò)誤声邦。當(dāng)有錯(cuò)誤拋出時(shí),就立刻轉(zhuǎn)到相應(yīng)的catch閉包中處理錯(cuò)誤摆舟。若沒(méi)有錯(cuò)誤亥曹,則繼續(xù)執(zhí)行do閉包中的剩下語(yǔ)句。
這些catch閉包不必處理do閉包中的代碼可能拋出的所有錯(cuò)誤恨诱。如果沒(méi)有catch閉包處理錯(cuò)誤媳瞪,則錯(cuò)誤將傳播到周圍的范圍。但是照宝,傳播的錯(cuò)誤必須由周圍的某個(gè)范圍處理蛇受。在非拋出函數(shù)中,包含do-catch的閉包必須處理錯(cuò)誤厕鹃。在拋出函數(shù)中兢仰,要么包含do-catch閉包乍丈,要么調(diào)用方必須處理錯(cuò)誤。如果錯(cuò)誤傳播到頂級(jí)范圍而不被處理把将,將出現(xiàn)運(yùn)行時(shí)錯(cuò)誤轻专。
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).將錯(cuò)誤轉(zhuǎn)換為可選值
可以使用try?
通過(guò)將錯(cuò)誤轉(zhuǎn)換為可選值來(lái)處理錯(cuò)誤。如果在執(zhí)行try?
表達(dá)式時(shí)拋出錯(cuò)誤察蹲,則表達(dá)式的值將為nil请垛。
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
當(dāng)你想用同樣的方式來(lái)處理所有的錯(cuò)誤時(shí),使用try?
可以讓你寫出簡(jiǎn)潔的錯(cuò)誤處理代碼洽议。
若下例所示宗收,如果所有方法都失敗,則返回nil:
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
(4).禁用錯(cuò)誤傳播
有時(shí)你知道拋出函數(shù)或方法不會(huì)在運(yùn)行時(shí)拋出錯(cuò)誤亚兄。在這種情況下混稽,你可以在表達(dá)式前面添加try!
來(lái)禁止錯(cuò)誤的傳播,并將調(diào)用包裝在運(yùn)行時(shí)斷言中儿捧,斷言不會(huì)拋出錯(cuò)誤荚坞。
如果實(shí)際拋出了一個(gè)錯(cuò)誤挑宠,將得到一個(gè)運(yùn)行時(shí)錯(cuò)誤菲盾。
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
3.指定清理操作
- defer語(yǔ)句所在的代碼塊,無(wú)論是以何種方式結(jié)束執(zhí)行(如拋出錯(cuò)誤各淀,break或return語(yǔ)句)懒鉴, defer中的代碼始終都會(huì)被執(zhí)行,且最后執(zhí)行碎浇;
- 當(dāng)多個(gè)defer語(yǔ)句在同一代碼塊中時(shí)临谱,它被執(zhí)行的順序與其在源代碼中寫入的順序相反(即源代碼順序中的最后一個(gè)defer語(yǔ)句首先執(zhí)行);
例如奴璃,使用defer語(yǔ)句來(lái)確保關(guān)閉了文件描述符并釋放手動(dòng)分配的內(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.斷言和先決條件
- 斷言和先決條件是在運(yùn)行時(shí)發(fā)生的檢查。
- 如果斷言或先決條件中的布爾值為true苟穆,則代碼將繼續(xù)正常執(zhí)行抄课。如果為false,則程序的當(dāng)前狀態(tài)無(wú)效; 代碼執(zhí)行結(jié)束雳旅,應(yīng)用程序終止跟磨。
- 斷言可以在開(kāi)發(fā)過(guò)程中發(fā)現(xiàn)錯(cuò)誤和錯(cuò)誤的假設(shè);先決條件可以檢測(cè)生產(chǎn)中的問(wèn)題攒盈。
- 斷言和先決條件與上述錯(cuò)誤處理中討論的錯(cuò)誤條件不同抵拘,它們不用于可恢復(fù)的或預(yù)期的錯(cuò)誤。因?yàn)槭〉臄嘌曰蛳葲Q條件表示無(wú)效的程序狀態(tài)型豁,所以無(wú)法捕獲失敗的斷言僵蛛。
- 使用斷言和先決條件執(zhí)行有效的數(shù)據(jù)和狀態(tài)尚蝌,可以使應(yīng)用程序在出現(xiàn)無(wú)效狀態(tài)時(shí)更容易終止,易于調(diào)試問(wèn)題充尉。
- 斷言和先決條件之間的區(qū)別在于何時(shí)檢測(cè)它們:斷言只在調(diào)試版本中檢查驼壶,而先決條件在調(diào)試和生產(chǎn)版本中都檢查。在生產(chǎn)版本中喉酌,不進(jìn)行評(píng)估斷言中的條件热凹。這意味著可以在開(kāi)發(fā)過(guò)程中使用任意數(shù)量的斷言,而不會(huì)影響生產(chǎn)中的性能泪电。
(1).使用斷言調(diào)試
從Swift標(biāo)準(zhǔn)庫(kù)中調(diào)用assert(_:_:file:line:)
函數(shù)來(lái)編寫斷言般妙。向此函數(shù)傳遞給一個(gè)表達(dá)式,該表達(dá)式求值為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ù)來(lái)指明斷言已失敗苫拍。例如:
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)條件可能為假時(shí),但是為了能夠使代碼繼續(xù)執(zhí)行旺隙,條件必須為真绒极,則使用先決條件。
可以通過(guò)調(diào)用precondition(_:_:file:line:)
函數(shù)來(lái)編寫先決條件蔬捷。向該函數(shù)傳遞一個(gè)計(jì)算結(jié)果為真或假的表達(dá)式垄提,并在條件結(jié)果為假時(shí)顯示一條消息。例如:
// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")
還可以調(diào)用preconditionFailure(_:file:line:)
函數(shù)來(lái)指明失敗發(fā)生時(shí)——例如周拐,如果選擇了開(kāi)關(guān)的默認(rèn)情況铡俐,但是所有有效的輸入數(shù)據(jù)應(yīng)該由開(kāi)關(guān)的其中一種情況處理。
5.致命錯(cuò)誤
- 無(wú)條件輸出給定的錯(cuò)誤消息并停止執(zhí)行妥粟。
//message: 要打印的字符串
//file: 輸出“message”的文件名
//line:輸出“message”的行號(hào)
public func fatalError(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) -> Never