1. 错误处理
错误处理(Error handling)是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一等公民支持。
1.1 表示并抛出错误
在 Swift 中,错误用符合Error 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息
enum VendingMachineError: Error {
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}
1.2 处理错误
某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。
1.2.1 用 throwing 函数传递错误
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上throws 关键字。一个标有throws 关键字的函数被称作throwing 函数。如果这个函数指明了返回值类型, throws 关键词需要写在箭头( -> )的前面。
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
注意
只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
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("Dispensing \(snack)")
}
func vend(itemNamed name: String) throws {
guard let 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
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("Dispensing \(name)")
}
}
在vend(itemNamed:) 方法的实现中使用了guard 语句来提前退出方法,确保在购买某个物品所需的条件中,有任一条件不满足时,能提前退出方法并抛出相应的错误。由于throw 语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
1.2.2 用 Do-Catch 处理错误
可以使用一个do-catch 语句运行一段闭包代码来处理错误。如果在do 子句中的代码抛出了一个错误,这个错误会与catch 子句做匹配,从而决定哪条子句能处理它。
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
1.2.3 将错误转换成可选值
可以使用try? 通过将错误转换成一个可选值来处理错误。如果在评估try? 表达式时一个错误被抛出,那么表达式的值就是nil
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
1.2.4 禁用错误传递
有时你知道某个throwing 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写try! 来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
1.3 指定清理操作
可以使用defer 语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如return 或者break 的语句。例如,你可以用defer 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
defer 语句将代码的执行延迟到当前的作用域退出之前。该语句由defer 关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如break 或是return 语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条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) 会在这里被调用,即作用域的最后。
}
}
上面的代码使用一条defer 语句来确保open(:) 函数有一个相应的对close(:) 函数的调用。
注意
即使没有涉及到错误处理,你也可以使用defer 语句。
2. 类型转换
<1>类型转换可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
<2>类型转换在 Swift 中使用is 和as 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
<3>你也可以用它来检查一个类型是否实现了某个协议
2.1 定义一个类层次作为例子
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型
2.2 检查类型
用类型检查操作符( is )来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回true ,否则返回false 。
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 “Media library contains 2 movies and 3 songs
2.3 向下转型
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符( as? 或as! )。
for item in library {
if let movie = item as? Movie {
print("Movie: '\(movie.name)', dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: '\(song.name)', by \(song.artist)")
}
}
// Movie: 'Casablanca', dir. Michael Curtiz
注意
转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。
2.4 Any 和 AnyObject 的类型转换
Swift 为不确定类型提供了两种特殊的类型别名:
• Any 可以表示任何类型,包括函数类型。
• AnyObject 可以表示任何类类型的实例。
注意
Any 类型可以表示所有类型的值,包括可选类型。Swift 会在你用Any 类型来表示一个可选值的时候,给你一个警告。如果你确实想使用Any 类型来承载可选值,你可以使用as 操作符显示转换为Any ,如下所示:
let optionalNumber: Int? = 3
things.append(optionalNumber) // 警告
things.append(optionalNumber as Any) // 没有警告
3. 嵌套类型
枚举常被用于为特定类或结构体实现某些功能。类似的,枚举可以方便的定义工具类或结构体,从而为某个复杂的类型所使用。为了实现这种功能,Swift 允许你定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体。
3.1 嵌套类型实践
要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的{} 内,而且可以根据需要定义多级嵌套。
3.2 引用嵌套类型
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
// 红心符号为 “?”