錯誤的類型
- 開發(fā)過程中常見的錯誤:
語法錯誤(編譯報錯)
邏輯錯誤(一般跟業(yè)務(wù)相關(guān))
運行時錯誤(可能會導(dǎo)致閃退,一般也叫異常)
......
自定義錯誤
- Swift中可以通過
Error協(xié)議自定義運行時的錯誤信息
enum SomeError: Error {
case illegalArg(String)
case outOfBounds(Int, Int)
case outOfMemory
}
- 函數(shù)內(nèi)部通過
throw拋出自定義Error,可能會拋出Error的函數(shù)必須加上throws聲明
func divide(_ num1: Int, _ num2: Int) throws -> Int {
if num2 == 0 {
throw SomeError.illegalArg("0不能作為除數(shù)")
}
return num1 / num2
}
- 需要使用
try調(diào)用可能會拋出Error的函數(shù)
var result = try divide(20, 0)
處理Error
- 處理
Error的兩種方式
① 通過do-catch捕捉Error
② 不捕捉Error,在當(dāng)前函數(shù)增加throws聲明,Error將自動拋給上層函數(shù);如果最頂層函數(shù)(main函數(shù))依然沒有捕捉Error,那么程序?qū)⒔K止
方法①:do-catch
- 可以使用
do-catch捕捉Error
func test() {
print("1")
do {
print("2")
_ = try divide(20, 0)
print("3")
} catch let SomeError.illegalArg(msg) {
print("參數(shù)異常", msg)
} catch let SomeError.outOfBounds(size, index) {
print("下標(biāo)越界:", "size=\(size)", "index=\(index)")
} catch SomeError.outOfMemory {
print("內(nèi)存溢出")
} catch {
print("其他錯誤")
}
print("4")
}
test()
/*輸出結(jié)果*/
1
2
參數(shù)異常 0不能作為除數(shù)
4
- 注意:拋出
Error后,try下一句直到作用域結(jié)束的代碼都將停止運行
當(dāng)然我們上面的do-catch還可以這樣寫:
do {
_ = try divide(20, 0)
} catch let error {
switch error {
case let SomeError.illegalArg(msg):
print("參數(shù)異常", msg)
default:
print("其他異常")
}
}
- 這里錯誤判斷,我們也可以先判斷大類,再細(xì)分具體的錯誤
do {
_ = try divide(20, 0)
} catch let err where err is SomeError { ///先區(qū)分error類型,再區(qū)分具體的錯誤內(nèi)容
print(err)
}
- 注意:只區(qū)分錯誤類型的話,只用
is SomeError就可以,但是這種寫法,在當(dāng)前catch作用域內(nèi),就無法去細(xì)分error了:
image.png
方法②:不捕捉,將Error拋給上層函數(shù)
func test() throws {
print("1")
print(try divide(20, 0))
print("2")
}
try test()
/*輸出結(jié)果*/
1
Fatal error: Error raised at top level:......
- 語法糖:如果結(jié)合
throws,我們還可以在test函數(shù)里面這樣寫(注意:如果test沒有聲明throws這樣寫會報錯的):
func test() throws {
print("1")
do {
print("2")
_ = try divide(20, 0)
print("3")
} catch let error as SomeError {
print(error)
}
print("4")
}
try test()
/*輸出結(jié)果*/
1
2
illegalArg("0不能作為除數(shù)")
4
try?、try!
- 可以使用
try?、try!調(diào)用可能會拋出Error的函數(shù),這樣就不用去處理Error
func test() {
print("1")
var result1 = try? divide(20, 10) // Optional(2), Int?
var result2 = try? divide(20, 0) // nil, Int?
var result3 = try! divide(20, 10) // 2, Int
print("2")
}
test()
- 下面兩段代碼是等價的,利用
do-catch可以做到和try?一樣的效果:
var a = try? divide(20, 0)
var b: Int?
do {
b = try divide(20, 0)
} catch{
b = nil
}
/************ 或者這樣寫也行 ***************/
var b: Int?
do {
b = try divide(20, 0)
} catch{}
rethrows
-
rethrows表明:函數(shù)本身不會拋出錯誤,但調(diào)用閉包參數(shù)(或者傳入的函數(shù))拋出錯誤,那么它會將錯誤向上拋 - 這里
rethrows僅僅是一個聲明,換成throws也可以,只是更加精確
func exc(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
print(try fn(num1, num2))
}
// Fatal error: Error raised at top level
try exc(divide(_:_:), 20, 0)
我們也可以參考一下??(空合并運算符):

image.png
defer
-
defer語句:用來定義以任何方式(拋錯誤、return等)離開代碼前必須要執(zhí)行的代碼 -
defer語句將延遲至當(dāng)前作用域結(jié)束前執(zhí)行
我們來看下面的一段代碼:
func open(_ filename: String) -> Int {
print("openFile")
return 0
}
func close(_ file: Int) {
print("closeFile")
}
接下來我利用上面的兩個函數(shù)對文件做一些操作
func processFile(_ filename: String) throws {
let file = open(filename)
// 使用file的相關(guān)操作
// ...此處省略一千行代碼
try divide(20, 0) // 中間有一個代碼可能會拋出錯誤
close(file)
}
do {
try processFile("test.txt")
} catch {}
/*打印結(jié)果*/
openFile
可以看到這種情況下,文件不會被關(guān)閉。在真是的開發(fā)環(huán)境中,會造成內(nèi)存泄漏,等等一些問題。
這個時候,我們就可以利用defer來避免這個問題:
func processFile(_ filename: String) throws {
let file = open(filename)
defer {
close(file)
}
// 使用file的相關(guān)操作
// ...此處省略一千行代碼
try divide(20, 0) // 中間有一個代碼可能會拋出錯誤
}
do {
try processFile("test.txt")
} catch {}
/*打印結(jié)果*/
openFile
closeFile
我們在open的時候,就直接利用defer語句把close函數(shù)code上;這樣,不管后面的代碼是否拋出異常、還是正常退出,文件都會被關(guān)掉。
- 還有一點要注意:
defer語句的執(zhí)行順序和定義順序是相反的:
func fn1() {
print("fn1")
}
func fn2() {
print("fn2")
}
func test() {
defer {
fn1()
}
defer {
fn2()
}
}
test()
/*輸出結(jié)果*/
fn2
fn1
assert(斷言)
- 很多編程語言都有斷言機(jī)制:不符合指定條件就拋出運行時錯誤,長用于調(diào)試(Debug)階段的條件判斷`
- 默認(rèn)情況下,Swift的斷言只會在
Debug模式下生效,Release模式下會忽略
func divide(_ v1: Int, _ v2: Int) -> Int {
assert(v2 != 0, "除數(shù)不能為0")
return v1 / v2
}
print(divide(20, 0))
- 增加
Swift Flags修改斷言的默認(rèn)行為 -
-assert-config Release:強(qiáng)制關(guān)閉斷言 -
-assert-config Debug:強(qiáng)制開啟斷言
image.png
fatalError
- 如果遇到嚴(yán)重問題,希望結(jié)束程序運行時,可以直接使用
fatalError函數(shù)拋出錯誤(這是無法通過do-catch捕捉的錯誤) - 使用了
fatalError函數(shù),就不需要再寫return
func test(_ num: Int) -> Int {
if num >= 0 {
return 1
}
fatalError("num不能小于0")
}
- 在某些不得不實現(xiàn)、但不希望別人調(diào)用的方法,可以考慮內(nèi)部使用
fatalError函數(shù)
image.png
局部作用域
- 可以使用
do實現(xiàn)局部作用域
do {
let dog1 = Dog()
dog1.age = 10
dog1.run()
}
do {
let dog2 = Dog()
dog2.age = 10
dog2.run()
}


