错误的类型
- 开发过程中常见的错误:
语法错误(编译报错)
逻辑错误(一般跟业务相关)
运行时错误(可能会导致闪退,一般也叫异常)
......
自定义错误
- Swift中可以通过
Error
协议自定义运行时的错误信息
enum SomeError: Error {
case illegalArg(String)
case outOfBounds(Int, Int)
case outOfMemory
}
- 函数内部通过
throw
抛出自定义Error
,可能会抛出Error
的函数必须加上throws
声明
func divide(_ num1: Int, _ num2: Int) throws -> Int {
if num2 == 0 {
throw SomeError.illegalArg("0不能作为除数")
}
return num1 / num2
}
- 需要使用
try
调用可能会抛出Error
的函数
var result = try divide(20, 0)
处理Error
- 处理
Error
的两种方式
① 通过do-catch
捕捉Error
② 不捕捉Error
,在当前函数增加throws
声明,Error
将自动抛给上层函数;如果最顶层函数(main
函数)依然没有捕捉Error
,那么程序将终止
方法①:do-catch
- 可以使用
do-catch
捕捉Error
func test() {
print("1")
do {
print("2")
_ = try divide(20, 0)
print("3")
} catch let SomeError.illegalArg(msg) {
print("参数异常", msg)
} catch let SomeError.outOfBounds(size, index) {
print("下标越界:", "size=\(size)", "index=\(index)")
} catch SomeError.outOfMemory {
print("内存溢出")
} catch {
print("其他错误")
}
print("4")
}
test()
/*输出结果*/
1
2
参数异常 0不能作为除数
4
- 注意:抛出
Error
后,try
下一句直到作用域结束的代码都将停止运行
当然我们上面的do-catch
还可以这样写:
do {
_ = try divide(20, 0)
} catch let error {
switch error {
case let SomeError.illegalArg(msg):
print("参数异常", msg)
default:
print("其他异常")
}
}
- 这里错误判断,我们也可以先判断大类,再细分具体的错误
do {
_ = try divide(20, 0)
} catch let err where err is SomeError { ///先区分error类型,再区分具体的错误内容
print(err)
}
- 注意:只区分错误类型的话,只用
is SomeError
就可以,但是这种写法,在当前catch
作用域内,就无法去细分error
了:
方法②:不捕捉,将Error抛给上层函数
func test() throws {
print("1")
print(try divide(20, 0))
print("2")
}
try test()
/*输出结果*/
1
Fatal error: Error raised at top level:......
- 语法糖:如果结合
throws
,我们还可以在test
函数里面这样写(注意:如果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()
/*输出结果*/
1
2
illegalArg("0不能作为除数")
4
try?、try!
- 可以使用
try?
、try!
调用可能会抛出Error
的函数,这样就不用去处理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
表明:函数本身不会抛出错误,但调用闭包参数(或者传入的函数)抛出错误,那么它会将错误向上抛 - 这里
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)
我们也可以参考一下??(空合并运算符)
:
defer
-
defer
语句:用来定义以任何方式(抛错误、return
等)离开代码前必须要执行的代码 -
defer
语句将延迟至当前作用域结束前执行
我们来看下面的一段代码:
func open(_ filename: String) -> Int {
print("openFile")
return 0
}
func close(_ file: Int) {
print("closeFile")
}
接下来我利用上面的两个函数对文件做一些操作
func processFile(_ filename: String) throws {
let file = open(filename)
// 使用file的相关操作
// ...此处省略一千行代码
try divide(20, 0) // 中间有一个代码可能会抛出错误
close(file)
}
do {
try processFile("test.txt")
} catch {}
/*打印结果*/
openFile
可以看到这种情况下,文件不会被关闭。在真是的开发环境中,会造成内存泄漏,等等一些问题。
这个时候,我们就可以利用defer
来避免这个问题:
func processFile(_ filename: String) throws {
let file = open(filename)
defer {
close(file)
}
// 使用file的相关操作
// ...此处省略一千行代码
try divide(20, 0) // 中间有一个代码可能会抛出错误
}
do {
try processFile("test.txt")
} catch {}
/*打印结果*/
openFile
closeFile
我们在open
的时候,就直接利用defer
语句把close
函数code上;这样,不管后面的代码是否抛出异常、还是正常退出,文件都会被关掉。
- 还有一点要注意:
defer
语句的执行顺序和定义顺序是相反的:
func fn1() {
print("fn1")
}
func fn2() {
print("fn2")
}
func test() {
defer {
fn1()
}
defer {
fn2()
}
}
test()
/*输出结果*/
fn2
fn1
assert(断言)
- 很多编程语言都有断言机制:不符合指定条件就抛出运行时错误,长用于调试(Debug)阶段的条件判断`
- 默认情况下,Swift的断言只会在
Debug
模式下生效,Release
模式下会忽略
func divide(_ v1: Int, _ v2: Int) -> Int {
assert(v2 != 0, "除数不能为0")
return v1 / v2
}
print(divide(20, 0))
- 增加
Swift Flags
修改断言的默认行为 -
-assert-config Release
:强制关闭断言 -
-assert-config Debug
:强制开启断言
fatalError
- 如果遇到严重问题,希望结束程序运行时,可以直接使用
fatalError
函数抛出错误(这是无法通过do-catch
捕捉的错误) - 使用了
fatalError
函数,就不需要再写return
func test(_ num: Int) -> Int {
if num >= 0 {
return 1
}
fatalError("num不能小于0")
}
- 在某些不得不实现、但不希望别人调用的方法,可以考虑内部使用
fatalError
函数
局部作用域
- 可以使用
do
实现局部作用域
do {
let dog1 = Dog()
dog1.age = 10
dog1.run()
}
do {
let dog2 = Dog()
dog2.age = 10
dog2.run()
}