Swift基础入门知识学习(21)-错误处理-讲给你懂

TED演讲的8个秘诀:学习18分钟高效表达-重点笔记

Swift基础入门知识学习(20)-可选链-讲给你懂


理解难度
★★★★☆
实用程度
★★★☆☆

代码运行中,有时会遇到错误需要处理,例如当读取一个档案,但是档案可能不存在或是没有读取权限;或是一个购物车需要进行业务逻辑上的判断,结帐前要检查是否有商品或是超过数量库存等等。对于错误的抛出、捕获、传递及处理, Swift 都提供了完整的支持。

错误的描述与抛出

首先我们必须定义一组错误描述,来让代码中遇到错误时,可以清楚知道当前是遇到了什么错误状况,以及各自匹配后续的处理。Swift 中通常是使用一个遵循Error协定(protocol)的枚举来表示一组错误描述(Error是一个空的协定,只是为了告诉 Swift 这个枚举是用来表示错误描述)。

下面的例子是一个自动贩卖机定义的一组错误描述的枚举,成员依序为:

  • 无此商品
  • 金额不足(有一个相关值为还需要补足多少钱币)
  • 商品已卖光
enum VendingMachineError: Error {

    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
    
}

当遇到一个错误的时候,这里会表示有一个错误发生,然后将这个错误抛出,后续再由自己定义处理方式或交由 Swift 自动处理。Swift 中使用关键字throw来抛出一个错误。下面例子表示抛出一个自动贩卖机还需要补足 3 个钱币的错误:

throw VendingMachineError.insufficientFunds(coinsNeeded: 3)

使用抛出函数传递错误

前面说明了如何抛出错误,接着我们必须设计一个函数,其内部会经过逻辑判断是否发生异常或错误,当发生时便会抛出错误。Swift 使用关键字throws来标记函数,称其为抛出函数(throwing function),格式如下:

func 函数名称() throws -> 返回值型别 {

    内部执行的代码
    
}

抛出函数中的抛出(throw)有点类似return,因为抛出一个错误表示一个异常或错误发生了,所以正常的执行流程会立即中止,其后的代码都不会继续执行,会直接传递至处理错误的地方继续。

只有抛出函数可以传递错误。任何在一个非抛出函数中抛出的错误都必须在该函数内部处理。

下面就定义一个自动贩卖机的类别:

// 先定义一个结构体来表示一个商品的内容 分别为商品的价钱及数量
struct Item {

    var price: Int
    var count: Int
    
}

// 定义一个自动贩卖机的类别
class VendingMachine {

    // 自动贩卖机内的商品
    var inventory = [
        "可乐": Item(price: 25, count: 4),
        "洋芋片": Item(price: 20, count: 7),
        "巧克力": Item(price: 35, count: 11)
    ]

    // 目前已投入了多少钱币 预设值为 0
    var coinsDeposited = 0

    // 所有判断错误的逻辑都通过后 确定购买商品的动作
    func dispenseSnack(snack: String) {
        print("Dispensing \(snack)")
        
    }

    // 贩售的动作 确定售出前会做些判断
    // 这是一个抛出函数 所以函数名称需要加上 throws
    func vend(itemNamed name: String) throws {
    
        // 检查是否有这个商品 没有的话会抛出错误
        guard var item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }

        // 检查这个商品是否还有剩 已卖光的话会抛出错误
        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }

        // 检查目前投入的钱币够不够 不够的话会抛出错误
        guard item.price <= coinsDeposited else {
            // 参数为还需要补足多少钱币 所以是商品价钱减掉已投入钱币
            throw VendingMachineError.insufficientFunds(
              coinsNeeded: item.price - coinsDeposited)
        }

        // 所有判断都通过后 才确定会售出
        coinsDeposited -= item.price
        item.count -= 1
        inventory[name] = item
        dispenseSnack(snack: name)
    }
}

错误的捕获及处理

前面定义的类别中有一个抛出函数,如果遇到错误时会将错误抛出并传递至错误处理的地方,目前尚未定义怎么处理错误,所以这时 Swift 会自动处理,不过这可能就是意味着代码中止,所以我们还是自行定义错误处理的方式。

Swift 使用 do-catch 语句来定义错误的捕获(catch)及处理,每一个catch表示可以捕获到一个错误抛出的处理方式,下面就是格式:

do {

    try 抛出函数
    其他执行的代码
    
} catch 错误1 {

    处理错误1
    
} catch 错误2 {

    处理错误2
    
}
  • 如果要呼叫抛出函数,必须在函数前加上 try 关键字。
  • 如果要捕获抛出的错误,必须将抛出函数(或抛出错误的代码)写在关键字do包含的大括号{ }内。
  • 使用关键字 catch 来匹配要捕获的每个错误。

下面是一个例子:

// 生成一个自动贩卖机类别的实例 并设置已投入 8 个钱币
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8

// 进行错误的抛出、捕获及处理
do {
    // 呼叫抛出函数 我要购买可乐这个商品
    try vendingMachine.vend(itemNamed: "可乐")

    // 其他可能需要执行的代码 这边先省略

// 下面就每个 catch 为各自匹配错误的处理
} catch VendingMachineError.invalidSelection {
    print("无此商品")
} catch VendingMachineError.outOfStock {
    print("商品已卖光")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("金额不足,还差 \(coinsNeeded) 个钱币")
}

上面的代码中可以看到 vendingMachine.vend(itemNamed:) 函数内,每个会抛出的错误都可以在 do-catch 中列出的 catch 语句中匹配到。

而上面的这个例子依序判断错误到最后,会因为钱币不足而抛出 VendingMachineError.insufficientFunds 这个错误,并在外面的 do-catch 中被捕获到,最后会打印出:金额不足,还差 17 个钱币。当然因为已经抛出错误,其后的代码都不会继续执行。

转换错误为可选值

前面提到可以使用一个抛出函数来抛出错误,并使用do-catch来捕获并处理错误。而如果只是要简单让错误发生时,返回为一个nil,像是如下的表示:

// 定义一个抛出函数 会返回一个 Int
func someThrowingFunction() throws -> Int {

    // 内部执行的代码 
    // 假设返回 10
    return 10
}

// 声明一个可选型别 Int? 的常量 x 
let x: Int?

do {
    // 呼叫抛出函数 会返回一个 Int
    x = try someThrowingFunction()
} catch {
    // 错误发生而被抛出 进而捕获时 将其设为 nil
    x = nil
}

如上面的代码功能,我们可以简单的将 try 改成使用 try? ,这样当错误发生要被抛出时,会简单的返回一个nil。如下:


let y = try? someThrowingFunction()

不论原本抛出函数返回的是不是可选值,使用 try? 呼叫的抛出函数,都会返回可选值。

禁用错误传递

当你知道一个抛出函数确定不会在执行时抛出错误,这时可以使用 try! 来呼叫抛出函数,这样会将错误传递禁用,但当错误真的被抛出时,会发生代码运行时错误。

也就是说,使用 try! 呼叫抛出函数来告诉 Swift 确定这个呼叫不会发生异常或错误。还有一点,使用 try! 呼叫抛出函数,可以不用放在 do 的大括号{ }内。


let z = try! someThrowingFunction()

必定执行的代码区块

我们可以使用 defer 定义一个代码区块,当在无论是抛出(throw)错误,或是使用 return、break 结束这个函数后,都必定会执行这个代码区块。

当在需要做清理工作或是释放记忆体之类的代码时很好用,像是一个开启档案的函数。

func someMethod() throws {

    // 打开一个资源 像是开启一个档案

    defer {
        // 释放资源记忆体或清理工作
        // 像是关闭一个开启的档案
    }

    // 错误处理 像是档案不存在或没有读取权限

    // 及其他要执行的代码
}

按照上面的代码,这样不论在正常执行代码到最后或是因为发生错误抛出而中止,最后都会执行 defer 内的代码,保证清理工作一定会执行。

如果定义多个 defer ,会先执行最后一个定义的 defer ,再依序往前执行到第一个。

defer 不是一定要与错误处理一起使用,普通的函数内也可以使用。

Swift基础入门知识学习(22)-类型转换(型别转换)-讲给你懂

高效阅读-事半功倍读书法-重点笔记-不长,都是干货

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MillVA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值