21.Swift错误处理

/**错误处理:是响应错误以及从错误中恢复的过程。
    Swift提供了在运行时对可恢复错误的抛出、捕获、传递和操作的支持。
 */

//表示并抛出错误
//在Swift 中 错误用符合Error协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
//枚举类型非常适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息,例如:
enum VendingMachineError: Error {//表示一个游戏中操作自动贩卖机可能会出现的错误状态
    case invalidSelection //选择无效
    case insufficientFunds(coinsNeeded: Int) //金额不足
    case outOfStock //缺货
}
//抛出错误使用 throw 关键字。
//throw VendingMachineError.insufficientFunds(coinsNeeded: 5) // 抛出一个错误提示:贩卖机还需要5个硬币


//处理错误
//当错误被抛出是,附近的代码必须负责处理这个错误:纠正、尝试另一种方式、向用户报告错误等。
//为了快速识别代码中抛出错误的地方,在调用一个能抛出错误的函数、方法或构造器之前,加上try关键字,或者try?、try!这种变体。
//Swift有四种处理错误的方式:把错误传递给调用此函数的代码、用do-catch语句处理错误、将错误作为可选类型处理、断言此错误根本不会发生。

//1.用 throwing 函数传递错误
//为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 throws 关键字、如果函数指明了返回值类型,throw关键字需要写在箭头(->)的前面;一个标有 throws 关键字的函数被称为 throwing 函数
func canThrowErrors() throws -> String {
    return "可以抛出错误的函数"
}
func cannotThrowErrors() -> String {
    return "不能抛出错误的函数"
}
//注意:throwing 函数在其内部抛出错误,并将错误传递到函数被调用时的作用域;其他函数内部抛出的错误都只能在函数内部处理,不能传递出去。
struct ItemTestError {
    var price: Int
    var count: Int
}
class VendingMachine {
    var inventory = ["棒棒糖": ItemTestError(price: 1, count: 3),
                     "榨菜": ItemTestError(price: 9, count: 4),
                     "酸奶": ItemTestError(price: 13, count: 2)]
    var coinsDeposited = 0 //投入的硬币
    func dispenseSnack(snack: String) {
        print("分配:\(snack)")
    }
    func vend(itemNamed name: String) throws {
        //以下使用了三条 guard 语句来提取退出方法,确保在购买中任一条件不满足是,能提前退出方法并抛出相应的错误
        guard let item = inventory[name] else { // 如果选择的商品不在 库存中
            throw VendingMachineError.invalidSelection // 抛出错误: 选择无效
        }
        guard item.count > 0 else { // 如果选择的商品 不大于0
            throw VendingMachineError.outOfStock // 抛出错误: 缺货
        }
        guard item.price <= coinsDeposited else { // 如果投入的硬币金额 小于 商品的金额
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited) // 抛出错误: 还需要**硬币
        }
        coinsDeposited -= item.price //计算找零
        
        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem
        
        print("购买2:\(name)")
    }
}

let favoriteSnacks = ["Alice": "榨菜",
                      "Bob": "酸奶",
                      "Eve": "棒棒糖"]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {//查找默认最喜欢的零食
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName) //调用该方法,来尝试购买;因为该方法可能会抛出错误,所以调用时前面加上了try
}

//throwing构造器能像throwing函数一样传递错误。
struct PurchasedSnack {//PurchasedSnack构造器在构造过程中调用了throwing函数,并且通过传递到它的调用者来处理这些错误。
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}


//用 Do-Catch处理错误
//do中抛出错误,catch中匹配错误,匹配成功者做相应的处理;如果catch中没有指定匹配模式,那么久可以匹配任何错误,并把错误绑定到一个名字为error的局部常量。
//若do中抛出的错误,没有被catch匹配处理,那么就会由他周围的作用域处理:要么是一个外围的do-catch错误处理语句,要么是一个throwing函数的内部。
var vendingMachineDo = VendingMachine()
vendingMachineDo.coinsDeposited = 10
do { //do 中抛出错误
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachineDo)
    print("如果上一句代码抛出了错误,就会跳到下方的catch中执行,如果没有抛出错误,这条语句才会被打印")
} catch VendingMachineError.invalidSelection {//抛出错误: 选择无效
    print("选择无效")
} catch VendingMachineError.outOfStock { //抛出错误: 缺货
    print("缺货")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) { //抛出错误:投入的硬币不够
    print("投入的硬币不够尚缺少:\(coinsNeeded)枚")
}


//将错误转换成可选值
/*可以使用try?通过将错误转换成一个可选值来处理错误。如果在评估try?表达式时一个错误被抛出,那么表达式的值就是nil。
func someKeFunction() throws -> Int {//如果抛出一个错误,那么x和y的值都是nil。否则x和y的值就是该函数的返回值。
 // ...
}
let x = try? someKeFunction()
let y: Int?
do {
    y = try someKeFunction()
} catch {
    y = nil
}
 */
//如果你想对所有的错误都采用同样的方式来处理,用try?就可以让你写出简洁的错误处理代码。
func fetchData() -> Int? {//如果下面的代码来获取数据都失败了,则返回nil
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}
func fetchDataFromDisk() throws -> Int {
    return 1
}
func fetchDataFromServer() throws -> Int {
    return 2
}

//禁用错误传递
//有时你知道某个throwing函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写try!来禁用错误传递,这回把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
//let photo = try! loadImage(atPath:"./Resources/John Appleseed.jpg")
//上面这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递。



/**指定清理操作
    可以使用defer语句在即将离开当前代码块时执行一系列语句。
    无论是由于抛出错误而离开,或是由于诸如return、break的语句而离开代码块,都可以用defer语句来做以前清理工作,比如:确保文件描述符得以关闭、释放手动分配的内存等。
    defer语句由defer关键字和要被延迟到作用域最后执行的语句组成,延迟执行的语句不能包含 break语句、return语句 或 抛出一个错误。
    如果有多个defer语句,会按声明的顺序倒序执行,即先声明的最后执行,最后一个声明的第一个执行。

 func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            //处理文件
        }
        //close(file) 会在这里被调用,即作用域的最后
    }
 }
 //即使没有涉及到错误处理,也可以使用defer语句
 */


你可能感兴趣的:(21.Swift错误处理)