Swift学习之错误处理(一)

一,概述

  错误处理:指对代码中的异常情况, 作出响应的过程。 swift 在运行时对错误的抛出、捕获、传递、操作提供了一级支持。

  开发过程中, 有些操作往往不能保证一定成功, 在失败时, 我们需要知道失败的原因, 因此, 便需要错误处理以便做出相应的响应。例如: 从硬盘上读取文件时, 有多种原因会导致读取操作失败: 文件不存在、没有读取权限、文件格式不能正确编码等, 用不同的错误来区分这些状态, 可以让你的程序正确处理, 并能告诉用户失败的原因对于错误表示, OC中用NSError, 而在swift中, 用 Error

  在OC中处理异常的方法是,在方法中传入一个error的指针地址,方法执行后,如果有错误,内部就会给error赋值。OC中异常处理的效率很低,通常情况下当程序出现错误时我们才会主动的抛出异常。Swift是苹果在OC之后发布的新的语言,两种语言做的事情相同,但Swift做了很多的优化和改动,在异常处理方面,Swift提供了一套完整的异常处理。

⚠️注意:跟Java语言类似, Swift的异常并不是真正的程序崩溃, 而是程序运行的一个逻辑分支;Swift和Java捕获异常的时序也是一样的。当Swift运行时抛出异常后并没有被处理, 那么程序就会崩溃。

⚠️注意:苹果建议使用枚举作为异常类型(为什么不推荐用类或者结构体?答案是枚举数据类型本身就是分成若干种情况,很适合做逻辑分支判断条件)。

二,表示错误

在swift中, 使用遵守Error协议的类型来表示错误.

/// A type representing an error value that can be thrown.
public protocol Error {

}
Error 协议实际上是空的, 只是用来表示遵守该协议的某类型可以用于错误处理

注意: 抛出不遵守Error协议的类型时, 编译器会报错

枚举特别适用于封装错误, 可以并利用关联值特性, 来关联相关的错误信息, 如

enum TestError: Error{
    case ErrorOne (String)
    case ErrorTwo (String)
    case ErrorThree (String)
    case ErrorOther (String)
}
//这里枚举是非常适合处理Error的数据类型

三,抛出错误

抛出函数:函数声明中标记有throws关键字。 在函数声明的参数(...)后,返回箭头->前使用throws关键字,表示此函数,此时该函数即为throwing函数(抛出函数)。方法或初始化方法可以抛出错误。

类似这样:

  • 抛出函数(throwing函数)

    func canThrowErrors() throws -> String
  • 非抛出函数

    func cannotThrowErrors() -> String

使用关键字throw来执行抛出错误的操作, throw 后边的类型必须遵守Error协议, 否则报编译错误

抛出函数可以传播错误,通过抛出其中的错误到函数被调用的范围内。需要注意的是只有抛出函数可以传播错误。在非抛出函数内的抛出的错误必须在函数内处理。

类似这样:

func throwErrorFun(number: Int) throws {
    if number == 0 {
        print("OK")
    } else if number == 1 {
        throw TestError.ErrorOne("错误1")
    } else if number == 2 {
        throw TestError.ErrorTwo("错误2")
    } else if number == 3 {
        throw TestError.ErrorThree("错误3")
    } else {
        throw TestError.ErrorOther("其他")
    }
}

Swift中的错误处理类似于其他语言中的异常处理,使用了trycatchthrow关键字。但是与许多其他语言(包括Objective-C)不一样的是,Swift中的错误处理不涉及展示函数调用的堆栈信息。因此throwreturn语句的性能相当。

使用try关键字调用抛出函数,以传播错误。

类似这样

  • 使用关键字throw来执行抛出错误

    enum VendingMachineError : Error {
        case InvalidGoods      //!< 商品无效
        case StockInsufficient //!< 库存不足
        case CoinInsufficient(coinNeeded:Int)
    }
    
    struct Item {
        var price : Int
        var count : Int
    }
    
    class VendingMachine {
        var coins : Int = 0
        var goods : [String : Item] = [
            "果粒橙": Item.init(price: 3, count: 2),
            "红牛": Item.init(price: 5, count: 4),
            "雪碧": Item.init(price: 4, count: 6)]
        
        func sell(itemName name : String , itemCount count : Int) throws -> Void {
            guard let item = goods[name] else {
                throw VendingMachineError.InvalidGoods
            }
            guard item.count > 0 && item.count > count else {
                throw VendingMachineError.StockInsufficient
            }
            guard item.price * count <= coins else {
                throw VendingMachineError.CoinInsufficient(coinNeeded: item.price * count - coins)
            }
            //可以成功购买
            coins -= item.price * count
            goods[name] = Item.init(price: item.price, count: item.count - count)
        }
    }
  • 使用try关键字调用抛出函数,以传播错误。(这里的try仅起到传播错误的作用,并未在init,buy相应的throwing方法内部进行do-catch处理或者其它操作的错误)

    class Customer {
        var itemName : String
        var itemCount : Int
        var vendingMachine : VendingMachine
        //使用`try`关键字调用抛出函数,以传播错误
        //可抛出的初始化方法
        init(itemName:String,itemCount:Int,vendingMachine:VendingMachine) throws {
            try vendingMachine.sell(itemName: itemName, itemCount: itemCount)
            self.itemName = itemName
            self.itemCount = itemCount
            self.vendingMachine = vendingMachine
        }
        //可抛出函数
        func buy() throws -> Void {
            try vendingMachine.sell(itemName: itemName, itemCount: itemCount)
        }
    }

四,处理错误

对于throwing函数, 调用时必须要处理错误, 否则报编译错误: Errors thrown from here are not handled
  • 使用do-catch捕获/处理错误 (try-do-catch)

    我们可以理解do-catch语句把try语句包裹起来,try后面必须接用throws 修饰的函数或方法,当这个方法发送错误并throw错误时,由catch语句来捕捉,并进行处理

    do-catch语句处理错误的形式:

    do {
        try expression  //throw error语句
        /// 没有错误发生时的后续语句
    } catch pattern 1 {
        statements
        /// 捕捉到错误,处理错误
    } catch pattern 2 where condition {
        statements
        /// 捕捉到错误,处理错误
    } catch {
        statements
        /// 捕捉到错误,处理错误
    }

    进行错误处理的操作示例

    class HandleError {
        class func test()->Void{
            let vendmachine = VendingMachine()
            vendmachine.coins = 10
            do {
                try vendmachine.sell(itemName: "红牛", itemCount: 5)
                print("购买成功")
            } catch VendingMachineError.InvalidGoods {
                print("购买失败" + "商品无效")
                
            } catch VendingMachineError.StockInsufficient {
                print("购买失败" + "库存不足")
                
            } catch VendingMachineError.CoinInsufficient(coinNeeded: let x){
                print("购买失败" + "货币不足还需" + "\(x)个硬币")
            }catch{
               print("购买失败")
            }
        }  
    }
    class HandleError {
        class func test()->Void{
            let vendmachine = VendingMachine()
            vendmachine.coins = 10
            do {
                try vendmachine.sell(itemName: "红牛", itemCount: 5)
                print("购买成功")
            } catch{
               print("购买失败")
            }
        }
    }

    判断捕获的错误类型:

    class HandleError {
        class func test()->Void{
            let vendmachine = VendingMachine()
            vendmachine.coins = 10
            do {
                try vendmachine.sell(itemName: "红牛", itemCount: 5)
                print("购买成功")
            } catch is VendingMachineError {
               print("抛出的错误是VendingMachineError")
            } catch {
              print("抛出的错误不是VendingMachineError")
            }
        }
    }

    注意catch语句如果没有指定匹配的错误值, 默认会有一个本地的error变量

  • 将错误转换为可选值(try?)

    使用try?将错误转换为可选值来处理。如果在评估try?表达式时抛出了错误,则表达式的值为nil

    func someThrowingFunction() throws -> Int {
        // ...
    }
    
    let x = try? someThrowingFunction()

    展开try?的写法:

    let y: Int?
    do {
        y = try someThrowingFunction()
    } catch {
        y = nil
    }

    注意: 使用try?处理错误, 如果有返回值, 则不管返回值是什么类型, 都会加一层可选类型封装, 即Int变成Int?Int?变成Int??

  • 断言错误(try! )

    使用关键字try!, 可以禁用错误传递, 因为系统会封装一层没有错误抛出的运行时断言, 如果有错误抛出, 则断言失败, 中断程序,报运行时错误

    let x = try! someThrowingFunction()

    注意: 该方法适用于确定可以成功操作的情况, 使用时务必小心, 因为一旦判断不严谨, 会造成app崩溃

五,总结

表示错误: 使用遵守Error协议的类型, 通常多用枚举

传递错误: 使用throws关键字, 声明函数为throwing函数

抛出错误: 使用throw关键字, 来执行抛出错误的操作

转换错误: 使用try?关键字, 有错误,表达式则为nil,并对返回值进行可选类型封装

断言错误: 使用try!关键字, 有错误,则中断程序, 有危险, 慎用!

捕获错误: 使用try + do-catch关键字, 有错误, 程序跳转到catch; 无错误, 则继续执行do语句块

  • try?try!try, do-catch的区别

try?尝试性的去做: 不管成功与否,都会正常执行下去, 不会打断程序执行流程, 有错误, 表达式就返回nil而已

try!确定性的去做: 确定可以成功, 如果不成功, 则说明有问题, 报运行错误,程序终止

try + do-catch负责任的去做:成功,则正常顺序执行;不成功, 对错误进行处理

注意: do-catch中也可以使用try?try!但这样做毫无意义, 因为try?try!实际上已经对错误进行了处理, 所以catch分支压根都不会被调用,也就相当于do{}, 只是加了一个内部作用域

posted on 2021-07-26 17:36  梁飞宇  阅读(492)  评论(0)    收藏  举报