本文为私人学习笔记,仅仅做为记录使用,详情内容请查阅 中文官方文档。
错误处理
定义和抛出异常
遵循 Error
协议的类型表示可以用于错误处理。
enum VendingMachineError: Error {
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}
不符合条件时,用关键字 trow
来抛出异常。
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
四种处理
- 传递给调用者
-
do-catch
捕获处理 -
try?
将错误作为可选类型 -
try!
断言此错误不会发生
传递给调用者
可以在函数声明的参数之后返回值之前加上关键字 throws
,该函数也被称为 throwing
函数。
func cannotThrowErrors() -> String
func canThrowErrors() throws // 无返回值
func canThrowErrors() throws -> String // 有返回值
一个 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)
]
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
print("Dispensing \(name)")
}
}
throwing
抛出错误:
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
do-catch
捕获处理
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
catch
子句不必将 do
子句中的代码所抛出的每一个可能的错误都做处理,即所有错误不必都捕获处理。如果列出的所有 catch
子句都没有处理错误,那么这个错误就被传递到周围的作用域,并该错误依旧需要周围作用域处理。
在普通的非 throwing
函数中,必须用 do-catch
语句处理错误。而 throwing
函数中,还可以将错误继续传递抛出,让调用者来处理错误,如果错误没有传递到最顶层作用域却依然没有被处理,那么会得到一个运行时错误。
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError { // 只处理一种类型的错误,如果是其他错误类型就会继续抛出
print("Invalid selection, out of stock, or not enough money.")
}
}
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
// 打印“Invalid selection, out of stock, or not enough money.”
try?
将错误作为可选类型
try?
可以将错误转换为可选类型,如果没有发生错误,那么你会得到一个可选类型的返回值,如果发生错误,那么你可以不做 do-catch
捕获处理,而只得到一个 nil
返回值。
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
x
的结果将是 Int?
可选类型或者是 nil
值。上述 x
语句等同于下面的语句:
let x: Int?
do {
x = try someThrowingFunction()
} catch {
x = nil
}
try!
断言此错误不会发生
当你明确知道 throwing
函数运行结果是一个确定值时,可以使用 try!
来禁用错误传递。但是如果抛出了错误,则会得到一个运行时错误,其作用是将 try?
的可选结果上进行了强解操作。
延迟操作
有时我们需要在工作完成结束之后完成一些善后工作,例如在打开文件进行读写之后,我们需要关闭文件。defer
关键字让语句在即将离开当前代码块时执行一系列的善后清理工作,不管是以何种方式离开当前代码块的,如抛出错误离开、return
、break
等语句,defer
都能将代理的执行延迟到当前作用域退出之前。延迟执行的语句不能包含任何控制转移语句,例如 break
、return
语句,或是抛出一个错误。
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
}
有了 defer
代码块之后,我们就可以将文件操作最后的关闭操作放在其中。defer
的好处就是能将善后工作统一,有利于后期改造时遗忘而导致错误的发生。
即使在普通的函数代码中,你也可以使用
defer
语句。
扩展
使用 extension
关键字声明扩展:
extension SomeType {
// 在这里给 SomeType 添加新的功能
}
Swift 中扩展的功能很多,具体可以:
- 添加计算型属性
- 添加方法
- 提供新的构造器(便利构造器)
- 定义下标
- 定义和使用新的嵌套类型
- 遵循协议
注意
扩展可以给一个类型添加新的功能,但是不能重写已经存在的功能。
扩展可以添加新的计算属性,但是不能添加存储属性,或者向现有的属性添加属性观察者。
扩展可以给现有的类型添加新的构造器。它使你可以把自定义的类型提供给其他类型的构造器使用,或者只作为额外构造选项。
扩展可以给一个类添加新的便利构造器,但是不能给类添加新的指定构造器或者析构器。指定构造器或者析构器只能由原始提供。
如果你通过扩展提供一个新的构造器,那么你由责任确保每个通过该构造器创建的实例都是初始化完整的。
协议
类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。
对属性的要求
协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型,此外,协议还可以指定属性是可读还是可读可写的。
协议总是用 var 关键字来声明变量属性。
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
对方法的要求
协议不支持方法提供默认参数。类方法的要求依然适用。
对异变方法的要求
实现协议中的 mutating
方法时,若是类,则不用写 mutating
关键字,而对于结构体和枚举,则必须写 mutating
关键字。
协议作为类型
尽管协议本身并未实现任何功能,但是协议可以被当做一个功能完备的类型来使用。协议可以像其他普通类型一样使用,使用场景如下:
- 作为函数、方法或构造器中的参数、返回值
- 作为常量、变量或者属性
- 作为数组、字典或其他容器的元素
协议和委托
委托是一种常见的设计模式,它允许类或者结构体将一些需求功能委托给其他类型的实例。
委托模式实现是这样的:定义协议来封装那些需要被委托的功能,确保遵循该协议的类型(被委托者)能够提供这些功能。
委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的提供者。
通过扩展让类型遵循协议
上一节中提到了扩展可以另已有的类型遵循并符合协议。
通过扩展是的已有类型遵循协议时,该类型的所有实例(包括之前创建使用的实例)都随之获得协议中定义的各项功能。
当一个类型已经遵循了某个类型中的所有要求,但是没有声明遵循该协议时,可以通过扩展显示的遵循该协议。
protocol TextRepresentable {
var textualDescription: String { get }
}
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
从现在起,Hamster
的实例可以作为 TextRepresentable
类型使用。
协议合成
协议组合使用 SomeProtocol
& AnotherProtocol
的形式。你可以列举任意数量的协议,用 &
符号分开。
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
检查协议一致性
is
和 as
操作符来检查协议的一致性,转换到指定的协议类型。
可选协议
optional
关键字作为前缀来定义可选项。可选项用在你需要和 OC 打交道的代码中时,协议和可选项都必须带上 @objc
属性。标记 @objc
特性的协议只能被继承自 OC 的类或者 @objc
类遵循,其他类以及结构体或者枚举均不能遵循这种协议。
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
为协议提供默认实现
Swift 可以通过扩展来为协议中的方法、计算属性提供默认的实现。如果遵循协议的类型也为这些方法、计算属性提供了自己的实现,那么这些实现将会替代扩展中的默认实现。
和协议中的可选项不同,提供默认实现的可以直接调用,而不需要使用可选链式调用。
// 协议的默认实现
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,可能获得协议扩展提供的默认实现。限制条件写在协议名之后,使用 where
子句来描述。
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
上述例子扩展 Collection
协议,适用于集合中的元素遵循了 Equatable
协议的情况。