The Swift Programming Language学习笔记(十九)——错误处理

原创 2016年02月25日 17:23:45

错误处理

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

某些操作无法保证总是执行完所有代码或总是生成有用的结果。可选类型可用来表示值缺失,但是当某个操作失败时,最好能得知失败的原因,从而可以作出相应的应对。

举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。

注意,Swift中的错误处理涉及到错误处理模式,这会用到Cocoa和Objective-C中的NSError

表示并抛出错误

在Swift中,错误用符合ErrorType协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。

Swift的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。

抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用throws关键字。

/**
* 一个游戏中操作自动贩卖机时可能会出现的错误状态
*/
enum VendingMachineError: ErrorType {
    case InvalidSelection                       // 选择无效
    case InsufficientFunds(coinsInNeed: Int)    // 金额不足
    case OutOfStock                             // 缺货
}

处理错误

某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。

Swift中有4种处理错误的方式。

  • 把函数抛出的错误传递给调用此函数的代码
  • do-catch语句处理错误
  • 将错误作为可选类型处理
  • 或者断言此错误根本不会发生

当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上try关键字,或者try?try!这种变体。

注意,Swift中的错误处理和其他语言中用trycatchthrow进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,throw语句的性能特性是可以和return语句相媲美的

用throwing函数传递错误

为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上throws关键字。一个标有throws关键字的函数被称作throwing函数。如果这个函数指明了返回值类型,throws关键词需要写在箭头(->)的前面。

一个throwing函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。

只有throwing函数可以传递错误。任何在某个非throwing函数内部抛出的错误只能在函数内部处理

/**
* 一个游戏中操作自动贩卖机时可能会出现的错误状态
*/
enum VendingMachineError: ErrorType {
    case InvalidSelection                       // 选择无效
    case InsufficientFunds(coinsInNeed: Int)    // 金额不足
    case OutOfStock                             // 缺货
}

struct Item {
    var price: Int
    var count: Int
}

class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    func dispenseSnack(snack: String) {
        print("didpensing \(snack)")
    }

    func vend(itemName name: String) throws {
        guard var item = inventory[name] else {     // 对于guard来说,任何使用了可选绑定作为条件的一部分并被分配了值的变量或常量对于剩下的保护语句出现的代码段是可用的。
            throw VendingMachineError.InvalidSelection
        }

        guard item.count > 0 else {
            throw VendingMachineError.OutOfStock
        }

        guard item.price <= coinsDeposited else {
            throw VendingMachineError.InsufficientFunds(coinsInNeed: item.price - coinsDeposited)
        }

        coinsDeposited -= item.price
        --item.count
        inventory[name] = item
        dispenseSnack(name)
    }
}

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]

func buyFavoriteSnacks(person: String, machine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try machine.vend(itemName: snackName)   // 继续传递错误
}

用do-catch处理错误

可以使用一个do-catch语句运行一段闭包代码来处理错误。如果在do子句中的代码抛出了一个错误,这个错误会与catch子句做匹配,从而决定哪条子句能处理它。

catch后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条catch子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为error局部常量

catch子句不必将do子句中的代码所抛出的每一个可能的错误都作处理如果所有catch子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的do-catch错误处理语句,要么是一个throwing函数的内部。

如果错误被抛出,相应的执行会马上转移到catch子句中,并判断这个错误是否要被继续传递下去。如果没有错误抛出,do子句中余下的语句就会被执行。

/**
* 一个游戏中操作自动贩卖机时可能会出现的错误状态
*/
enum VendingMachineError: ErrorType {
    case InvalidSelection                       // 选择无效
    case InsufficientFunds(coinsInNeed: Int)    // 金额不足
    case OutOfStock                             // 缺货
}

struct Item {
    var price: Int
    var count: Int
}

class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    func dispenseSnack(snack: String) {
        print("didpensing \(snack)")
    }

    func vend(itemName name: String) throws {
        guard var item = inventory[name] else {     // 对于guard来说,任何使用了可选绑定作为条件的一部分并被分配了值的变量或常量对于剩下的保护语句出现的代码段是可用的。
            throw VendingMachineError.InvalidSelection
        }

        guard item.count > 0 else {
            throw VendingMachineError.OutOfStock
        }

        guard item.price <= coinsDeposited else {
            throw VendingMachineError.InsufficientFunds(coinsInNeed: item.price - coinsDeposited)
        }

        coinsDeposited -= item.price
        --item.count
        inventory[name] = item
        dispenseSnack(name)
    }
}

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]

func buyFavoriteSnacks(person: String, machine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try machine.vend(itemName: snackName)   // 继续传递错误
}

var machine = VendingMachine()
machine.coinsDeposited = 8
do {
    try buyFavoriteSnacks("Alice", machine: machine)
} catch VendingMachineError.InvalidSelection {
    print("invalid selection")
} catch VendingMachineError.OutOfStock {
    print("out of stock")
} catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
    print("insuffcient funds, please insert \(coinsNeeded) coins")  // insuffcient funds, please insert 2 coins
}

把错误转换成可选值

可以使用try?通过将错误转换成一个可选值来处理错误。如果在评估try?表达式时一个错误被抛出,那么表达式的值就是nil

enum IntError: ErrorType {
    case ZeroError
}

func fun(x: Int) throws -> Int {
    if x == 0 {
        throw IntError.ZeroError
    }
    return x
}

let x = try? fun(0)
print(x)    // nil

let y: Int?
do {
    y = try fun(0)
} catch IntError.ZeroError {
    y = nil
}
print(y)    // nil

如果你想对所有的错误都采用同样的方式来处理,用try?就可以让你写出简洁的错误处理代码。

func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}

禁用错误传递

有时你知道某个throwing函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写try!来禁用错误传递,这会把调用包装在一个断言不会有错误抛出的运行时断言中。如果实际上抛出了错误,你会得到一个运行时错误

enum IntError: ErrorType {
    case ZeroError
}

func fun(x: Int) throws -> Int {
    if x == 0 {
        throw IntError.ZeroError
    }
    return x
}

// let x = try! fun(0)     // 运行时错误:fatal error: 'try!' expression unexpectedly raised an error: IntError.ZeroError: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.0.59/src/swift/stdlib/public/core/ErrorType.swift, line 50

指定清理操作

可以使用defer语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如return或者break的语句。例如,你可以用defer语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。

defer语句将代码的执行延迟到当前的作用域退出之前。该语句由defer关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如break或是return语句,或是抛出一个错误延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条defer语句中的代码会在第二条defer语句中的代码被执行之后才执行,以此类推。

即使没有涉及到错误处理,你也可以使用defer语句。

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // 处理文件。
        }
        // close(file) 会在这里被调用,即作用域的最后。
    }
}
版权声明:本文为博主原创文章,未经博主允许不得转载。

芒果iOS开发之CocoaPods:Pods written in Swift can only be integrated as frameworks; add `use_frameworks!`

【主要内容:】 1. 错误起因 2. 错误提示 3. 解决问题办法 一、错误起因: 今天新创建了一个Xcode工程,准备安装CocoaPods,下边是我添...
  • CrazyZhang1990
  • CrazyZhang1990
  • 2016年10月09日 16:38
  • 2480

xcode 真机编译codesign failed with exit code 1错误 解决

这种错误出现目前我看到过一下几种。 1.证书的目录在登录和系统中都有而且同名,这样xcode就找不到所要的证书,解决办法删除同名。 2.证书删除错误重装证书后依然不能编译(这个...
  • menuconfig
  • menuconfig
  • 2015年10月23日 17:54
  • 4986

ios 真机测试中遇到的问题

症状 Certificate identity 'iPhone Developer: xxxx ho (2J123456HA)' appears more than once in the ke...
  • huifeidexin_1
  • huifeidexin_1
  • 2012年07月23日 16:11
  • 4216

swift 2.2 语法 (下)

前言: 1.此文中的语法会根据Swift的升级变动而更新。 2.如果需要请移步 -> swift2.2 语法(上)、swift 2.2语法(中)闭包 闭包类似于OC中的Blockswift的闭包...
  • yeshaojian
  • yeshaojian
  • 2016年05月19日 20:30
  • 713

Q:python编码

代码: # coding: utf-8 import re import urllib2 import chardet baseUrl = 'http://tieba.baidu.com/p/31...
  • P_LarT
  • P_LarT
  • 2016年08月02日 15:54
  • 1939

http://blog.csdn.net/neiloid/article/details/7037093#

1. 显示系统中全部Android平台:      android list targets  2. 显示系统中全部AVD(模拟器):      android list avd  ...
  • ge_1shunjian
  • ge_1shunjian
  • 2016年04月09日 21:28
  • 3112

The Swift Programming Language 中英文双语版

目录 欢迎使用 Swift 关于 SwiftSwift 初见 Swift 教程 基础部分基本运算符字符串和字符集合类型控制流函数闭包枚举类和结构体属性方法附属脚本继承构造过程析构过程自...
  • sunnyboy9
  • sunnyboy9
  • 2016年03月10日 23:50
  • 584

The Swift Programming Language 3.0版本的更新

Swift更新至3.0版本. 更新了函数和函数声明章节中关于函数的讨论, 新版本中所有的参数都默认获得一个标签. 指定Attribute参数时,新版本中使用”:”替代之前的”=”. 在Switch分支...
  • showgp
  • showgp
  • 2016年06月16日 10:27
  • 2084

JavaScript笔记:错误处理与调试

JavaScript笔记:错误处理与调试 1、浏览器报告的错误 2、错误处理 3、调试技术 4、常见的IE错误...
  • u014328357
  • u014328357
  • 2016年09月23日 11:16
  • 1578

Microsoft SQL Server Version List(SQL Server 版本号)

What version of SQL Server do I have?This unofficial build chart lists all of the known Service Pack...
  • DBA_Huangzj
  • DBA_Huangzj
  • 2014年07月30日 11:23
  • 20596
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:The Swift Programming Language学习笔记(十九)——错误处理
举报原因:
原因补充:

(最多只允许输入30个字)