Swift 错误处理
开发过程中的常见错误
// 1. 语法错误(编译时报错)
// let x = 10 + // 语法错误,会在编译时发现
// 2. 逻辑错误(程序运行但结果不正确)
func add(a: Int, b: Int) -> Int {
return a * b // 逻辑错误:应该是加法但写成了乘法
}
// 3. 运行时错误(可能导致程序崩溃)
// let array = [1, 2, 3]
// let item = array[10] // 运行时错误:数组越界
自定义错误
在swift中可以通过Error协议自定义运行时的错误信息
enum SomeError: Error {
case illegalArg(String)
case outOfBounds(Int, Int)
case outOfMemory
}
函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
func divide(_ a: Int, _ b: Int) throws -> Int {
guard b != 0 else { throw SomeError.illegalArg("0 不能做除数") }
return a / b
}
需要使用try调用可能会抛出Error的函数
var result = try divide(20, 10)
do catch
先看下面的例子
func test() {
print("1")
do {
print("2")
print(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")
}
这里的代码需要注意点是 catch let SomeError.illegalArg(msg)这些语句,其实这里后面几个catch,这里类似于swift中case的情况,当前面的error和当前错误类型匹配的时候,那么就会产生illegalArg这样一个枚举类型,当然了要产生这样一个枚举类型,必须要把msg传进来。还有我们可以看到case 后面的let,代表的是msg是let类型。而let SomeError.outOfBounds(size, index)代表的是size,index都是let类型。当然如果你不想写那么多catch,可以使用下面的写法,下面的写法本质其实是用switch 代替了一部分catch。
do {
try divide(20, 0)
} catch let error {
switch error {
case let SomeError.illegalArg(msg):
print("参数错误: ", msg)
default:
print("其他错误")
}
}
抛出Error后,try下一句直到作用域结束的代码都将停止运行,这个特性其实和别语言的try,catch一样的
处理Error
处理 Error 的2种方式
- 通过 do-catch 捕捉 Error2) 不捕捉 Error,在当前函数增加 throws 声明,Error 将自动抛给上层函数
- 如果最顶层函数(main 函数)依然没有捕捉 Error,那么程序将终止.
- 其实也可以使用可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error
其实对于错误我们除了在当前调用的地方进行处理,也有可能我们当前调用的地方不想处理,那么我会继续往上抛。
func test() throws {
print("1")
print(try divide(20, 0))
print("2")
}
try test()
// 1
// Fatal error: Error raised at top level
do {
print(try divide(20, 0))
} catch is SomeError {
print("SomeError")
}
这里其实很好理解,主要有一个地方,is这里是怎么理解的?,看下面的swift中as.
func test() throws {
print("1")
do {
print("2")
print(try divide(20, 0))
print("3")
} catch let error as SomeError {
print(error)
}
print("4")
}
try test()
// 1
// 2
// illegalArg("0不能作为除数")
// 4
swift 中 is
is用于“类型检查”,判断一个值在运行时是否为某个类型或是否遵循某个协议,返回 Bool,其实就是判断是不是某个类型或者是不是遵守某个协议。注意is是只检查类型,如果你想转换类型那应该使用as相关的
// 1) 检查类/结构体/枚举实例类型
let v: Any = "hello"
if v is String { /* true */ }
if v is Int { /* false */ }
// 2) 检查协议一致性
protocol P {}
struct S: P {}
let s: Any = S()
if s is P { /* true */ }
// 3) 与 as?/as! 配合(先 is 再转换)
if v is String, let str = v as? String { print(str.count) }
// 4) switch 中的类型分发
switch v {
case is Int: print("int")
case is String: print("string")
default: break
}
// 5) catch 中匹配错误类型
do {
try divide(20, 0)
} catch is SomeError {
print("命中 SomeError 的任意 case")
}
// 6) 与集合一起用
let arr: [Any] = [1, "a", 2.0]
let ints = arr.compactMap { $0 as? Int } // 或 arr.filter { $0 is Int }
使用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
var result3 = try! divide(20, 10) // 2, Int
print("2")
}
test()
下面两种写法的 a和b其实是等价的
var a = try? divide(20, 0)
var b: Int?
do {
b = try divide(20, 0)
} catch {
b = nil
}
rethrows
rethrows 表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
print(try fn(num1, num2))
}
// Fatal error: Error raised at top level
try exec(divide, 20, 0)
defer
其实类似于别的语言中错误处理的finnly
- defer 语句:用来定义以任何方式(抛错误、return 等)离开代码块前必须执行的代码
- defer 语句将延迟至当前作用域结束之前执行
- defer 语句的执行顺序与定义顺序相反
func open(_ filename: String) -> Int {
print("open")
return 0
}
func close(_ file: Int) {
print("close")
}
func processFile(_ filename: String) throws {
let file = open(filename)
defer {
close(file)
}
// 使用 file
// ...
try divide(20, 0) // close 将会在这里调用(作用域结束前)
}
try processFile("test.txt")
// open
// close
// Fatal error: Error raised at top level
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))
fatalError
如果遇到严重问题,希望结束程序运行时,可以直接使用 fatalError 函数抛出错误(这是无法通过 do-catch 捕捉的错误)。简单来说我现在错误不想让你捕捉了,就是要闪退,就可以用这个。
func test(_ num: Int) -> Int {
if num >= 0 {
return 1
}
fatalError("num不能小于0")
}
在某些不得不实现、但不希望别人调用的方法,可以考虑内部使用 fatalError 函数
class Person { required init() {} }
class Student: Person {
required init() { fatalError("don't call Student.init") }
init(score: Int) {}
}
var stu1 = Student(score: 98)
var stu2 = Student()