Swift-错误处理
关键字:
throws
、throw
、try
、try?
、try!
、do-catch
、defer
错误处理 是指对代码中的异常情况, 作出响应的过程. swift
在运行时对错误的抛出、捕获、传递、操作提供了一级支持
开发过程中, 有些操作往往不能保证一定成功, 在失败时, 我们需要知道失败的原因, 因此, 便需要错误处理
以便做出相应的响应.
例如: 从硬盘上读取文件时, 有多种原因会导致读取操作失败: 文件不存在、没有读取权限、文件格式不能正确编码等, 用不同的错误来区分这些状态, 可以让你的程序正确处理, 并能告诉用户失败的原因
对于错误
表示, OC
中用NSError
, 而在swift
中, 用Error
表示错误
在
swift
中, 使用遵守Error
协议的类型来表示错误.
/// A type representing an error value that can be thrown.
public protocol Error {
}
Error 协议实际上是空的, 只是用来表示遵守该协议的某类型可以用于错误处理
注意: 抛出不遵守Error
协议的类型时, 编译器会报错
枚举
特别适用于封装错误, 可以并利用关联值特性, 来关联相关的错误信息, 如
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
抛出错误
使用关键字
throw
来执行抛出错误的操作,throw
后边的类型必须遵守Error
协议, 否则报编译错误
抛出一个还需要5枚硬币的错误, 就可以这样:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
处理错误
要表明一个函数、方法 或 构造器 可能会抛出错误, 可以使用关键字
throws
声明在函数的形参之后, 则该函数即为throwing
函数.
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
对于throwing
函数, 调用时必须要处理错误, 否则报编译错误: Errors thrown from here are not handled
处理错误的方式有4种:
- 传递错误
- 捕获错误
- 转换错误为可选类型
- 断言错误
当函数抛出错误时, 会改变程序的当前执行流程, 因此在代码中确认抛出错误的位置显得尤为重要, 要确认抛出异常的位置,就需要使用关键字try
, 或者try?
, try!
, 声明在函数调用前面.
注意:
swift
中的错误处理和其它语言的try catch throw
类似, 但是与OC
的异常处理不同,swift
不会展开调用栈, 展开调用栈是一个很耗费性能的进程, 因此,throw
语句的性能与return
语句差不多
传递错误:
throwing
函数 会将在其内部抛出的错误传递到调用的代码块中, 如:
enum MyError: Error {
case error1, error2, error3
}
func throw1() throws {
throw MyError.error3
}
func throw2() throws {
do {
try throw1()
} catch MyError.error3 {
print("error3")
}
}
func excute() {
do {
try throw2()
} catch {
print(error)
}
}
excute()
throw1
抛出的错误, 会传递到它的调用者throw2
中, 如果throw2
不能对该错误进行处理,则错误继续传递到throw2
的调用者excute
中, 在excute
中进行处理
捕获错误
使用
do-catch
语句来捕获抛出的错误
如传递错误示例代码:
func excute() {
do {
try throw2()
doSomething()
} catch {
print(error)
}
}
do
语句块中, 执行throwing
函数throw2
的调用
- 如果有错误抛出, 程序就会由
do
语句块转移到catch
语句,try throw2()
之后的doSomething
就不会执行了 - 如果没有错误, 则顺序执行
do
语句块之后的doSomething
注意: catch
语句如果没有指定匹配的错误值, 默认会有一个本地的error
变量
转换错误为可选类型
使用关键字
try?
来讲一个错误转换为可选类型
当有错误抛出时, 整个try? 表达式
的值为nil
, 如下, x
和 y
的值相同
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
注意: 使用try?
处理错误, 如果有返回值, 则不管返回值是什么类型, 都会加一层可选类型封装, 即Int
变成Int?
, Int?
变成Int??
断言错误
使用关键字
try!
, 可以禁用错误传递, 因为系统会封装一层没有错误抛出
的运行时断言, 如果有错误抛出, 则断言失败, 中断程序,报运行时错误
注意: 该方法适用于确定可以成功操作的情况, 使用时务必小心, 因为一旦判断不严谨, 会造成app
崩溃
与Cocoa的桥接
Swift
会将Cocoa
中带error
参数的方法, 转换为throwing
函数
在Cocoa
中, 表示错误, 通常使用NSError
指针作为方法的最后一个参数, Swift
会检查OC
方法声明, 翻译为Swift
的throwing
函数, 函数名字甚至可以更短
例如, 移除文件的方法, 在OC
中, 方法声明如下:
- (BOOL)removeItemAtURL:(NSURL *)URL
error:(NSError **)error;
而在swift
中, 声明如下:
func removeItem(at: URL) throws
可以看到, swift
中的函数是没有返回值的, 也没有error
参数, 还多了throws
声明.
具体转换规则如下:
- 如果
OC
方法的最后一个非block
类型的参数, 是NSError**
,swift
就能将其转换为throwing
函数 - 如果
OC
的error
参数是它的第一个参数,swift
会尝试移除WithError
或AndReturnError
后缀 来简化方法名字 - 如果
OC
方法返回一个BOOL
值来表明成功或失败,swift
会改变返回值类型为Void
- 如果
OC
方法返回nil
来表示方法调用失败,swift
会改变返回值为nonoptional
类型 - 如果转换方式, 推断不出来, 则默认保留方法名的左边部分
注意: 可以对OC
方法添加宏NS_SWIFT_NOTHROW
, 来表示阻止Swift
转换为throwing
函数
只能在OC中处理异常
在OC
中, 异常与错误是明显不同的
- 异常:
OC
的异常处理使用@try
、@catch
、@throw
语法来表明不可恢复的程序错误 - 错误:
OC
是用NSError
来表示一个可以恢复的错误
在Swift
中, 错误可以进行处理来恢复程序运行, 而对OC
的异常, 则没有安全的方法来恢复, 所要处理OC
中的异常, 只能用OC
来处理后, 再在用Swift
调用
延迟执行
使用关键字
defer
可以将一系列语句, 延迟到当前代码块结束时执行, 而不用关心具体在代码块中的位置, 一般用来做一些必要的清除操作.
func defer1() {
var c = 0
print(c)
c += 1
print(c)
defer {
c += 2
print("defer", c)
}
c += 1
print(c)
}
输出结果:
0
1
2
defer 4
可以看出: defer的执行时机,是在当前代码块的}
之前, 且defer
在执行前并不会捕获代码块中的变量
如果有多个defer
, defer
的执行会按照添加的反序执行, 即先添加的后执行, 如:
func deferN() {
var c = 0
print(c)
c += 1
print(c)
defer {
c += 2
print("defer1", c)
}
c += 1
print(c)
defer {
c += 3
print("defer2", c)
}
defer {
c += 5
print("defer3", c)
}
}
输出结果:
0
1
2
defer3 7
defer2 10
defer1 12
函数类型 | defer执行时机 |
---|---|
无返回值 | 在} 之前执行 |
有返回值 | 在return 之后, } 之前执行 |
throwing |
在throw 之后, } 之前执行 |
总结
-
表示错误: 使用遵守
Error
协议的类型, 通常多用枚举 -
传递错误: 使用
throws
关键字, 声明函数为throwing
函数 -
抛出错误: 使用
throw
关键字, 来执行抛出错误的操作 -
转换错误: 使用
try?
关键字, 有错误,表达式则为nil,并对返回值进行可选类型封装 -
断言错误: 使用
try!
关键字, 有错误,则中断程序, 有危险, 慎用! -
捕获错误: 使用
try
+do-catch
关键字, 有错误, 程序跳转到catch
; 无错误, 则继续执行do
语句块 -
异常处理: 对于
OC
中的异常, 只能用OC
语言来处理 -
延迟执行: 使用
defer
关键字, 延迟执行
难点: try?
、try!
、try, do-catch
的区别
* try?
: 尝试性的去做: 不管成功与否,都会正常执行下去, 不会打断程序执行流程, 有错误, 表达式就返回nil
而已
* try!
: 确定性的去做: 确定可以成功, 如果不成功, 则说明有问题, 报运行错误,程序终止
* try
+ do-catch
: 负责任的去做:成功,则正常顺序执行;不成功, 对错误进行处理
* 注意: do-catch
中也可以使用try?
和try!
但这样做毫无意义, 因为try?
和try!
实际上已经对错误进行了处理, 所以catch
分支压根都不会被调用,也就相当于do{}
, 只是加了一个内部作用域