在介绍多模式catch子句之前,我们先来复习一下平时是怎么catch的。
单模式 catch 子句
有以下函数:
enum IOError: Error {
case diskError(code:Int)
case networkError(code:Int)
}
func foo(a: Int) throws -> Int {
if a == 0 {
return 0
}
throw IOError.networkError(code: 3)
}
在使用带有 throws
的函数的时候我们必须 try
:除了 try?
用于不成功变成空,try!
用于不成功变崩溃(很少用)之外,最常用的就是try
的语句了。
一把梭catch:
do {
try print(foo(a: 1))
} catch {
print("error occurred")
}
一把梭catch,并进行变量绑定:
do {
try print(foo(a: 1))
} catch let err {
print("error occurred \(err)")
}
值得注意的是,如果当前的函数签名不带 throws
, 则 catch 必须 exhaustive 穷举。但是你可能会问下面这个一把梭 catch 真的需要吗?函数foo
不是只会抛出一种错误IOError
吗?
func myFunc() {
do {
try print(foo(a: 1))
} catch let err as IOError {
print("IO Error: \(err)")
} catch {
print("Unexpected Error")
}
}
那么细的细节编译器可不知道,不写肯定是编译不过的,因为函数签名只约定了这个函数会不会 throws,并没有约定 throws 的具体类型是什么。尽管看起来粗糙,但实际对于二进制兼容是有帮助的。例如,增加、减少或者改变一种抛出的错误不会造成源代码兼容性被破坏。也可以用 is
来匹配:
func myFunc() {
do {
try print(foo(a: 1))
} catch is IOError {
print("Caught IO Error")
} catch {
print("Unexpected Error")
}
}
我们逐渐来到“模式匹配”(pattern matching)的领域,来看这个例子:
func myFunc() {
do {
try print(foo(a: 1))
} catch IOError.diskError(let code) {
print("Caught error:\(code)")
} catch IOError.networkError(let code) {
print("Caught error:\(code)")
} catch {
print("Unexpected Error")
}
}
这个例子展示了单模式子句的局限性。尽管绑定的是同一类型的 code,但是必须写成两个catch子句,并且重复处理逻辑。在 Swift 5.3 之前,要解决这个问题还得借助 switch
,一个 case
语句可以多模式匹配,唯一的要求是绑定的值必须名字相同、类型相同、并且出现在每一个pattern中。
func myFunc() {
do {
try print(foo(a: 1))
} catch let err as IOError {
switch err {
case .diskError(let code), .networkError(let code):
print("Caught error:\(code)")
}
} catch {
print("Unexpected Error")
}
}
不用我多说,这样的问题还是丑。
多模式 catch
有了多模式匹配后,明显漂亮了很多,我们不需要再借助 switch
写出多模式匹配。
func myFunc() {
do {
try print(foo(a: 1))
} catch IOError.diskError(let code), IOError.networkError(let code){
print("Caught error:\(code)")
} catch {
print("Unexpected Error")
}
}
为了展示多模式匹配的能力,我们玩得花一点:
enum FooBarError: Error {
case fooError(first:String, second:Int)
}
func myFunc() {
do {
try print(foo(a: 1))
} catch IOError.diskError(let code) where code % 2 == 1, FooBarError.fooError(_, let code){
print("Caught error:\(code)")
} catch {
print("Unexpected Error")
}
}
这里加上了 where
子句用于筛选条件,并且同时匹配的 Error 换成了一个完全不同的类型,但是只要满足绑定的变量名字相同、类型相同、并且出现在每一个pattern中,那么就可以通过编译。
不是有绑定才是模式匹配,用 is
的也可以两句并一句了。
func myFunc() {
do {
try print(foo(a: 1))
} catch is IOError, is FooBarError{
print("Caught known error")
} catch {
print("Unexpected Error")
}
}
但是两种 Error 类型能不能并一句呢?
func myFunc() {
do {
try print(foo(a: 1))
} catch let err as IOError, let err as FooBarError{
print("Caught known error")
} catch {
print("Unexpected Error")
}
}
上面的代码不能编译通过。尽管两个 err 名字相同,也都出现在两个pattern中,但是它们的类型不相同,违反了上面提到过的原则。
小结
通过多模式 catch 子句,我们可以像 switch 语句那样对于 Error 进行多模式匹配,减少重复的处理代码。
多模式匹配时,需保证绑定的变量名字相同、类型相同并且出现在每一个模式中。
网友评论