Swift 异常处理,以及throws和rethrows的使用

为了减少程序运行中产生异常,就需要一套异常处理机制
那些常规错误,例如网络请求错误。这些错误,在编写代码的时候尽可能去避免就好。那些预料之外的错误,比如,数组越界,range边界,除数不能是0等,就要用到异常处理机制。
在swift中在发生异常的位置添加如下几种代码,可以通过下面几种方式处理错误:

assertpreconditionfatalError.

assert 只在Debug环境下生效
precondition 在Debug和生产阶段都都生效
fatalError 抛出异常,无法捕获

这几种方式发生异常都能定位到具体的行,缺点是不能再恢复执行(也就是闪退)

swift的错误处理机制

Error协议

Error协议本身没有内容,遵守这个Error协议,就可以将错误抛出和捕获,而且任何自定义的类型都可以遵守此Error协议。

看一个官方文档中的例子:

从字符串解析整数时可能发生的两种不同类型的错误:

enum IntParsingError: Error {
    case overflow // 溢出
    case invalidInput(Character) //包含无效的字符
}
// 扩展 Int类型 ,
extension Int {
    init(validating input: String) throws {
        // ...
        let c = _nextCharacter(from: input)
        if !_isValid(c) {
            throw IntParsingError.invalidInput(c)
        }
    }
}

// 解析字符串,通过 do-catch
do {
    let price = try Int(validating: "$100")
} catch IntParsingError.invalidInput(let invalid) {
    print("Invalid character: '\(invalid)'")
} catch IntParsingError.overflow {
    print("Overflow error")
} catch {
    print("Other error")
}
// Prints "Invalid character: '$'"

throws

throws用来标记一个方法,该方法会在失败时抛出异常

// 无返回值
func initWithValidating(from:String) throws
// 有返回值
func initWithValidating(from:String) throws -> Int

完整的定义如下:

// 传入一个字符串,返回该字符串的Int类型
func initWithValidating(from:String) throws -> Int {
    // ...
    let c = _nextCharacter(from)
    if !_isValid(c) {
        throw IntParsingError.invalidInput(c)
    }
    return Int(from)
}

被标记为throws的API必须通过完整的 try catch 来捕获可能的异常,否则无法编译通过:

do{
    let result = try initWithValidating(from:"$100")
    // do...
}catch{
    //  throw抛出异常会来到这里       
}

或者带有更具体的错误信息

do{
    let result = try initWithValidating(from:"$100")
    // do...
} catch IntParsingError.invalidInput(let invalid) {
    print("Invalid character: '\(invalid)'")
} catch IntParsingError.overflow {
    print("Overflow error")
} catch {
    // 虽然两种情况都考虑到了,但是编译器要求必须加上默认的catch块
    // 编译器自动生成的error变量
    print("Other \(error)")
}

rethrows

继续上面的例子,我们现在需要对返回的整数做一下格式化,输出如下效果:

输入:10000000000
输出:10,000,000,000

下面这个函数可以对转换后的数字格式化:

func formatNum(from:String,v:(String) throws -> Int) throws {
    
    do{
        let num = try v(from)
        let format = NumberFormatter()
        format.numberStyle = .decimal
        if let result = format.string(from: NSNumber(value: num)){
            print(result)
        }
    }catch{
        throw error
    }
}

现在使用这个新的方法formatNum,参数v可以传入上面的initWithValidating并且传入"10000000000"作为第一个参数,编译器会提示我们必须加上 try 来处理可能发生的异常,但是很明显这个数字不会发生异常。

typealias verify = (String) throws -> Int

var validating:verify = { from in
    for i in from where !i.isNumber{
        throw "Error:'\(i)' not a number!"
    }
    return Int(from)!
}
// 编译器会提示我们必须加上 try 来处理可能发生的异常
try formatNum(from: "10000000000", v: validating)

现在把formatNum后面的throws标记改成rethrows,并且传入一个不会抛出异常的参数v,这个时候编译器不再提示我们必须加上 try 来处理可能发生的异常,编译也正常通过

var noErrorValidating:(String)->Int = { from in
    return Int(from)!
}
formatNum(from: "10000000000", v: noErrorValidating)
// 输出:10,000,000,000

乍一看好像没什么用,只是省略了try来捕获异常。
其实,Swift标准库提供的map函数也是这么实现的,当我map一个序列,后面传入的transform不带抛出异常的时候我们不需要前面的try

@inlinable public func map<T>(_ transform: (Character) throws -> T) rethrows -> [T]

结论:

rethrows 关键字用于本身不抛出错误,而是转发包含抛出错误的函数类型参数(闭包参数)。同时提供一个便利,仅在函数参数抛出错误时需要try关键字。

使用带异常函数

do/catch

调用一个可抛出错误的函数,编译器会迫使我们决定如何处理错误。
使用 do/catch 直接处理,可能会存在多条 catch 语句,可以用模式匹配来捕获某个特定的错误类型,见上文的例子:

错误转换try try? try!
try用于 throws 和 Optionals 之间转换

try 用在do/catch中,或者转移错误
try? 返回一个可选值,并且忽略错误信息,发生错误时返回 nil
try! 返回一个可选值并且强制解包,发生错误时会crash

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

推荐阅读更多精彩内容

  • swift 是一个类型严格的语言,在很多情况下各种不匹配或者强制解包出错都会导致系统崩溃。所以我们需要进行异常处理...
    三国韩信阅读 4,835评论 0 5
  • 你有没有遇到过这样的情况呢?学校大作业 5 人一组,4 人划水,直接变成个人项目;工作里安排 2 个人一起协作,同...
    iOS开发面试总结阅读 495评论 0 2
  • 跟其它语言一样,Swift的异常处理是在程序抛出异常后的处理逻辑。 Swift提供了一流的异常抛出、捕获和处理的能...
    捕梦少女的梦想阅读 4,961评论 2 8
  • 一.异常的介绍 .只要我们在编程,就一定要面对错误处理的问题. .Swift在设计的时候就尽可能让我们明确感知错误...
    Amok校长阅读 231评论 0 0
  • 说明 跟其它语言一样,Swift的异常处理是在程序抛出异常后的处理逻辑。 Swift提供了一流的异常抛出、捕获和处...
    ProfessorFan阅读 256评论 0 0