Merge remote-tracking branch 'upstream/gh-pages' into gh-pages
This commit is contained in:
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
Swift 4.2 翻译中,请到 [Issues](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/issues) 中认领章节(把 Issue 的 Assignee 设置成你自己就表示认领,不要认领已经有人认领的章节)。
|
Swift 4.2 翻译中,请到 [Issues](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/issues) 中认领章节(把 Issue 的 Assignee 设置成你自己就表示认领,不要认领已经有人认领的章节)。
|
||||||
|
|
||||||
- 更新到 Swift 3.0。 2016-09-23
|
- 更新到 Swift 4.1,2018-04-12,感谢[@Mylittleswift](https://github.com/Mylittleswift)
|
||||||
|
- 更新到 Swift 3.0,2016-09-23
|
||||||
|
|
||||||
# 贡献力量
|
# 贡献力量
|
||||||
|
|
||||||
|
|||||||
@ -667,7 +667,7 @@ let implicitString: String = assumedString // 不需要感叹号
|
|||||||
|
|
||||||
```swift
|
```swift
|
||||||
if assumedString != nil {
|
if assumedString != nil {
|
||||||
print(assumedString)
|
print(assumedString!)
|
||||||
}
|
}
|
||||||
// 输出 "An implicitly unwrapped optional string."
|
// 输出 "An implicitly unwrapped optional string."
|
||||||
```
|
```
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。
|
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。
|
||||||
|
|
||||||
Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+`,`-`,`*`,`/`,`%` 等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](./26_Advanced_Operators.html#overflow_operators)。
|
Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(`=`)不再有返回值,这样就消除了手误将判等运算符(`==`)写成赋值符导致代码错误的缺陷。算术运算符(`+`,`-`,`*`,`/`,`%` 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](./26_Advanced_Operators.html#overflow_operators)。
|
||||||
|
|
||||||
Swift 还提供了 C 语言没有的区间运算符,例如 `a..<b` 或 `a...b`,这方便我们表达一个区间内的数值。
|
Swift 还提供了 C 语言没有的区间运算符,例如 `a..<b` 或 `a...b`,这方便我们表达一个区间内的数值。
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ let (x, y) = (1, 2)
|
|||||||
// 现在 x 等于 1,y 等于 2
|
// 现在 x 等于 1,y 等于 2
|
||||||
```
|
```
|
||||||
|
|
||||||
与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下陈述时无效的:
|
与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以下面语句是无效的:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
if x = y {
|
if x = y {
|
||||||
@ -46,7 +46,7 @@ if x = y {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这个特性使你无法把(`==`)错写成(`=`),由于 `if x = y` 是无效的,Swift 能帮你避免此类错误发生。
|
通过将 `if x = y` 标记为无效语句,Swift 能帮你避免把 (`==`)错写成(`=`)这类错误的出现。
|
||||||
|
|
||||||
<a name="arithmetic_operators"></a>
|
<a name="arithmetic_operators"></a>
|
||||||
## 算术运算符
|
## 算术运算符
|
||||||
@ -264,12 +264,12 @@ if hasHeader {
|
|||||||
|
|
||||||
第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。
|
第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。
|
||||||
|
|
||||||
三元运算提供有效率且便捷的方式来表达二选一的选择。需要注意的事,过度使用三元运算符会使简洁的代码变的难懂。我们应避免在一个组合语句中使用多个三元运算符。
|
三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。
|
||||||
|
|
||||||
<a name="nil_coalescing_operator"></a>
|
<a name="nil_coalescing_operator"></a>
|
||||||
## 空合运算符(Nil Coalescing Operator)
|
## 空合运算符(Nil Coalescing Operator)
|
||||||
|
|
||||||
*空合运算符*(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解封,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
|
*空合运算符*(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解包,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
|
||||||
|
|
||||||
空合运算符是对以下代码的简短表达方法:
|
空合运算符是对以下代码的简短表达方法:
|
||||||
|
|
||||||
|
|||||||
@ -265,9 +265,9 @@ Swift 的所有基本类型(比如 `String`,`Int`,`Double` 和 `Bool`)默认
|
|||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 Swift 标准库中的 `Hashable` 协议。符合 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。
|
> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型遵循 Swift 标准库中的 `Hashable` 协议。遵循 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。
|
||||||
>
|
>
|
||||||
> 因为 `Hashable` 协议符合 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何符合 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况:
|
> 因为 `Hashable` 协议遵循 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何遵循 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况:
|
||||||
|
|
||||||
> * `a == a`(自反性)
|
> * `a == a`(自反性)
|
||||||
> * `a == b` 意味着 `b == a`(对称性)
|
> * `a == b` 意味着 `b == a`(对称性)
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
# 闭包
|
# 闭包
|
||||||
|
|
||||||
*闭包*是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。
|
*闭包*是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。
|
||||||
|
|
||||||
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为*包裹*常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
|
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为*包裹*常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> 如果你不熟悉捕获(capturing)这个概念也不用担心,你可以在[值捕获](#capturing_values)章节对其进行详细了解。
|
> 如果你不熟悉捕获(capturing)这个概念也不用担心,在[值捕获](#capturing_values)章节有它更详细的介绍。
|
||||||
|
|
||||||
在[函数](./06_Functions.md)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
|
在[函数](./06_Functions.md)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:
|
||||||
|
|
||||||
* 全局函数是一个有名字但不会捕获任何值的闭包
|
* 全局函数是一个有名字但不会捕获任何值的闭包
|
||||||
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
|
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
|
||||||
@ -24,14 +24,14 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进
|
|||||||
<a name="closure_expressions"></a>
|
<a name="closure_expressions"></a>
|
||||||
## 闭包表达式
|
## 闭包表达式
|
||||||
|
|
||||||
[嵌套函数](./06_Functions.md#Nested_Functions)是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候编写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在你处理一些函数并需要将另外一些函数作为该函数的参数时。
|
[嵌套函数](./06_Functions.md#Nested_Functions)作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。
|
||||||
|
|
||||||
*闭包表达式*是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。下面闭包表达式的例子通过使用几次迭代展示了 `sorted(by:)` 方法定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能。
|
*闭包表达式*是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式。下面通过对 `sorted(by:)` 这一个案例的多次迭代改进来展示这个过程,每次迭代都使用了更加简明的方式描述了相同功能。。
|
||||||
|
|
||||||
<a name="the_sorted_function"></a>
|
<a name="the_sorted_function"></a>
|
||||||
### 排序方法
|
### 排序方法
|
||||||
|
|
||||||
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,`sorted(by:)` 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 `sorted(by:)` 方法修改。
|
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会基于你提供的排序闭包表达式的判断结果对数组中的值(类型确定)进行排序。一旦它完成排序过程,`sorted(by:)` 方法会返回一个与旧数组类型大小相同类型的新数组,该数组的元素有着正确的排序顺序。原数组不会被 `sorted(by:)` 方法修改。
|
||||||
|
|
||||||
下面的闭包表达式示例使用 `sorted(by:)` 方法对一个 `String` 类型的数组进行字母逆序排序。以下是初始数组:
|
下面的闭包表达式示例使用 `sorted(by:)` 方法对一个 `String` 类型的数组进行字母逆序排序。以下是初始数组:
|
||||||
|
|
||||||
@ -104,7 +104,9 @@ reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
|
|||||||
尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
|
尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
|
||||||
|
|
||||||
<a name="implicit_returns_from_single_expression_closures"></a>
|
<a name="implicit_returns_from_single_expression_closures"></a>
|
||||||
### 单表达式闭包隐式返回
|
|
||||||
|
### 单表达式闭包的隐式返回
|
||||||
|
|
||||||
单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
|
单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
@ -129,7 +131,7 @@ reversedNames = names.sorted(by: { $0 > $1 } )
|
|||||||
<a name="operator_methods"></a>
|
<a name="operator_methods"></a>
|
||||||
### 运算符方法
|
### 运算符方法
|
||||||
|
|
||||||
实际上还有一种更简短的方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断出你想使用大于号的字符串函数实现:
|
实际上还有一种更*简短的*方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
reversedNames = names.sorted(by: >)
|
reversedNames = names.sorted(by: >)
|
||||||
@ -140,7 +142,7 @@ reversedNames = names.sorted(by: >)
|
|||||||
<a name="trailing_closures"></a>
|
<a name="trailing_closures"></a>
|
||||||
## 尾随闭包
|
## 尾随闭包
|
||||||
|
|
||||||
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用*尾随闭包*来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
|
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func someFunctionThatTakesAClosure(closure: () -> Void) {
|
func someFunctionThatTakesAClosure(closure: () -> Void) {
|
||||||
@ -158,7 +160,7 @@ someFunctionThatTakesAClosure() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在[闭包表达式语法](#closure_expression_syntax)一节中作为 `sorted(by:)` 方法参数的字符串排序闭包可以改写为:
|
在[闭包表达式语法](#closure_expression_syntax)上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
reversedNames = names.sorted() { $0 > $1 }
|
reversedNames = names.sorted() { $0 > $1 }
|
||||||
|
|||||||
@ -14,7 +14,6 @@ subscript(index: Int) -> Int {
|
|||||||
get {
|
get {
|
||||||
// 返回一个适当的 Int 类型的值
|
// 返回一个适当的 Int 类型的值
|
||||||
}
|
}
|
||||||
|
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
// 执行适当的赋值操作
|
// 执行适当的赋值操作
|
||||||
}
|
}
|
||||||
@ -67,7 +66,7 @@ numberOfLegs["bird"] = 2
|
|||||||
|
|
||||||
上例定义一个名为 `numberOfLegs` 的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs` 字典的类型被推断为 `[String: Int]`。字典创建完成后,该例子通过下标将 `String` 类型的键 `bird` 和 `Int` 类型的值 `2` 添加到字典中。
|
上例定义一个名为 `numberOfLegs` 的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs` 字典的类型被推断为 `[String: Int]`。字典创建完成后,该例子通过下标将 `String` 类型的键 `bird` 和 `Int` 类型的值 `2` 添加到字典中。
|
||||||
|
|
||||||
更多关于 `Dictionary` 下标的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)
|
更多关于 `Dictionary` 下标的信息请参考 [读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
@ -107,7 +106,7 @@ struct Matrix {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Matrix` 提供了一个接受两个入参的构造方法,入参分别是 `rows` 和 `columns`,创建了一个足够容纳 `rows * columns` 个 `Double` 类型的值的数组。通过传入数组长度和初始值 `0.0` 到数组的构造器,将矩阵中每个位置的值初始化为 `0.0`。关于数组的这种构造方法请参考[创建一个带有默认值的数组](./04_Collection_Types.html#creating_an_array_with_a_default_value)。
|
`Matrix` 提供了一个接受两个入参的构造方法,入参分别是 `rows` 和 `columns`,创建了一个足够容纳 `rows * columns` 个 `Double` 类型的值的数组。通过传入数组长度和初始值 `0.0` 到数组的构造器,将矩阵中每个位置的值初始化为 `0.0`。关于数组的这种构造方法请参考 [创建一个带有默认值的数组](./04_Collection_Types.html#creating_an_array_with_a_default_value)。
|
||||||
|
|
||||||
你可以通过传入合适的 `row` 和 `column` 的数量来构造一个新的 `Matrix` 实例:
|
你可以通过传入合适的 `row` 和 `column` 的数量来构造一个新的 `Matrix` 实例:
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ var matrix = Matrix(rows: 2, columns: 2)
|
|||||||
|
|
||||||
上例中创建了一个 `Matrix` 实例来表示两行两列的矩阵。该 `Matrix` 实例的 `grid` 数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
|
上例中创建了一个 `Matrix` 实例来表示两行两列的矩阵。该 `Matrix` 实例的 `grid` 数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
将 `row` 和 `column` 的值传入下标来为矩阵设值,下标的入参使用逗号分隔:
|
将 `row` 和 `column` 的值传入下标来为矩阵设值,下标的入参使用逗号分隔:
|
||||||
|
|
||||||
@ -128,7 +127,7 @@ matrix[1, 0] = 3.2
|
|||||||
|
|
||||||
上面两条语句分别调用下标的 setter 将矩阵右上角位置(即 `row` 为 `0`、`column` 为 `1` 的位置)的值设置为 `1.5`,将矩阵左下角位置(即 `row` 为 `1`、`column` 为 `0` 的位置)的值设置为 `3.2`:
|
上面两条语句分别调用下标的 setter 将矩阵右上角位置(即 `row` 为 `0`、`column` 为 `1` 的位置)的值设置为 `1.5`,将矩阵左下角位置(即 `row` 为 `1`、`column` 为 `0` 的位置)的值设置为 `3.2`:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
`Matrix` 下标的 getter 和 setter 中都含有断言,用来检查下标入参 `row` 和 `column` 的值是否有效。为了方便进行断言,`Matrix` 包含了一个名为 `indexIsValid(row:column:)` 的便利方法,用来检查入参 `row` 和 `column` 的值是否在矩阵范围内:
|
`Matrix` 下标的 getter 和 setter 中都含有断言,用来检查下标入参 `row` 和 `column` 的值是否有效。为了方便进行断言,`Matrix` 包含了一个名为 `indexIsValid(row:column:)` 的便利方法,用来检查入参 `row` 和 `column` 的值是否在矩阵范围内:
|
||||||
|
|
||||||
|
|||||||
@ -13,11 +13,11 @@
|
|||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
|
> Swift 中的类并不是从一个通用的基类继承而来的。如果你不为自己定义的类指定一个超类的话,这个类就会自动成为基类。
|
||||||
|
|
||||||
下面的例子定义了一个叫 `Vehicle` 的基类。这个基类声明了一个名为 `currentSpeed`,默认值是 `0.0` 的存储属性(属性类型推断为 `Double`)。`currentSpeed` 属性的值被一个 `String` 类型的只读计算型属性 `description` 使用,用来创建车辆的描述。
|
下面的例子定义了一个叫 `Vehicle` 的基类。这个基类声明了一个名为 `currentSpeed`,默认值是 `0.0` 的存储型属性(属性类型推断为 `Double`)。`currentSpeed` 属性的值被一个 `String` 类型的只读计算型属性 `description` 使用,用来创建对于车辆的描述。
|
||||||
|
|
||||||
`Vehicle` 基类也定义了一个名为 `makeNoise` 的方法。这个方法实际上不为 `Vehicle` 实例做任何事,但之后将会被 `Vehicle` 的子类定制:
|
`Vehicle` 基类还定义了一个名为 `makeNoise` 的方法。这个方法实际上不为 `Vehicle` 实例做任何事,但之后将会被 `Vehicle` 的子类定制:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class Vehicle {
|
class Vehicle {
|
||||||
@ -31,7 +31,7 @@ class Vehicle {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
您可以用初始化语法创建一个 `Vehicle` 的新实例,即类名后面跟一个空括号:
|
可以用初始化语法创建一个 `Vehicle` 的新实例,即类名后面跟一个空括号:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let someVehicle = Vehicle()
|
let someVehicle = Vehicle()
|
||||||
@ -44,7 +44,7 @@ print("Vehicle: \(someVehicle.description)")
|
|||||||
// 打印 "Vehicle: traveling at 0.0 miles per hour"
|
// 打印 "Vehicle: traveling at 0.0 miles per hour"
|
||||||
```
|
```
|
||||||
|
|
||||||
`Vehicle` 类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要完善它从而能够描述一个更加具体类型的车辆。
|
`Vehicle` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。
|
||||||
|
|
||||||
<a name="subclassing"></a>
|
<a name="subclassing"></a>
|
||||||
## 子类生成
|
## 子类生成
|
||||||
@ -59,7 +59,7 @@ class SomeClass: SomeSuperclass {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
下一个例子,定义一个叫 `Bicycle` 的子类,继承成父类 `Vehicle`:
|
下一个例子,定义了一个叫 `Bicycle` 的子类,继承自父类 `Vehicle`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class Bicycle: Vehicle {
|
class Bicycle: Vehicle {
|
||||||
@ -67,11 +67,11 @@ class Bicycle: Vehicle {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
新的 `Bicycle` 类自动获得 `Vehicle` 类的所有特性,比如 `currentSpeed` 和 `description` 属性,还有它的 `makeNoise()` 方法。
|
新的 `Bicycle` 类自动继承 `Vehicle` 类的所有特性,比如 `currentSpeed` 和 `description` 属性,还有 `makeNoise()` 方法。
|
||||||
|
|
||||||
除了它所继承的特性,`Bicycle` 类还定义了一个默认值为 `false` 的存储型属性 `hasBasket`(属性推断为 `Bool`)。
|
除了所继承的特性,`Bicycle` 类还定义了一个默认值为 `false` 的存储型属性 `hasBasket`(属性推断为 `Bool`)。
|
||||||
|
|
||||||
默认情况下,你创建任何新的 `Bicycle` 实例将不会有一个篮子(即 `hasBasket` 属性默认为 `false`),创建该实例之后,你可以为特定的 `Bicycle` 实例设置 `hasBasket` 属性为 `ture`:
|
默认情况下,你创建的所有新的 `Bicycle` 实例不会有一个篮子(即 `hasBasket` 属性默认为 `false`)。创建该实例之后,你可以为 `Bicycle` 实例设置 `hasBasket` 属性为 `ture`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let bicycle = Bicycle()
|
let bicycle = Bicycle()
|
||||||
@ -110,9 +110,9 @@ print("Tandem: \(tandem.description)")
|
|||||||
<a name="overriding"></a>
|
<a name="overriding"></a>
|
||||||
## 重写
|
## 重写
|
||||||
|
|
||||||
子类可以为继承来的实例方法,类方法,实例属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。
|
子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。
|
||||||
|
|
||||||
如果要重写某个特性,你需要在重写定义的前面加上 `override` 关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少 `override` 关键字的重写都会在编译时被诊断为错误。
|
如果要重写某个特性,你需要在重写定义的前面加上 `override` 关键字。这么做,就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少 `override` 关键字的重写都会在编译时被认定为错误。
|
||||||
|
|
||||||
`override` 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
|
`override` 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
|
||||||
|
|
||||||
@ -150,11 +150,11 @@ train.makeNoise()
|
|||||||
|
|
||||||
### 重写属性
|
### 重写属性
|
||||||
|
|
||||||
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
|
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器,使重写的属性可以观察到底层的属性值什么时候发生改变。
|
||||||
|
|
||||||
#### 重写属性的 Getters 和 Setters
|
#### 重写属性的 Getters 和 Setters
|
||||||
|
|
||||||
你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
|
你可以提供定制的 getter(或 setter)来重写任何一个继承来的属性,无论这个属性是存储型还是计算型属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必须将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
|
||||||
|
|
||||||
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
|
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
|
||||||
|
|
||||||
@ -188,14 +188,14 @@ print("Car: \(car.description)")
|
|||||||
<a name="overriding_property_observers"></a>
|
<a name="overriding_property_observers"></a>
|
||||||
#### 重写属性观察器
|
#### 重写属性观察器
|
||||||
|
|
||||||
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
|
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,无论被继承属性原本是如何实现的,当其属性值发生改变时,你就会被通知到。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供 `willSet` 或 `didSet` 实现是不恰当。
|
> 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供 `willSet` 或 `didSet` 实现也是不恰当。
|
||||||
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。
|
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。
|
||||||
|
|
||||||
下面的例子定义了一个新类叫 `AutomaticCar`,它是 `Car` 的子类。`AutomaticCar` 表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位:
|
下面的例子定义了一个新类叫 `AutomaticCar`,它是 `Car` 的子类。`AutomaticCar` 表示自动档汽车,它可以根据当前的速度自动选择合适的档位:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class AutomaticCar: Car {
|
class AutomaticCar: Car {
|
||||||
@ -207,7 +207,7 @@ class AutomaticCar: Car {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
无论何时当你设置 `AutomaticCar` 的 `currentSpeed` 属性,属性的 `didSet` 观察器就会自动地设置 `gear` 属性,为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以 `10`,然后向下取得最接近的整数值,最后加 `1` 来得到档位 `gear` 的值。例如,速度为 `35.0` 时,挡位为 `4`:
|
当你设置 `AutomaticCar` 的 `currentSpeed` 属性,属性的 `didSet` 观察器就会自动地设置 `gear` 属性,为新的速度选择一个合适的档位。具体来说就是,属性观察器将新的速度值除以 `10`,然后向下取得最接近的整数值,最后加 `1` 来得到档位 `gear` 的值。例如,速度为 `35.0` 时,档位为 `4`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let automatic = AutomaticCar()
|
let automatic = AutomaticCar()
|
||||||
@ -221,6 +221,6 @@ print("AutomaticCar: \(automatic.description)")
|
|||||||
|
|
||||||
你可以通过把方法,属性或下标标记为*`final`*来防止它们被重写,只需要在声明关键字前加上 `final` 修饰符即可(例如:`final var`,`final func`,`final class func`,以及 `final subscript`)。
|
你可以通过把方法,属性或下标标记为*`final`*来防止它们被重写,只需要在声明关键字前加上 `final` 修饰符即可(例如:`final var`,`final func`,`final class func`,以及 `final subscript`)。
|
||||||
|
|
||||||
任何试图对带有 `final` 标记的方法、属性或下标进行重写,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final 的。
|
任何试图对带有 `final` 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final。
|
||||||
|
|
||||||
你可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。
|
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。
|
||||||
|
|
||||||
特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。
|
这里需要特别指出,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。
|
||||||
|
|
||||||
下面几段代码将解释可选链式调用和强制展开的不同。
|
下面几段代码将解释可选链式调用和强制展开的不同。
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ class Residence {
|
|||||||
|
|
||||||
`Residence` 有一个 `Int` 类型的属性 `numberOfRooms`,其默认值为 `1`。`Person` 具有一个可选的 `residence` 属性,其类型为 `Residence?`。
|
`Residence` 有一个 `Int` 类型的属性 `numberOfRooms`,其默认值为 `1`。`Person` 具有一个可选的 `residence` 属性,其类型为 `Residence?`。
|
||||||
|
|
||||||
假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是是可选型而将初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil` 的 `residence` 属性:
|
假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是可选类型而将被初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil` 的 `residence` 属性:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let john = Person()
|
let john = Person()
|
||||||
@ -44,7 +44,7 @@ let roomCount = john.residence!.numberOfRooms
|
|||||||
// 这会引发运行时错误
|
// 这会引发运行时错误
|
||||||
```
|
```
|
||||||
|
|
||||||
`john.residence` 为非 `nil` 值的时候,上面的调用会成功,并且把 `roomCount` 设置为 `Int` 类型的房间数量。正如上面提到的,当 `residence` 为 `nil` 的时候上面这段代码会触发运行时错误。
|
`john.residence` 为非 `nil` 值的时候,上面的调用会成功,并且把 `roomCount` 设置为 `Int` 类型的房间数量。正如上面提到的,当 `residence` 为 `nil` 的时候,上面这段代码会触发运行时错误。
|
||||||
|
|
||||||
可选链式调用提供了另一种访问 `numberOfRooms` 的方式,使用问号(`?`)来替代原来的叹号(`!`):
|
可选链式调用提供了另一种访问 `numberOfRooms` 的方式,使用问号(`?`)来替代原来的叹号(`!`):
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ if let roomCount = john.residence?.numberOfRooms {
|
|||||||
<a name="defining_model_classes_for_optional_chaining"></a>
|
<a name="defining_model_classes_for_optional_chaining"></a>
|
||||||
## 为可选链式调用定义模型类
|
## 为可选链式调用定义模型类
|
||||||
|
|
||||||
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。
|
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法和下标。
|
||||||
|
|
||||||
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在 `Person` 和 `Residence` 的基础上增加了 `Room` 类和 `Address` 类,以及相关的属性、方法以及下标。
|
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在 `Person` 和 `Residence` 的基础上增加了 `Room` 类和 `Address` 类,以及相关的属性、方法以及下标。
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ class Room {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示某个大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称:
|
最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class Address {
|
class Address {
|
||||||
@ -145,7 +145,7 @@ class Address {
|
|||||||
func buildingIdentifier() -> String? {
|
func buildingIdentifier() -> String? {
|
||||||
if buildingName != nil {
|
if buildingName != nil {
|
||||||
return buildingName
|
return buildingName
|
||||||
} else if buildingNumber != nil && street != nil {
|
} else if let buildingNumber = buildingNumber, let street = street {
|
||||||
return "\(buildingNumber) \(street)"
|
return "\(buildingNumber) \(street)"
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@ -154,14 +154,14 @@ class Address {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Address` 类提供了 `buildingIdentifier()` 方法,返回值为 `String?`。 如果 `buildingName` 有值则返回 `buildingName`。或者,如果 `buildingNumber` 和 `street` 均有值则返回 `buildingNumber`。否则,返回 `nil`。
|
`Address` 类提供了 `buildingIdentifier()` 方法,返回值为 `String?`。 如果 `buildingName` 有值则返回 `buildingName`。或者,如果 `buildingNumber` 和 `street` 均有值,则返回两者拼接得到的字符串。否则,返回 `nil`。
|
||||||
|
|
||||||
<a name="accessing_properties_through_optional_chaining"></a>
|
<a name="accessing_properties_through_optional_chaining"></a>
|
||||||
## 通过可选链式调用访问属性
|
## 通过可选链式调用访问属性
|
||||||
|
|
||||||
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
||||||
|
|
||||||
下面的代码创建了一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性:
|
使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let john = Person()
|
let john = Person()
|
||||||
@ -186,7 +186,7 @@ john.residence?.address = someAddress
|
|||||||
|
|
||||||
在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`。
|
在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`。
|
||||||
|
|
||||||
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行。
|
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印 “Function was called”,这使你能验证等号右侧的代码是否被执行。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func createAddress() -> Address {
|
func createAddress() -> Address {
|
||||||
@ -299,7 +299,7 @@ testScores["Brian"]?[0] = 72
|
|||||||
// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81]
|
// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81]
|
||||||
```
|
```
|
||||||
|
|
||||||
上面的例子中定义了一个 `testScores` 数组,包含了两个键值对,把 `String` 类型的键映射到一个 `Int` 值的数组。这个例子用可选链式调用把 `"Dave"` 数组中第一个元素设为 `91`,把 `"Bev"` 数组的第一个元素 `+1`,然后尝试把 `"Brian"` 数组中的第一个元素设为 `72`。前两个调用成功,因为 `testScores` 字典中包含 `"Dave"` 和 `"Bev"` 这两个键。但是 `testScores` 字典中没有 `"Brian"` 这个键,所以第三个调用失败。
|
上面的例子中定义了一个 `testScores` 数组,包含了两个键值对,分别把 `String` 类型的键映射到一个 `Int` 值的数组。这个例子用可选链式调用把 `"Dave"` 数组中第一个元素设为 `91`,把 `"Bev"` 数组的第一个元素 `+1`,然后尝试把 `"Brian"` 数组中的第一个元素设为 `72`。前两个调用成功,因为 `testScores` 字典中包含 `"Dave"` 和 `"Bev"` 这两个键。但是 `testScores` 字典中没有 `"Brian"` 这个键,所以第三个调用失败。
|
||||||
|
|
||||||
<a name="linking_multiple_levels_of_chaining"></a>
|
<a name="linking_multiple_levels_of_chaining"></a>
|
||||||
## 连接多层可选链式调用
|
## 连接多层可选链式调用
|
||||||
@ -367,6 +367,7 @@ if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
|||||||
|
|
||||||
```swift
|
```swift
|
||||||
if let beginsWithThe =
|
if let beginsWithThe =
|
||||||
|
|
||||||
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
||||||
if beginsWithThe {
|
if beginsWithThe {
|
||||||
print("John's building identifier begins with \"The\".")
|
print("John's building identifier begins with \"The\".")
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
# 错误处理
|
# 错误处理
|
||||||
|
|
||||||
*错误处理(Error handling)*是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一等公民支持。
|
*错误处理(Error handling)* 是响应错误以及从错误中恢复的过程。Swift 在运行时提供了抛出、捕获、传递和操作可恢复错误(recoverable errors)的一等支持(first-class support)。
|
||||||
|
|
||||||
某些操作无法保证总是执行完所有代码或总是生成有用的结果。可选类型可用来表示值缺失,但是当某个操作失败时,最好能得知失败的原因,从而可以作出相应的应对。
|
某些操作无法保证总是执行完所有代码或生成有用的结果。可选类型用来表示值缺失,但是当某个操作失败时,理解造成失败的原因有助于你的代码作出相应的应对。
|
||||||
|
|
||||||
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。
|
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序处理并解决某些错误,然后把它解决不了的错误报告给用户。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的 `NSError`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 4.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的[错误处理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。
|
> Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的 `NSError`。更多详情参见 [用 Swift 解决 Cocoa 错误](https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift)。
|
||||||
|
|
||||||
<a name="representing_and_throwing_errors"></a>
|
<a name="representing_and_throwing_errors"></a>
|
||||||
## 表示并抛出错误
|
## 表示与抛出错误
|
||||||
|
|
||||||
在 Swift 中,错误用符合 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
|
在 Swift 中,错误用遵循 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
|
||||||
|
|
||||||
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态:
|
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,在游戏中操作自动贩卖机时,你可以这样表示可能会出现的错误状态:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
enum VendingMachineError: Error {
|
enum VendingMachineError: Error {
|
||||||
@ -25,7 +25,7 @@ enum VendingMachineError: Error {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币:
|
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 语句。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
||||||
@ -47,10 +47,11 @@ Swift 中有 `4` 种处理错误的方式。你可以把函数抛出的错误传
|
|||||||
<a name="propagating_errors_using_throwing_functions"></a>
|
<a name="propagating_errors_using_throwing_functions"></a>
|
||||||
### 用 throwing 函数传递错误
|
### 用 throwing 函数传递错误
|
||||||
|
|
||||||
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作*throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在箭头(`->`)的前面。
|
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作 *throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在返回箭头(`->`)的前面。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
func canThrowErrors() throws -> String
|
func canThrowErrors() throws -> String
|
||||||
|
|
||||||
func cannotThrowErrors() -> String
|
func cannotThrowErrors() -> String
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -64,8 +65,8 @@ func cannotThrowErrors() -> String
|
|||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct Item {
|
struct Item {
|
||||||
var price: Int
|
var price: Int
|
||||||
var count: Int
|
var count: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
class VendingMachine {
|
class VendingMachine {
|
||||||
@ -75,9 +76,6 @@ class VendingMachine {
|
|||||||
"Pretzels": Item(price: 7, count: 11)
|
"Pretzels": Item(price: 7, count: 11)
|
||||||
]
|
]
|
||||||
var coinsDeposited = 0
|
var coinsDeposited = 0
|
||||||
func dispenseSnack(snack: String) {
|
|
||||||
print("Dispensing \(snack)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func vend(itemNamed name: String) throws {
|
func vend(itemNamed name: String) throws {
|
||||||
guard let item = inventory[name] else {
|
guard let item = inventory[name] else {
|
||||||
@ -103,7 +101,7 @@ class VendingMachine {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在 `vend(itemNamed:)` 方法的实现中使用了 `guard` 语句来提前退出方法,确保在购买某个物品所需的条件中,有任一条件不满足时,能提前退出方法并抛出相应的错误。由于 `throw` 语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
|
在 `vend(itemNamed:)` 方法的实现中使用了 `guard` 语句来确保在购买某个物品所需的条件中有任一条件不满足时,能提前退出方法并抛出相应的错误。由于 `throw` 语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
|
||||||
|
|
||||||
因为 `vend(itemNamed:)` 方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用 `do-catch` 语句,`try?` 或 `try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(person:vendingMachine:)` 同样是一个 throwing 函数,任何由 `vend(itemNamed:)` 方法抛出的错误会一直被传递到 `buyFavoriteSnack(person:vendingMachine:)` 函数被调用的地方。
|
因为 `vend(itemNamed:)` 方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用 `do-catch` 语句,`try?` 或 `try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(person:vendingMachine:)` 同样是一个 throwing 函数,任何由 `vend(itemNamed:)` 方法抛出的错误会一直被传递到 `buyFavoriteSnack(person:vendingMachine:)` 函数被调用的地方。
|
||||||
|
|
||||||
@ -147,29 +145,57 @@ do {
|
|||||||
statements
|
statements
|
||||||
} catch pattern 2 where condition {
|
} catch pattern 2 where condition {
|
||||||
statements
|
statements
|
||||||
|
} catch {
|
||||||
|
statements
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在 `catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
|
在 `catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
|
||||||
|
|
||||||
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的 `do-catch` 错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理:
|
举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部三种情况:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
var vendingMachine = VendingMachine()
|
var vendingMachine = VendingMachine()
|
||||||
vendingMachine.coinsDeposited = 8
|
vendingMachine.coinsDeposited = 8
|
||||||
do {
|
do {
|
||||||
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
||||||
|
print("Success! Yum.")
|
||||||
} catch VendingMachineError.invalidSelection {
|
} catch VendingMachineError.invalidSelection {
|
||||||
print("Invalid Selection.")
|
print("Invalid Selection.")
|
||||||
} catch VendingMachineError.outOfStock {
|
} catch VendingMachineError.outOfStock {
|
||||||
print("Out of Stock.")
|
print("Out of Stock.")
|
||||||
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
||||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||||
|
} catch {
|
||||||
|
print("Unexpected error: \(error).")
|
||||||
}
|
}
|
||||||
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
|
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
|
||||||
```
|
```
|
||||||
|
|
||||||
上面的例子中,`buyFavoriteSnack(person:vendingMachine:)` 函数在一个 `try` 表达式中调用,因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到 `catch` 子句中,并判断这个错误是否要被继续传递下去。如果没有错误抛出,`do` 子句中余下的语句就会被执行。
|
上面的例子中,`buyFavoriteSnack(person:vendingMachine:)` 函数在一个 `try` 表达式中被调用,是因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到 `catch` 子句中,并判断这个错误是否要被继续传递下去。如果错误没有被匹配,它会被最后一个 `catch` 语句捕获,并赋值给一个 `error` 常量。如果没有错误被抛出,`do` 子句中余下的语句就会被执行。
|
||||||
|
|
||||||
|
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的。在不会抛出错误的函数中,必须用 `do-catch` 语句处理错误。而能够抛出错误的函数既可以使用 `do-catch` 语句处理,也可以让调用方来处理错误。如果错误传递到了顶层作用域却依然没有被处理,你会得到一个运行时错误。
|
||||||
|
|
||||||
|
以下面的代码为例,不是 `VendingMachineError` 中申明的错误会在调用函数的地方被捕获:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
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."
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 `vend(itemNamed:)` 抛出的是一个 `VendingMachineError` 类型的错误,`nourish(with:)` 会打印一条消息,否则 `nourish(with:)` 会将错误抛给它的调用方。这个错误之后会被通用的 `catch` 语句捕获。
|
||||||
|
|
||||||
### 将错误转换成可选值
|
### 将错误转换成可选值
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
|
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
|
||||||
|
|
||||||
你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./21_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
|
你也可以用它来检查一个类型是否遵循了某个协议,就像在[检验协议遵循](./21_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
|
||||||
|
|
||||||
<a name="defining_a_class_hierarchy_for_type_casting"></a>
|
<a name="defining_a_class_hierarchy_for_type_casting"></a>
|
||||||
## 为类型转换定义类层次
|
## 为类型转换定义类层次
|
||||||
|
|||||||
@ -77,7 +77,7 @@ let john = Person(fullName: "John Appleseed")
|
|||||||
|
|
||||||
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。
|
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。
|
||||||
|
|
||||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。)
|
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好遵循了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地遵循了协议。(如果协议要求未被完全遵循,在编译时会报错。)
|
||||||
|
|
||||||
下面是一个更为复杂的类,它适配并遵循了 `FullyNamed` 协议:
|
下面是一个更为复杂的类,它适配并遵循了 `FullyNamed` 协议:
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ protocol RandomNumberGenerator {
|
|||||||
|
|
||||||
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
|
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
|
||||||
|
|
||||||
如下所示,下边是一个遵循并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。
|
如下所示,下边是一个遵循 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class LinearCongruentialGenerator: RandomNumberGenerator {
|
class LinearCongruentialGenerator: RandomNumberGenerator {
|
||||||
@ -209,7 +209,7 @@ class SomeClass: SomeProtocol {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
|
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议。
|
||||||
|
|
||||||
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
|
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
|
||||||
|
|
||||||
@ -392,7 +392,7 @@ class DiceGameTracker: DiceGameDelegate {
|
|||||||
|
|
||||||
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
|
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
|
||||||
|
|
||||||
无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。
|
无论当前进行的是何种游戏,由于 `game` 遵循 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。
|
||||||
|
|
||||||
`DiceGameTracker` 的运行情况如下所示:
|
`DiceGameTracker` 的运行情况如下所示:
|
||||||
|
|
||||||
@ -413,11 +413,11 @@ game.play()
|
|||||||
<a name="adding_protocol_conformance_with_an_extension"></a>
|
<a name="adding_protocol_conformance_with_an_extension"></a>
|
||||||
## 在扩展里添加协议遵循
|
## 在扩展里添加协议遵循
|
||||||
|
|
||||||
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。
|
即便无法修改源代码,依然可以通过扩展令已有类型遵循协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以遵循协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
|
> 通过扩展令已有类型遵循协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
|
||||||
|
|
||||||
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
|
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
|
||||||
|
|
||||||
@ -427,7 +427,7 @@ protocol TextRepresentable {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
可以通过扩展,令先前提到的 `Dice` 类遵循并符合 `TextRepresentable` 协议:
|
可以通过扩展,令先前提到的 `Dice` 类遵循 `TextRepresentable` 协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension Dice: TextRepresentable {
|
extension Dice: TextRepresentable {
|
||||||
@ -437,7 +437,7 @@ extension Dice: TextRepresentable {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
通过扩展遵循并符合协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
|
通过扩展遵循协议,和在原始定义中遵循协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
|
||||||
|
|
||||||
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
|
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
|
||||||
|
|
||||||
@ -447,7 +447,7 @@ print(d12.textualDescription)
|
|||||||
// 打印 “A 12-sided dice”
|
// 打印 “A 12-sided dice”
|
||||||
```
|
```
|
||||||
|
|
||||||
同样,`SnakesAndLadders` 类也可以通过扩展遵循并符合 `TextRepresentable` 协议:
|
同样,`SnakesAndLadders` 类也可以通过扩展遵循 `TextRepresentable` 协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension SnakesAndLadders: TextRepresentable {
|
extension SnakesAndLadders: TextRepresentable {
|
||||||
@ -462,7 +462,7 @@ print(game.textualDescription)
|
|||||||
<a name="Conditionally_Conforming_to_a_Protocol"></a>
|
<a name="Conditionally_Conforming_to_a_Protocol"></a>
|
||||||
## 有条件地遵循协议
|
## 有条件地遵循协议
|
||||||
|
|
||||||
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见[泛型 Where 分句](./22_Generics.html##where_clauses)。
|
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你遵循协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见[泛型 Where 分句](./22_Generics.html##where_clauses)。
|
||||||
|
|
||||||
下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。
|
下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。
|
||||||
|
|
||||||
@ -479,9 +479,9 @@ print(myDice.textualDescription)
|
|||||||
```
|
```
|
||||||
|
|
||||||
<a name="declaring_protocol_adoption_with_an_extension"></a>
|
<a name="declaring_protocol_adoption_with_an_extension"></a>
|
||||||
## 在扩展里声明采纳协议
|
## 在扩展里声明遵循协议
|
||||||
|
|
||||||
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展采纳该协议:
|
当一个类型已经遵循了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展遵循该协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct Hamster {
|
struct Hamster {
|
||||||
@ -549,7 +549,7 @@ protocol PrettyTextRepresentable: TextRepresentable {
|
|||||||
|
|
||||||
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
|
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
|
||||||
|
|
||||||
如下所示,扩展 `SnakesAndLadders`,使其遵循并符合 `PrettyTextRepresentable` 协议:
|
如下所示,扩展 `SnakesAndLadders`,使其遵循 `PrettyTextRepresentable` 协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension SnakesAndLadders: PrettyTextRepresentable {
|
extension SnakesAndLadders: PrettyTextRepresentable {
|
||||||
@ -587,7 +587,7 @@ print(game.prettyTextualDescription)
|
|||||||
<a name="class_only_protocol"></a>
|
<a name="class_only_protocol"></a>
|
||||||
## 类专属的协议
|
## 类专属的协议
|
||||||
|
|
||||||
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。
|
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型遵循(以及非结构体或者非枚举的类型)。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
|
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
|
||||||
@ -595,7 +595,7 @@ protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳 `SomeClassOnlyProtocol`,则会导致编译时错误。
|
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型遵循。如果尝试让结构体或枚举类型遵循 `SomeClassOnlyProtocol` 协议,则会导致编译时错误。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
@ -629,11 +629,11 @@ wishHappyBirthday(to: birthdayPerson)
|
|||||||
// 打印 “Happy birthday Malcolm - you're 21!”
|
// 打印 “Happy birthday Malcolm - you're 21!”
|
||||||
```
|
```
|
||||||
|
|
||||||
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。
|
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体遵循了这两个协议。
|
||||||
|
|
||||||
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged`, 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数符合这两个协议即可。
|
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged`, 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数遵循这两个协议即可。
|
||||||
|
|
||||||
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
|
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时遵循这两个协议,所以这个参数合法,函数将打印生日问候语。
|
||||||
|
|
||||||
这里有一个例子:将 Location 类和前面的 Named 协议进行组合:
|
这里有一个例子:将 Location 类和前面的 Named 协议进行组合:
|
||||||
|
|
||||||
@ -667,12 +667,12 @@ beginConcert(in: seattle)
|
|||||||
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是一个 Location 的子类。就像,如果你新建一个类继承与 Location,但是没有遵循 Named 协议,你用这个类的实例去调用 `beginConcert(in:)` 函数也是不合法的。
|
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是一个 Location 的子类。就像,如果你新建一个类继承与 Location,但是没有遵循 Named 协议,你用这个类的实例去调用 `beginConcert(in:)` 函数也是不合法的。
|
||||||
|
|
||||||
<a name="checking_for_protocol_conformance"></a>
|
<a name="checking_for_protocol_conformance"></a>
|
||||||
## 检查协议一致性
|
## 检查协议遵循
|
||||||
|
|
||||||
你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
|
你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议遵循,即是否遵循了某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
|
||||||
|
|
||||||
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`。
|
* `is` 用来检查实例是否遵循某个协议,若遵循则返回 `true`,否则返回 `false`。
|
||||||
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。
|
* `as?` 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。
|
||||||
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
|
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
|
||||||
|
|
||||||
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`:
|
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`:
|
||||||
@ -698,7 +698,7 @@ class Country: HasArea {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地符合了 `HasArea` 协议。
|
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地遵循了 `HasArea` 协议。
|
||||||
|
|
||||||
如下所示,`Animal` 是一个未遵循 `HasArea` 协议的类:
|
如下所示,`Animal` 是一个未遵循 `HasArea` 协议的类:
|
||||||
|
|
||||||
@ -721,7 +721,7 @@ let objects: [AnyObject] = [
|
|||||||
|
|
||||||
`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。
|
`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。
|
||||||
|
|
||||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议:
|
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否遵循 `HasArea` 协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
for object in objects {
|
for object in objects {
|
||||||
@ -736,7 +736,7 @@ for object in objects {
|
|||||||
// Something that doesn't have an area
|
// Something that doesn't have an area
|
||||||
```
|
```
|
||||||
|
|
||||||
当迭代出的元素符合 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
|
当迭代出的元素遵循 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
|
||||||
|
|
||||||
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle`,`Country`,`Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
|
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle`,`Country`,`Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
|
||||||
|
|
||||||
@ -900,7 +900,7 @@ extension PrettyTextRepresentable {
|
|||||||
|
|
||||||
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.html#where_clauses)中所描述的。
|
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.html#where_clauses)中所描述的。
|
||||||
|
|
||||||
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。
|
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵循 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension Collection where Element: Equatable {
|
extension Collection where Element: Equatable {
|
||||||
|
|||||||
@ -235,11 +235,11 @@ if let topItem = stackOfStrings.topItem {
|
|||||||
<a name="type_constraints"></a>
|
<a name="type_constraints"></a>
|
||||||
## 类型约束
|
## 类型约束
|
||||||
|
|
||||||
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
|
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者遵循一个特定的协议或协议组合。
|
||||||
|
|
||||||
例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
|
例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
|
||||||
|
|
||||||
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须符合 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。
|
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须遵循 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。
|
||||||
|
|
||||||
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
|
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
|
||||||
|
|
||||||
@ -254,7 +254,7 @@ func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须符合 `SomeProtocol` 协议的类型约束。
|
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须遵循 `SomeProtocol` 协议的类型约束。
|
||||||
|
|
||||||
<a name="type_constraints_in_action"></a>
|
<a name="type_constraints_in_action"></a>
|
||||||
### 类型约束实践
|
### 类型约束实践
|
||||||
@ -314,9 +314,9 @@ func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`findIndex(of:in:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T`”。
|
`findIndex(of:in:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何遵循 `Equatable` 协议的类型 `T`”。
|
||||||
|
|
||||||
`findIndex(of:in:)` 函数现在可以成功编译了,并且可以作用于任何符合 `Equatable` 的类型,如 `Double` 或 `String`:
|
`findIndex(of:in:)` 函数现在可以成功编译了,并且可以作用于任何遵循 `Equatable` 协议的类型,如 `Double` 或 `String`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
|
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
|
||||||
@ -328,7 +328,7 @@ let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
|
|||||||
<a name="associated_types"></a>
|
<a name="associated_types"></a>
|
||||||
## 关联类型
|
## 关联类型
|
||||||
|
|
||||||
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
|
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被遵循时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
|
||||||
|
|
||||||
<a name="associated_types_in_action"></a>
|
<a name="associated_types_in_action"></a>
|
||||||
### 关联类型实践
|
### 关联类型实践
|
||||||
@ -344,21 +344,21 @@ protocol Container {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Container` 协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:
|
`Container` 协议定义了三个任何遵循了该协议的类型(即容器)必须提供的功能:
|
||||||
|
|
||||||
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
|
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
|
||||||
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
|
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
|
||||||
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
|
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
|
||||||
|
|
||||||
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵从 `Container` 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵循 `Container` 协议的类型必须提供的功能。遵循协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||||
|
|
||||||
任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
|
任何遵循 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
|
||||||
|
|
||||||
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
|
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
|
||||||
|
|
||||||
为了达到这个目的,`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。这个协议无法定义 `Item` 是什么类型的别名,这个信息将留给遵从协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。
|
为了达到这个目的,`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。这个协议无法定义 `Item` 是什么类型的别名,这个信息将留给遵循协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。
|
||||||
|
|
||||||
下面是先前的非泛型的 `IntStack` 类型,这一版本采纳并符合了 `Container` 协议:
|
下面是先前的非泛型的 `IntStack` 类型,这一版本遵循了 `Container` 协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct IntStack: Container {
|
struct IntStack: Container {
|
||||||
@ -388,9 +388,9 @@ struct IntStack: Container {
|
|||||||
|
|
||||||
此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item` 为 `Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。
|
此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item` 为 `Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。
|
||||||
|
|
||||||
由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `Item` 为 `Int`。因为 `IntStack` 符合 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
|
由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `Item` 为 `Int`。因为 `IntStack` 遵循 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
|
||||||
|
|
||||||
你也可以让泛型 `Stack` 结构体遵从 `Container` 协议:
|
你也可以让泛型 `Stack` 结构体遵循 `Container` 协议:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct Stack<Element>: Container {
|
struct Stack<Element>: Container {
|
||||||
@ -420,9 +420,9 @@ struct Stack<Element>: Container {
|
|||||||
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
|
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
|
||||||
### 通过扩展一个存在的类型来指定关联类型
|
### 通过扩展一个存在的类型来指定关联类型
|
||||||
|
|
||||||
[通过扩展添加协议一致性](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。
|
[通过扩展添加协议遵循](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型遵循一个协议,这包括使用了关联类型的协议。
|
||||||
|
|
||||||
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 采纳该协议就可以扩展 `Array`,使其遵从 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展采纳协议](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
|
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都遵循 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 遵循该协议就可以扩展 `Array`,使其遵循 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展遵循协议](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension Array: Container {}
|
extension Array: Container {}
|
||||||
@ -433,7 +433,7 @@ extension Array: Container {}
|
|||||||
<a name="using_type_annotations_to_constrain_an_associated_type"></a>
|
<a name="using_type_annotations_to_constrain_an_associated_type"></a>
|
||||||
### 给关联类型添加约束
|
### 给关联类型添加约束
|
||||||
|
|
||||||
你可以给协议里的关联类型添加类型注释,让遵守协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` 的 `Container` 类型:
|
你可以给协议里的关联类型添加类型注释,让遵循协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` 的 `Container` 类型:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
protocol Container {
|
protocol Container {
|
||||||
@ -444,7 +444,7 @@ protocol Container {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
为了遵守了 `Container` 协议,Item 类型也必须遵守 `Equatable` 协议。
|
为了遵循 `Container` 协议,Item 类型也必须遵循 `Equatable` 协议。
|
||||||
|
|
||||||
<a name="Using_a_Protocol_in_Its_Associated_Type’s_Constraints"></a>
|
<a name="Using_a_Protocol_in_Its_Associated_Type’s_Constraints"></a>
|
||||||
### 在关联类型约束里使用协议
|
### 在关联类型约束里使用协议
|
||||||
@ -502,7 +502,7 @@ extension IntStack: SuffixableContainer {
|
|||||||
|
|
||||||
[类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。
|
[类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。
|
||||||
|
|
||||||
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
|
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵循某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
|
||||||
|
|
||||||
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。
|
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。
|
||||||
|
|
||||||
@ -534,10 +534,10 @@ func allItemsMatch<C1: Container, C2: Container>
|
|||||||
|
|
||||||
这个函数的类型参数列表还定义了对两个类型参数的要求:
|
这个函数的类型参数列表还定义了对两个类型参数的要求:
|
||||||
|
|
||||||
- `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
|
- `C1` 必须遵循 `Container` 协议(写作 `C1: Container`)。
|
||||||
- `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
|
- `C2` 必须遵循 `Container` 协议(写作 `C2: Container`)。
|
||||||
- `C1` 的 `Item` 必须和 `C2` 的 `Item` 类型相同(写作 `C1.Item == C2.Item`)。
|
- `C1` 的 `Item` 必须和 `C2` 的 `Item` 类型相同(写作 `C1.Item == C2.Item`)。
|
||||||
- `C1` 的 `Item` 必须符合 `Equatable` 协议(写作 `C1.Item: Equatable`)。
|
- `C1` 的 `Item` 必须遵循 `Equatable` 协议(写作 `C1.Item: Equatable`)。
|
||||||
|
|
||||||
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
|
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
|
||||||
|
|
||||||
@ -576,7 +576,7 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
|||||||
// 打印 “All items match.”
|
// 打印 “All items match.”
|
||||||
```
|
```
|
||||||
|
|
||||||
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵循 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
||||||
|
|
||||||
<a name="extensions_with_a_generic_where_clause"></a>
|
<a name="extensions_with_a_generic_where_clause"></a>
|
||||||
## 具有泛型 Where 子句的扩展
|
## 具有泛型 Where 子句的扩展
|
||||||
@ -594,7 +594,7 @@ extension Stack where Element: Equatable {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是符合 Equatable 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 Equatable 协议时,扩展才会添加 `isTop(_:)` 方法。
|
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是遵循 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素遵循 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
|
||||||
|
|
||||||
以下是 `isTop(_:)` 方法的调用方式:
|
以下是 `isTop(_:)` 方法的调用方式:
|
||||||
|
|
||||||
@ -607,7 +607,7 @@ if stackOfStrings.isTop("tres") {
|
|||||||
// 打印 "Top element is tres."
|
// 打印 "Top element is tres."
|
||||||
```
|
```
|
||||||
|
|
||||||
如果尝试在其元素不符合 Equatable 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
|
如果尝试在其元素未遵循 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct NotEquatable { }
|
struct NotEquatable { }
|
||||||
@ -627,7 +627,7 @@ extension Container where Item: Equatable {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 Equatable 协议的。
|
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何遵循 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是遵循 `Equatable` 协议的。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
if [9, 9, 9].startsWith(42) {
|
if [9, 9, 9].startsWith(42) {
|
||||||
@ -638,7 +638,7 @@ if [9, 9, 9].startsWith(42) {
|
|||||||
// 打印 "Starts with something else."
|
// 打印 "Starts with something else."
|
||||||
```
|
```
|
||||||
|
|
||||||
上述示例中的泛型 `where` 子句要求 `Item` 符合协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
|
上述示例中的泛型 `where` 子句要求 `Item` 遵循协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
extension Container where Item == Double {
|
extension Container where Item == Double {
|
||||||
@ -705,7 +705,7 @@ extension Container {
|
|||||||
|
|
||||||
这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:
|
这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:
|
||||||
|
|
||||||
- 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。
|
- 在尖括号中的泛型参数 `Indices`,必须是遵循标准库中的 `Sequence` 协议的类型。
|
||||||
- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
|
- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
|
||||||
- 泛型 `where` 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。
|
- 泛型 `where` 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ Swift 使用*自动引用计数(ARC)*机制来跟踪和管理你的应用程
|
|||||||
> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
|
> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
|
||||||
|
|
||||||
<a name="how_arc_works"></a>
|
<a name="how_arc_works"></a>
|
||||||
|
|
||||||
## 自动引用计数的工作机制
|
## 自动引用计数的工作机制
|
||||||
|
|
||||||
当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
|
当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
|
||||||
@ -39,7 +40,7 @@ class Person {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Person` 类有一个构造函数,此构造函数为实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息。
|
`Person` 类有一个构造器,此构造器为实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构器,这个析构器会在实例被销毁时打印一条消息。
|
||||||
|
|
||||||
接下来的代码片段定义了三个类型为 `Person?` 的变量,用来按照代码片段中的顺序,为新的 `Person` 实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是 `Person`),它们的值会被自动初始化为 `nil`,目前还不会引用到 `Person` 类的实例。
|
接下来的代码片段定义了三个类型为 `Person?` 的变量,用来按照代码片段中的顺序,为新的 `Person` 实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是 `Person`),它们的值会被自动初始化为 `nil`,目前还不会引用到 `Person` 类的实例。
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ reference1 = Person(name: "John Appleseed")
|
|||||||
// 打印 "John Appleseed is being initialized"
|
// 打印 "John Appleseed is being initialized"
|
||||||
```
|
```
|
||||||
|
|
||||||
应当注意到当你调用 `Person` 类的构造函数的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造函数被执行。
|
应当注意到当你调用 `Person` 类的构造器的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造器被执行。
|
||||||
|
|
||||||
由于 `Person` 类的新实例被赋值给了 `reference1` 变量,所以 `reference1` 到 `Person` 类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证 `Person` 实例被保持在内存中不被销毁。
|
由于 `Person` 类的新实例被赋值给了 `reference1` 变量,所以 `reference1` 到 `Person` 类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证 `Person` 实例被保持在内存中不被销毁。
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ class Apartment {
|
|||||||
|
|
||||||
类似的,每个 `Apartment` 实例有一个叫 `unit`,类型为 `String` 的属性,并有一个可选的初始化为 `nil` 的 `tenant` 属性。`tenant` 属性是可选的,因为一栋公寓并不总是有居民。
|
类似的,每个 `Apartment` 实例有一个叫 `unit`,类型为 `String` 的属性,并有一个可选的初始化为 `nil` 的 `tenant` 属性。`tenant` 属性是可选的,因为一栋公寓并不总是有居民。
|
||||||
|
|
||||||
这两个类都定义了析构函数,用以在类实例被析构的时候输出信息。这让你能够知晓 `Person` 和 `Apartment` 的实例是否像预期的那样被销毁。
|
这两个类都定义了析构器,用以在类实例被析构的时候输出信息。这让你能够知晓 `Person` 和 `Apartment` 的实例是否像预期的那样被销毁。
|
||||||
|
|
||||||
接下来的代码片段定义了两个可选类型的变量 `john` 和 `unit4A`,并分别被设定为下面的 `Apartment` 和 `Person` 的实例。这两个变量都被初始化为 `nil`,这正是可选类型的优点:
|
接下来的代码片段定义了两个可选类型的变量 `john` 和 `unit4A`,并分别被设定为下面的 `Apartment` 和 `Person` 的实例。这两个变量都被初始化为 `nil`,这正是可选类型的优点:
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ unit4A = Apartment(unit: "4A")
|
|||||||
|
|
||||||
在两个实例被创建和赋值后,下图表现了强引用的关系。变量 `john` 现在有一个指向 `Person` 实例的强引用,而变量 `unit4A` 有一个指向 `Apartment` 实例的强引用:
|
在两个实例被创建和赋值后,下图表现了强引用的关系。变量 `john` 现在有一个指向 `Person` 实例的强引用,而变量 `unit4A` 有一个指向 `Apartment` 实例的强引用:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来展开和访问可选变量 `john` 和 `unit4A` 中的实例,这样实例的属性才能被赋值:
|
现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来展开和访问可选变量 `john` 和 `unit4A` 中的实例,这样实例的属性才能被赋值:
|
||||||
|
|
||||||
@ -143,7 +144,7 @@ unit4A!.tenant = john
|
|||||||
|
|
||||||
在将两个实例联系在一起之后,强引用的关系如图所示:
|
在将两个实例联系在一起之后,强引用的关系如图所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
不幸的是,这两个实例关联后会产生一个循环强引用。`Person` 实例现在有了一个指向 `Apartment` 实例的强引用,而 `Apartment` 实例也有了一个指向 `Person` 实例的强引用。因此,当你断开 `john` 和 `unit4A` 变量所持有的强引用时,引用计数并不会降为 `0`,实例也不会被 ARC 销毁:
|
不幸的是,这两个实例关联后会产生一个循环强引用。`Person` 实例现在有了一个指向 `Apartment` 实例的强引用,而 `Apartment` 实例也有了一个指向 `Person` 实例的强引用。因此,当你断开 `john` 和 `unit4A` 变量所持有的强引用时,引用计数并不会降为 `0`,实例也不会被 ARC 销毁:
|
||||||
|
|
||||||
@ -152,20 +153,21 @@ john = nil
|
|||||||
unit4A = nil
|
unit4A = nil
|
||||||
```
|
```
|
||||||
|
|
||||||
注意,当你把这两个变量设为 `nil` 时,没有任何一个析构函数被调用。循环强引用会一直阻止 `Person` 和 `Apartment` 类实例的销毁,这就在你的应用程序中造成了内存泄漏。
|
注意,当你把这两个变量设为 `nil` 时,没有任何一个析构器被调用。循环强引用会一直阻止 `Person` 和 `Apartment` 类实例的销毁,这就在你的应用程序中造成了内存泄漏。
|
||||||
|
|
||||||
在你将 `john` 和 `unit4A` 赋值为 `nil` 后,强引用关系如下图:
|
在你将 `john` 和 `unit4A` 赋值为 `nil` 后,强引用关系如下图:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
`Person` 和 `Apartment` 实例之间的强引用关系保留了下来并且不会被断开。
|
`Person` 和 `Apartment` 实例之间的强引用关系保留了下来并且不会被断开。
|
||||||
|
|
||||||
<a name="resolving_strong_reference_cycles_between_class_instances"></a>
|
<a name="resolving_strong_reference_cycles_between_class_instances"></a>
|
||||||
|
|
||||||
## 解决实例之间的循环强引用
|
## 解决实例之间的循环强引用
|
||||||
|
|
||||||
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
|
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
|
||||||
|
|
||||||
弱引用和无主引用允许循环引用中的一个实例引用而另外一个实例*不*保持强引用。这样实例能够互相引用而不产生循环强引用。
|
弱引用和无主引用允许循环引用中的一个实例引用另一个实例而*不*保持强引用。这样实例能够互相引用而不产生循环强引用。
|
||||||
|
|
||||||
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。
|
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。
|
||||||
|
|
||||||
@ -174,7 +176,7 @@ Swift 提供了两种办法用来解决你在使用类的属性时所遇到的
|
|||||||
|
|
||||||
*弱引用*不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 `weak` 关键字表明这是一个弱引用。
|
*弱引用*不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 `weak` 关键字表明这是一个弱引用。
|
||||||
|
|
||||||
因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其赋值为 `nil`。并且因为弱引用可以允许它们的值在运行时被赋值为 `nil`,所以它们会被定义为可选类型变量,而不是常量。
|
因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其弱引用赋值为 `nil`。并且因为弱引用需要在运行时允许被赋值为 `nil`,所以它们会被定义为可选类型变量,而不是常量。
|
||||||
|
|
||||||
你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。
|
你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。
|
||||||
|
|
||||||
@ -215,31 +217,29 @@ unit4A!.tenant = john
|
|||||||
|
|
||||||
现在,两个关联在一起的实例的引用关系如下图所示:
|
现在,两个关联在一起的实例的引用关系如下图所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
`Person` 实例依然保持对 `Apartment` 实例的强引用,但是 `Apartment` 实例只持有对 `Person` 实例的弱引用。这意味着当你断开 `john` 变量所保持的强引用时,再也没有指向 `Person` 实例的强引用了:
|
`Person` 实例依然保持对 `Apartment` 实例的强引用,但是 `Apartment` 实例只持有对 `Person` 实例的弱引用。这意味着当你通过把 `john` 变量赋值为 `nil` 而断开其所保持的强引用时,再也没有指向 `Person` 实例的强引用了:
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁:
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
john = nil
|
john = nil
|
||||||
// 打印 "John Appleseed is being deinitialized"
|
// 打印 "John Appleseed is being deinitialized"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁,且 `tenant` 属性会被赋值为 `nil`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
唯一剩下的指向 `Apartment` 实例的强引用来自于变量 `unit4A`。如果你断开这个强引用,再也没有指向 `Apartment` 实例的强引用了:
|
唯一剩下的指向 `Apartment` 实例的强引用来自于变量 `unit4A`。如果你断开这个强引用,再也没有指向 `Apartment` 实例的强引用了:
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
由于再也没有指向 `Apartment` 实例的强引用,该实例也会被销毁:
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
unit4A = nil
|
unit4A = nil
|
||||||
// 打印 "Apartment 4A is being deinitialized"
|
// 打印 "Apartment 4A is being deinitialized"
|
||||||
```
|
```
|
||||||
|
|
||||||
上面的两段代码展示了变量 `john` 和 `unit4A` 在被赋值为 `nil` 后,`Person` 实例和 `Apartment` 实例的析构函数都打印出“销毁”的信息。这证明了引用循环被打破了。
|
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
@ -262,7 +262,7 @@ unit4A = nil
|
|||||||
|
|
||||||
`Customer` 和 `CreditCard` 之间的关系与前面弱引用例子中 `Apartment` 和 `Person` 的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer` 类有一个可选类型的 `card` 属性,但是 `CreditCard` 类有一个非可选类型的 `customer` 属性。
|
`Customer` 和 `CreditCard` 之间的关系与前面弱引用例子中 `Apartment` 和 `Person` 的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer` 类有一个可选类型的 `card` 属性,但是 `CreditCard` 类有一个非可选类型的 `customer` 属性。
|
||||||
|
|
||||||
此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造函数的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。
|
此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造器的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。
|
||||||
|
|
||||||
由于信用卡总是关联着一个客户,因此将 `customer` 属性定义为无主引用,用以避免循环强引用:
|
由于信用卡总是关联着一个客户,因此将 `customer` 属性定义为无主引用,用以避免循环强引用:
|
||||||
|
|
||||||
@ -306,13 +306,13 @@ john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
|
|||||||
|
|
||||||
在你关联两个实例后,它们的引用关系如下图所示:
|
在你关联两个实例后,它们的引用关系如下图所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
`Customer` 实例持有对 `CreditCard` 实例的强引用,而 `CreditCard` 实例持有对 `Customer` 实例的无主引用。
|
`Customer` 实例持有对 `CreditCard` 实例的强引用,而 `CreditCard` 实例持有对 `Customer` 实例的无主引用。
|
||||||
|
|
||||||
由于 `customer` 的无主引用,当你断开 `john` 变量持有的强引用时,再也没有指向 `Customer` 实例的强引用了:
|
由于 `customer` 的无主引用,当你断开 `john` 变量持有的强引用时,再也没有指向 `Customer` 实例的强引用了:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
由于再也没有指向 `Customer` 实例的强引用,该实例被销毁了。其后,再也没有指向 `CreditCard` 实例的强引用,该实例也随之被销毁了:
|
由于再也没有指向 `Customer` 实例的强引用,该实例被销毁了。其后,再也没有指向 `CreditCard` 实例的强引用,该实例也随之被销毁了:
|
||||||
|
|
||||||
@ -322,14 +322,14 @@ john = nil
|
|||||||
// 打印 "Card #1234567890123456 is being deinitialized"
|
// 打印 "Card #1234567890123456 is being deinitialized"
|
||||||
```
|
```
|
||||||
|
|
||||||
最后的代码展示了在 `john` 变量被设为 `nil` 后 `Customer` 实例和 `CreditCard` 实例的构造函数都打印出了“销毁”的信息。
|
最后的代码展示了在 `john` 变量被设为 `nil` 后 `Customer` 实例和 `CreditCard` 实例的析构器都打印出了“销毁”的信息。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
> 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如,出于性能方面的原因),Swift 还提供了不安全的无主引用。与所有不安全的操作一样,你需要负责检查代码以确保其安全性。
|
> 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如,出于性能方面的原因),Swift 还提供了不安全的无主引用。与所有不安全的操作一样,你需要负责检查代码以确保其安全性。
|
||||||
> 你可以通过 `unowned(unsafe)` 来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。
|
> 你可以通过 `unowned(unsafe)` 来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。
|
||||||
|
|
||||||
<a name="unowned_references_and_implicitly_unwrapped_optional_properties"></a>
|
<a name="unowned_references_and_implicitly_unwrapped_optional_properties"></a>
|
||||||
### 无主引用和隐式解析可选属性
|
### 无主引用和隐式解包可选值属性
|
||||||
|
|
||||||
上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
|
上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
|
||||||
|
|
||||||
@ -337,7 +337,7 @@ john = nil
|
|||||||
|
|
||||||
`Customer` 和 `CreditCard` 的例子展示了一个属性的值允许为 `nil`,而另一个属性的值不允许为 `nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
|
`Customer` 和 `CreditCard` 的例子展示了一个属性的值允许为 `nil`,而另一个属性的值不允许为 `nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
|
||||||
|
|
||||||
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 `nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。
|
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 `nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。
|
||||||
|
|
||||||
这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。
|
这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。
|
||||||
|
|
||||||
@ -363,13 +363,13 @@ class City {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
为了建立两个类的依赖关系,`City` 的构造函数接受一个 `Country` 实例作为参数,并且将实例保存到 `country` 属性。
|
为了建立两个类的依赖关系,`City` 的构造器接受一个 `Country` 实例作为参数,并且将实例保存到 `country` 属性。
|
||||||
|
|
||||||
`Country` 的构造函数调用了 `City` 的构造函数。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造函数才能把 `self` 传给 `City` 的构造函数。在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述。
|
`Country` 的构造器调用了 `City` 的构造器。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造器才能把 `self` 传给 `City` 的构造器。在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述。
|
||||||
|
|
||||||
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country` 的 `capitalCity` 属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要展开它的值就能访问它。在[隐式解析可选类型](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述。
|
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country` 的 `capitalCity` 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要展开它的值就能访问它。在[隐式解包可选值](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述。
|
||||||
|
|
||||||
由于 `capitalCity` 默认值为 `nil`,一旦 `Country` 的实例在构造函数中给 `name` 属性赋值后,整个初始化过程就完成了。这意味着一旦 `name` 属性被赋值后,`Country` 的构造函数就能引用并传递隐式的 `self`。`Country` 的构造函数在赋值 `capitalCity` 时,就能将 `self` 作为参数传递给 `City` 的构造函数。
|
由于 `capitalCity` 默认值为 `nil`,一旦 `Country` 的实例在构造器中给 `name` 属性赋值后,整个初始化过程就完成了。这意味着一旦 `name` 属性被赋值后,`Country` 的构造器就能引用并传递隐式的 `self`。`Country` 的构造器在赋值 `capitalCity` 时,就能将 `self` 作为参数传递给 `City` 的构造器。
|
||||||
|
|
||||||
以上的意义在于你可以通过一条语句同时创建 `Country` 和 `City` 的实例,而不产生循环强引用,并且 `capitalCity` 的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
|
以上的意义在于你可以通过一条语句同时创建 `Country` 和 `City` 的实例,而不产生循环强引用,并且 `capitalCity` 的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
|
||||||
|
|
||||||
@ -379,7 +379,7 @@ print("\(country.name)'s capital city is called \(country.capitalCity.name)")
|
|||||||
// 打印 "Canada's capital city is called Ottawa"
|
// 打印 "Canada's capital city is called Ottawa"
|
||||||
```
|
```
|
||||||
|
|
||||||
在上面的例子中,使用隐式解析可选值意味着满足了类的构造函数的两个构造阶段的要求。`capitalCity` 属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
|
在上面的例子中,使用隐式解包可选值值意味着满足了类的构造器的两个构造阶段的要求。`capitalCity` 属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
|
||||||
|
|
||||||
<a name="strong_reference_cycles_for_closures"></a>
|
<a name="strong_reference_cycles_for_closures"></a>
|
||||||
## 闭包的循环强引用
|
## 闭包的循环强引用
|
||||||
@ -444,7 +444,7 @@ print(heading.asHTML())
|
|||||||
>
|
>
|
||||||
> `asHTML` 声明为 `lazy` 属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用 `asHTML`。也就是说,在默认的闭包中可以使用 `self`,因为只有当初始化完成以及 `self` 确实存在后,才能访问 `lazy` 属性。
|
> `asHTML` 声明为 `lazy` 属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用 `asHTML`。也就是说,在默认的闭包中可以使用 `self`,因为只有当初始化完成以及 `self` 确实存在后,才能访问 `lazy` 属性。
|
||||||
|
|
||||||
`HTMLElement` 类只提供了一个构造函数,通过 `name` 和 `text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构函数,当 `HTMLElement` 实例被销毁时,打印一条消息。
|
`HTMLElement` 类只提供了一个构造器,通过 `name` 和 `text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构器,当 `HTMLElement` 实例被销毁时,打印一条消息。
|
||||||
|
|
||||||
下面的代码展示了如何用 `HTMLElement` 类创建实例并打印消息:
|
下面的代码展示了如何用 `HTMLElement` 类创建实例并打印消息:
|
||||||
|
|
||||||
@ -460,7 +460,7 @@ print(paragraph!.asHTML())
|
|||||||
|
|
||||||
不幸的是,上面写的 `HTMLElement` 类产生了类实例和作为 `asHTML` 默认值的闭包之间的循环强引用。循环强引用如下图所示:
|
不幸的是,上面写的 `HTMLElement` 类产生了类实例和作为 `asHTML` 默认值的闭包之间的循环强引用。循环强引用如下图所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name` 和 `self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。
|
实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name` 和 `self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。
|
||||||
|
|
||||||
@ -474,7 +474,7 @@ print(paragraph!.asHTML())
|
|||||||
paragraph = nil
|
paragraph = nil
|
||||||
```
|
```
|
||||||
|
|
||||||
注意,`HTMLElement` 的析构函数中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。
|
注意,`HTMLElement` 的析构器中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。
|
||||||
|
|
||||||
<a name="resolving_strong_reference_cycles_for_closures"></a>
|
<a name="resolving_strong_reference_cycles_for_closures"></a>
|
||||||
## 解决闭包的循环强引用
|
## 解决闭包的循环强引用
|
||||||
@ -560,9 +560,9 @@ print(paragraph!.asHTML())
|
|||||||
|
|
||||||
使用捕获列表后引用关系如下图所示:
|
使用捕获列表后引用关系如下图所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
这一次,闭包以无主引用的形式捕获 `self`,并不会持有 `HTMLElement` 实例的强引用。如果将 `paragraph` 赋值为 `nil`,`HTMLElement` 实例将会被销毁,并能看到它的析构函数打印出的消息:
|
这一次,闭包以无主引用的形式捕获 `self`,并不会持有 `HTMLElement` 实例的强引用。如果将 `paragraph` 赋值为 `nil`,`HTMLElement` 实例将会被销毁,并能看到它的析构器打印出的消息:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
paragraph = nil
|
paragraph = nil
|
||||||
|
|||||||
@ -322,9 +322,9 @@ public struct TrackedString {
|
|||||||
<a name="protocols"></a>
|
<a name="protocols"></a>
|
||||||
## 协议
|
## 协议
|
||||||
|
|
||||||
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。
|
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被遵循。
|
||||||
|
|
||||||
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。
|
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意遵循者都将可用。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
@ -336,17 +336,17 @@ public struct TrackedString {
|
|||||||
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
|
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
|
||||||
|
|
||||||
<a name="protocol_conformance"></a>
|
<a name="protocol_conformance"></a>
|
||||||
### 协议一致性
|
### 协议遵循
|
||||||
|
|
||||||
一个类型可以采纳比自身访问级别低的协议。例如,你可以定义一个 `public` 级别的类型,它可以在其他模块中使用,同时它也可以采纳一个 `internal` 级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。
|
一个类型可以遵循比它级别更低的协议。例如,你可以定义一个 `public` 级别类型,它能在别的模块中使用,但是如果它遵循一个 `internal` 协议,这个遵循的部分就只能在这个 `internal` 协议所在的模块中使用。
|
||||||
|
|
||||||
采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。也就是说如果一个类型是 `public` 级别,采纳的协议是 `internal` 级别,那么采纳了这个协议后,该类型作为符合协议的类型时,其访问级别也是 `internal`。
|
遵循协议时的上下文级别是类型和协议中级别最小的那个。如果一个类型是 `public` 级别,但它要遵循的协议是 `internal` 级别,那么这个类型对该协议的遵循上下文就是 `internal` 级别。
|
||||||
|
|
||||||
如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 `public` 级别的类型,采纳了 `internal` 级别的协议,那么协议的实现至少也得是 `internal` 级别。
|
当你编写或扩展一个类型让它遵循一个协议时,你必须确保该类型对协议的每一个要求的实现,至少与遵循协议的上下文级别一致。例如,一个 `public` 类型遵循一个 `internal` 协议,这个类型对协议的所有实现至少都应是 `internal` 级别的。
|
||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
|
> Swift 和 Objective-C 一样,协议遵循是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
|
||||||
|
|
||||||
<a name="extensions"></a>
|
<a name="extensions"></a>
|
||||||
## Extension
|
## Extension
|
||||||
@ -374,7 +374,7 @@ protocol SomeProtocol {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
你可以使用 extension 来遵守协议,就想这样:
|
你可以使用 extension 来遵循协议,就像这样:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
struct SomeStruct {
|
struct SomeStruct {
|
||||||
@ -400,4 +400,4 @@ extension SomeStruct: SomeProtocol {
|
|||||||
|
|
||||||
> 注意
|
> 注意
|
||||||
>
|
>
|
||||||
> 这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。
|
> 这条规则也适用于为满足协议遵循而将类型别名用于关联类型的情况。
|
||||||
|
|||||||
Reference in New Issue
Block a user