Swift-错误处理

Swift-错误处理

关键字: throwsthrowtrytry?try!do-catchdefer

错误处理 是指对代码中的异常情况, 作出响应的过程. 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, 如下, xy的值相同

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方法声明, 翻译为Swiftthrowing函数, 函数名字甚至可以更短

例如, 移除文件的方法, 在OC中, 方法声明如下:

- (BOOL)removeItemAtURL:(NSURL *)URL
                  error:(NSError **)error;

而在swift中, 声明如下:

func removeItem(at: URL) throws

可以看到, swift中的函数是没有返回值的, 也没有error参数, 还多了throws声明.

具体转换规则如下:

  • 如果OC方法的最后一个非block类型的参数, 是NSError**, swift就能将其转换为throwing函数
  • 如果OCerror参数是它的第一个参数, swift会尝试移除WithErrorAndReturnError后缀 来简化方法名字
  • 如果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之后, }之前执行

总结

  1. 表示错误: 使用遵守Error协议的类型, 通常多用枚举
  2. 传递错误: 使用throws关键字, 声明函数为throwing函数
  3. 抛出错误: 使用throw关键字, 来执行抛出错误的操作
  4. 转换错误: 使用try?关键字, 有错误,表达式则为nil,并对返回值进行可选类型封装
  5. 断言错误: 使用try!关键字, 有错误,则中断程序, 有危险, 慎用!
  6. 捕获错误: 使用try + do-catch关键字, 有错误, 程序跳转到catch; 无错误, 则继续执行do语句块
  7. 异常处理: 对于OC中的异常, 只能用OC语言来处理
  8. 延迟执行: 使用defer关键字, 延迟执行

难点: try?try!try, do-catch的区别
* try?: 尝试性的去做: 不管成功与否,都会正常执行下去, 不会打断程序执行流程, 有错误, 表达式就返回nil而已
* try!: 确定性的去做: 确定可以成功, 如果不成功, 则说明有问题, 报运行错误,程序终止
* try + do-catch: 负责任的去做:成功,则正常顺序执行;不成功, 对错误进行处理
* 注意: do-catch中也可以使用try?try!但这样做毫无意义, 因为try?try!实际上已经对错误进行了处理, 所以catch分支压根都不会被调用,也就相当于do{}, 只是加了一个内部作用域

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容