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 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
|
||||
if assumedString != nil {
|
||||
print(assumedString)
|
||||
print(assumedString!)
|
||||
}
|
||||
// 输出 "An implicitly unwrapped optional string."
|
||||
```
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `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`,这方便我们表达一个区间内的数值。
|
||||
|
||||
@ -38,7 +38,7 @@ let (x, y) = (1, 2)
|
||||
// 现在 x 等于 1,y 等于 2
|
||||
```
|
||||
|
||||
与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下陈述时无效的:
|
||||
与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以下面语句是无效的:
|
||||
|
||||
```swift
|
||||
if x = y {
|
||||
@ -46,7 +46,7 @@ if x = y {
|
||||
}
|
||||
```
|
||||
|
||||
这个特性使你无法把(`==`)错写成(`=`),由于 `if x = y` 是无效的,Swift 能帮你避免此类错误发生。
|
||||
通过将 `if x = y` 标记为无效语句,Swift 能帮你避免把 (`==`)错写成(`=`)这类错误的出现。
|
||||
|
||||
<a name="arithmetic_operators"></a>
|
||||
## 算术运算符
|
||||
@ -264,12 +264,12 @@ if hasHeader {
|
||||
|
||||
第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。
|
||||
|
||||
三元运算提供有效率且便捷的方式来表达二选一的选择。需要注意的事,过度使用三元运算符会使简洁的代码变的难懂。我们应避免在一个组合语句中使用多个三元运算符。
|
||||
三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。
|
||||
|
||||
<a name="nil_coalescing_operator"></a>
|
||||
## 空合运算符(Nil Coalescing Operator)
|
||||
|
||||
*空合运算符*(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解封,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
|
||||
*空合运算符*(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解包,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
|
||||
|
||||
空合运算符是对以下代码的简短表达方法:
|
||||
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
# 字符串和字符
|
||||
|
||||
*字符串*是是一系列字符的集合,例如 `"hello, world"`,`"albatross"`。Swift 的字符串通过 `String` 类型来表示。
|
||||
一个 `String` 的内容可以用许多方式读取,包括作为一个 `Character` 值的集合。
|
||||
*字符串*是是一系列字符的集合,例如 `"hello, world"`,`"albatross"`。Swift 的字符串通过 `String` 类型来表示。而 `String` 内容的访问方式有多种,例如以 `Character` 值的集合。
|
||||
|
||||
Swift 的 `String` 和 `Character` 类型提供了快速和兼容 Unicode 的方式供你的代码使用。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。字符串连接操作只需要简单地通过 `+` 符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。
|
||||
Swift 的 `String` 和 `Character` 类型提供了一种快速且兼容 Unicode 的方式来处理代码中的文本内容。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。通过 `+` 符号就可以非常简单的实现两个字符串的拼接操作。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你可以在已有字符串中插入常量、变量、字面量和表达式从而形成更长的字符串,这一过程也被成为字符串插值。尤其是在为显示、存储和打印创建自定义字符串值时,字符串插值操作尤其有用。
|
||||
|
||||
尽管语法简易,但 `String` 类型是一种快速、现代化的字符串实现。
|
||||
每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。
|
||||
尽管语法简易,但 Swift 中的 `String` 类型的实现却很快速和现代化。每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `String` 类型与 Foundation `NSString` 类进行了无缝桥接。Foundation 也可以对 `String` 进行扩展,暴露在 `NSString` 中定义的方法。 这意味着,如果你在 `String` 中调用这些 `NSString` 的方法,将不用进行转换。
|
||||
> Swift 的 `String` 类型与 Foundation `NSString` 类进行了无缝桥接。Foundation 还对 `String` 进行扩展使其可以访问 `NSString` 类型中定义的方法。这意味着调用那些 `NSString` 的方法,你无需进行任何类型转换。
|
||||
>
|
||||
> 更多关于在 Foundation 和 Cocoa 中使用 `String` 的信息请查看 *[Using Swift with Cocoa and Objective-C (Swift 4)](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)*。
|
||||
|
||||
@ -25,7 +23,7 @@ Swift 的 `String` 和 `Character` 类型提供了快速和兼容 Unicode 的方
|
||||
let someString = "Some string literal value"
|
||||
```
|
||||
|
||||
注意 `someString` 常量通过字符串字面量进行初始化,Swift 会推断该常量为 `String` 类型。
|
||||
注意,Swift 之所以推断 `someString` 常量为字符串类型,是因为它使用了字面量方式进行初始化。
|
||||
|
||||
<a name="multiline_string_literals"></a>
|
||||
### 多行字符串字面量
|
||||
@ -120,7 +118,7 @@ var anotherEmptyString = String() // 初始化方法
|
||||
// 两个字符串均为空并等价。
|
||||
```
|
||||
|
||||
您可以通过检查其 `Bool` 类型的 `isEmpty` 属性来判断该字符串是否为空:
|
||||
你可以通过检查 `Bool` 类型的 `isEmpty` 属性来判断该字符串是否为空:
|
||||
|
||||
```swift
|
||||
if emptyString.isEmpty {
|
||||
@ -132,7 +130,7 @@ if emptyString.isEmpty {
|
||||
<a name="string_mutability"></a>
|
||||
## 字符串可变性
|
||||
|
||||
您可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
|
||||
你可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
|
||||
|
||||
```swift
|
||||
var variableString = "Horse"
|
||||
@ -146,26 +144,21 @@ constantString += " and another Highlander"
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在 Objective-C 和 Cocoa 中,您需要通过选择两个不同的类(`NSString` 和 `NSMutableString`)来指定字符串是否可以被修改。
|
||||
> 在 Objective-C 和 Cocoa 中,需要通过选择两个不同的类(`NSString` 和 `NSMutableString`)来指定字符串是否可以被修改。
|
||||
|
||||
<a name="strings_are_value_types"></a>
|
||||
## 字符串是值类型
|
||||
|
||||
Swift 的 `String` 类型是*值类型*。
|
||||
如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。
|
||||
任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。
|
||||
值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types) 中进行了详细描述。
|
||||
在 Swift 中 `String` 类型是*值类型*。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types) 中进行了详细描述。
|
||||
|
||||
Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值。
|
||||
很明显无论该值来自于哪里,都是您独自拥有的。
|
||||
您可以确信传递的字符串不会被修改,除非你自己去修改它。
|
||||
Swift 默认拷贝字符串的行为保证了在函数/方法向你传递的字符串所属权属于你,无论该值来自于哪里。你可以确信传递的字符串不会被修改,除非你自己去修改它。
|
||||
|
||||
在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。
|
||||
在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着你将字符串作为值类型的同时可以获得极高的性能。
|
||||
|
||||
<a name="working_with_characters"></a>
|
||||
## 使用字符
|
||||
|
||||
您可通过 `for-in` 循环来遍历字符串,获取字符串中每一个字符的值:
|
||||
你可通过 `for-in` 循环来遍历字符串,获取字符串中每一个字符的值:
|
||||
|
||||
```swift
|
||||
for character in "Dog!🐶" {
|
||||
@ -207,7 +200,7 @@ var welcome = string1 + string2
|
||||
// welcome 现在等于 "hello there"
|
||||
```
|
||||
|
||||
您也可以通过加法赋值运算符(`+=`)将一个字符串添加到一个已经存在字符串变量上:
|
||||
你也可以通过加法赋值运算符(`+=`)将一个字符串添加到一个已经存在字符串变量上:
|
||||
|
||||
```swift
|
||||
var instruction = "look over"
|
||||
@ -215,7 +208,7 @@ instruction += string2
|
||||
// instruction 现在等于 "look over there"
|
||||
```
|
||||
|
||||
您可以用 `append()` 方法将一个字符附加到一个字符串变量的尾部:
|
||||
你可以用 `append()` 方法将一个字符附加到一个字符串变量的尾部:
|
||||
|
||||
```swift
|
||||
let exclamationMark: Character = "!"
|
||||
@ -225,7 +218,7 @@ welcome.append(exclamationMark)
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 您不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
|
||||
> 你不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
|
||||
|
||||
如果你需要使用多行字符串字面量来拼接字符串,并且你需要字符串每一行都以换行符结尾,包括最后一行:
|
||||
|
||||
@ -259,8 +252,7 @@ print(goodStart + end)
|
||||
<a name="string_interpolation"></a>
|
||||
## 字符串插值
|
||||
|
||||
*字符串插值*是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。**字符串字面量**和**多行字符串字面量**都可以使用字符串插值。
|
||||
您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
|
||||
*字符串插值*是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。**字符串字面量**和**多行字符串字面量**都可以使用字符串插值。你插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
|
||||
|
||||
```swift
|
||||
let multiplier = 3
|
||||
@ -268,12 +260,9 @@ let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
|
||||
// message 是 "3 times 2.5 is 7.5"
|
||||
```
|
||||
|
||||
在上面的例子中,`multiplier` 作为 `\(multiplier)` 被插入到一个字符串常量量中。
|
||||
当创建字符串执行插值计算时此占位符会被替换为 `multiplier` 实际的值。
|
||||
在上面的例子中,`multiplier` 作为 `\(multiplier)` 被插入到一个字符串常量量中。当创建字符串执行插值计算时此占位符会被替换为 `multiplier` 实际的值。
|
||||
|
||||
`multiplier` 的值也作为字符串中后面表达式的一部分。
|
||||
该表达式计算 `Double(multiplier) * 2.5` 的值并将结果(`7.5`)插入到字符串中。
|
||||
在这个例子中,表达式写为 `\(Double(multiplier) * 2.5)` 并包含在字符串字面量中。
|
||||
`multiplier` 的值也作为字符串中后面表达式的一部分。该表达式计算 `Double(multiplier) * 2.5` 的值并将结果(`7.5`)插入到字符串中。在这个例子中,表达式写为 `\(Double(multiplier) * 2.5)` 并包含在字符串字面量中。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -282,34 +271,24 @@ let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
|
||||
<a name="unicode"></a>
|
||||
## Unicode
|
||||
|
||||
*Unicode*是一个国际标准,用于文本的编码和表示。
|
||||
它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。
|
||||
Swift 的 `String` 和 `Character` 类型是完全兼容 Unicode 标准的。
|
||||
*Unicode*是一个用于在不同书写系统中对文本进行编码、表示和处理的国际标准。它使你可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。Swift 的 `String` 和 `Character` 类型是完全兼容 Unicode 标准的。
|
||||
|
||||
<a name="unicode_scalars"></a>
|
||||
### Unicode 标量
|
||||
|
||||
Swift 的 `String` 类型是基于 *Unicode 标量* 建立的。
|
||||
Unicode 标量是对应字符或者修饰符的唯一的21位数字,例如 `U+0061` 表示小写的拉丁字母(`LATIN SMALL LETTER A`)("`a`"),`U+1F425` 表示小鸡表情(`FRONT-FACING BABY CHICK`)("`🐥`")。
|
||||
Swift 的 `String` 类型是基于 *Unicode 标量* 建立的。Unicode 标量是对应字符或者修饰符的唯一的 21 位数字,例如 `U+0061` 表示小写的拉丁字母(`LATIN SMALL LETTER A`)("`a`"),`U+1F425` 表示小鸡表情(`FRONT-FACING BABY CHICK`)("`🐥`")。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Unicode *码位(code poing)* 的范围是 `U+0000` 到 `U+D7FF` 或者 `U+E000` 到 `U+10FFFF`。Unicode 标量不包括 Unicode *代理项(surrogate pair)* 码位,其码位范围是 `U+D800` 到 `U+DFFF`。
|
||||
|
||||
注意不是所有的21位 Unicode 标量都代表一个字符,因为有一些标量是留作未来分配的。已经代表一个典型字符的标量都有自己的名字,例如上面例子中的 `LATIN SMALL LETTER A` 和 `FRONT-FACING BABY CHICK`。
|
||||
|
||||
<a name="special_characters_in_string_literals"></a>
|
||||
请注意,并非所有 21 位 Unicode 标量值都分配给字符,某些标量被保留用于将来分配或用于 UTF-16 编码。已分配的标量值通常也有一个名称,例如上面示例中的 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK。
|
||||
|
||||
<a name="extended_grapheme_clusters"></a>
|
||||
### 可扩展的字形群集
|
||||
|
||||
每一个 Swift 的 `Character` 类型代表一个*可扩展的字形群*。
|
||||
一个可扩展的字形群是一个或多个可生成人类可读的字符 Unicode 标量的有序排列。
|
||||
每一个 Swift 的 `Character` 类型代表一个*可扩展的字形群*。而一个可扩展的字形群构成了人类可读的单个字符,它由一个或多个(当组合时) Unicode 标量的序列组成。
|
||||
|
||||
举个例子,字母 `é` 可以用单一的 Unicode 标量 `é`(`LATIN SMALL LETTER E WITH ACUTE`, 或者 `U+00E9`)来表示。然而一个标准的字母 `e`(`LATIN SMALL LETTER E` 或者 `U+0065`) 加上一个急促重音(`COMBINING ACTUE ACCENT`)的标量(`U+0301`),这样一对标量就表示了同样的字母 `é`。
|
||||
这个急促重音的标量形象的将 `e` 转换成了 `é`。
|
||||
|
||||
在这两种情况中,字母 `é` 代表了一个单一的 Swift 的 `Character` 值,同时代表了一个可扩展的字形群。
|
||||
在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
|
||||
在这两种情况中,字母 `é` 代表了一个单一的 Swift 的 `Character` 值,同时代表了一个可扩展的字形群。在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
|
||||
|
||||
```swift
|
||||
let eAcute: Character = "\u{E9}" // é
|
||||
@ -317,9 +296,7 @@ let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
|
||||
// eAcute 是 é, combinedEAcute 是 é
|
||||
```
|
||||
|
||||
可扩展的字符群集是一个灵活的方法,用许多复杂的脚本字符表示单一的 `Character` 值。
|
||||
例如,来自朝鲜语字母表的韩语音节能表示为组合或分解的有序排列。
|
||||
在 Swift 都会表示为同一个单一的 `Character` 值:
|
||||
可扩展的字形集是一个将许多复杂的脚本字符表示为单个字符值的灵活方式。例如,来自朝鲜语字母表的韩语音节能表示为组合或分解的有序排列。在 Swift 都会表示为同一个单一的 `Character` 值:
|
||||
|
||||
```swift
|
||||
let precomposed: Character = "\u{D55C}" // 한
|
||||
@ -369,7 +346,7 @@ print("the number of characters in \(word) is \(word.count)")
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果您正在处理一个长字符串,需要注意 `count` 属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
|
||||
> 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果你正在处理一个长字符串,需要注意 `count` 属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
|
||||
>
|
||||
> 另外需要注意的是通过 `count` 属性返回的字符数量并不总是与包含相同字符的 `NSString` 的 `length` 属性相同。`NSString` 的 `length` 属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。
|
||||
|
||||
@ -387,7 +364,7 @@ print("the number of characters in \(word) is \(word.count)")
|
||||
|
||||
使用 `startIndex` 属性可以获取一个 `String` 的第一个 `Character` 的索引。使用 `endIndex` 属性可以获取最后一个 `Character` 的后一个位置的索引。因此,`endIndex` 属性不能作为一个字符串的有效下标。如果 `String` 是空串,`startIndex` 和 `endIndex` 是相等的。
|
||||
|
||||
通过调用 `String` 的 `index(before:)` 或 `index(after:)` 方法,可以立即得到前面或后面的一个索引。您还可以通过调用 `index(_:offsetBy:)` 方法来获取对应偏移量的索引,这种方式可以避免多次调用 `index(before:)` 或 `index(after:)` 方法。
|
||||
通过调用 `String` 的 `index(before:)` 或 `index(after:)` 方法,可以立即得到前面或后面的一个索引。你还可以通过调用 `index(_:offsetBy:)` 方法来获取对应偏移量的索引,这种方式可以避免多次调用 `index(before:)` 或 `index(after:)` 方法。
|
||||
|
||||
你可以使用下标语法来访问 `String` 特定索引的 `Character`。
|
||||
|
||||
@ -422,7 +399,7 @@ for index in greeting.indices {
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 您可以使用 `startIndex` 和 `endIndex` 属性或者 `index(before:)` 、`index(after:)` 和 `index(_:offsetBy:)` 方法在任意一个确认的并遵循 `Collection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
|
||||
> 你可以使用 `startIndex` 和 `endIndex` 属性或者 `index(before:)` 、`index(after:)` 和 `index(_:offsetBy:)` 方法在任意一个确认的并遵循 `Collection` 协议的类型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
|
||||
|
||||
<a name="inserting_and_removing"></a>
|
||||
### 插入和删除
|
||||
@ -451,7 +428,7 @@ welcome.removeSubrange(range)
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 您可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
|
||||
> 你可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
|
||||
|
||||
<a name="substrings"></a>
|
||||
## 子字符串
|
||||
@ -497,7 +474,7 @@ if quotation == sameQuotation {
|
||||
// 打印输出 "These two strings are considered equal"
|
||||
```
|
||||
|
||||
如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等的,那就认为它们是相等的。在这个情况下,即使可扩展的字形群集是有不同的 Unicode 标量构成的,只要它们有同样的语言意义和外观,就认为它们标准相等。
|
||||
如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等,那就认为它们是相等的。只要可扩展的字形群集有同样的语言意义和外观则认为它们标准相等,即使它们是由不同的 Unicode 标量构成。
|
||||
|
||||
例如,`LATIN SMALL LETTER E WITH ACUTE`(`U+00E9`)就是标准相等于 `LATIN SMALL LETTER E`(`U+0065`)后面加上 `COMBINING ACUTE ACCENT`(`U+0301`)。这两个字符群集都是表示字符 `é` 的有效方式,所以它们被认为是标准相等的:
|
||||
|
||||
@ -554,7 +531,7 @@ let romeoAndJuliet = [
|
||||
]
|
||||
```
|
||||
|
||||
您可以调用 `hasPrefix(_:)` 方法来计算话剧中第一幕的场景数:
|
||||
你可以调用 `hasPrefix(_:)` 方法来计算话剧中第一幕的场景数:
|
||||
|
||||
```swift
|
||||
var act1SceneCount = 0
|
||||
@ -567,7 +544,7 @@ print("There are \(act1SceneCount) scenes in Act 1")
|
||||
// 打印输出 "There are 5 scenes in Act 1"
|
||||
```
|
||||
|
||||
相似地,您可以用 `hasSuffix(_:)` 方法来计算发生在不同地方的场景数:
|
||||
相似地,你可以用 `hasSuffix(_:)` 方法来计算发生在不同地方的场景数:
|
||||
|
||||
```swift
|
||||
var mansionCount = 0
|
||||
@ -590,17 +567,15 @@ print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
|
||||
<a name="unicode_representations_of_strings"></a>
|
||||
## 字符串的 Unicode 表示形式
|
||||
|
||||
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种 `编码格式`(encoding forms)编码。每一个字符串中的小块编码都被称 `代码单元`(code units)。这些包括 UTF-8 编码格式(编码字符串为8位的代码单元), UTF-16 编码格式(编码字符串位16位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。
|
||||
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种 `编码格式`(encoding forms)编码。每一个字符串中的小块编码都被称 `代码单元`(code units)。这些包括 UTF-8 编码格式(编码字符串为 8 位的代码单元), UTF-16 编码格式(编码字符串位 16 位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。
|
||||
|
||||
Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。
|
||||
您可以利用 `for-in` 来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个 `Character` 值。
|
||||
该过程在 [使用字符](#working_with_characters) 中进行了描述。
|
||||
Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。你可以利用 `for-in` 来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个 `Character` 值。该过程在 [使用字符](#working_with_characters) 中进行了描述。
|
||||
|
||||
另外,能够以其他三种 Unicode 兼容的方式访问字符串的值:
|
||||
|
||||
* UTF-8 代码单元集合(利用字符串的 `utf8` 属性进行访问)
|
||||
* UTF-16 代码单元集合(利用字符串的 `utf16` 属性进行访问)
|
||||
* 21位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式(利用字符串的 `unicodeScalars` 属性进行访问)
|
||||
* 21 位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式(利用字符串的 `unicodeScalars` 属性进行访问)
|
||||
|
||||
下面由 `D`,`o`,`g`,`‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和 `🐶`(`DOG FACE`,Unicode 标量为 `U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
|
||||
|
||||
@ -611,8 +586,7 @@ let dogString = "Dog‼🐶"
|
||||
<a name="UTF-8_representation"></a>
|
||||
### UTF-8 表示
|
||||
|
||||
您可以通过遍历 `String` 的 `utf8` 属性来访问它的 `UTF-8` 表示。
|
||||
其为 `String.UTF8View` 类型的属性,`UTF8View` 是无符号8位(`UInt8`)值的集合,每一个 `UInt8` 值都是一个字符的 UTF-8 表示:
|
||||
你可以通过遍历 `String` 的 `utf8` 属性来访问它的 `UTF-8` 表示。其为 `String.UTF8View` 类型的属性,`UTF8View` 是无符号 8 位(`UInt8`)值的集合,每一个 `UInt8` 值都是一个字符的 UTF-8 表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
@ -659,15 +633,12 @@ print("")
|
||||
// 68 111 103 226 128 188 240 159 144 182
|
||||
```
|
||||
|
||||
上面的例子中,前三个10进制 `codeUnit` 值(`68`、`111`、`103`)代表了字符 `D`、`o` 和 `g`,它们的 UTF-8 表示与 ASCII 表示相同。
|
||||
接下来的三个10进制 `codeUnit` 值(`226`、`128`、`188`)是 `DOUBLE EXCLAMATION MARK` 的3字节 UTF-8 表示。
|
||||
最后的四个 `codeUnit` 值(`240`、`159`、`144`、`182`)是 `DOG FACE` 的4字节 UTF-8 表示。
|
||||
上面的例子中,前三个 10 进制 `codeUnit` 值(`68`、`111`、`103`)代表了字符 `D`、`o` 和 `g`,它们的 UTF-8 表示与 ASCII 表示相同。接下来的三个 10 进制 `codeUnit` 值(`226`、`128`、`188`)是 `DOUBLE EXCLAMATION MARK` 的3字节 UTF-8 表示。最后的四个 `codeUnit` 值(`240`、`159`、`144`、`182`)是 `DOG FACE` 的4字节 UTF-8 表示。
|
||||
|
||||
<a name="UTF-16_representation"></a>
|
||||
### UTF-16 表示
|
||||
|
||||
您可以通过遍历 `String` 的 `utf16` 属性来访问它的 `UTF-16` 表示。
|
||||
其为 `String.UTF16View` 类型的属性,`UTF16View` 是无符号16位(`UInt16`)值的集合,每一个 `UInt16` 都是一个字符的 UTF-16 表示:
|
||||
你可以通过遍历 `String` 的 `utf16` 属性来访问它的 `UTF-16` 表示。其为 `String.UTF16View` 类型的属性,`UTF16View` 是无符号16位(`UInt16`)值的集合,每一个 `UInt16` 都是一个字符的 UTF-16 表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
@ -710,17 +681,14 @@ print("")
|
||||
|
||||
第四个 `codeUnit` 值(`8252`)是一个等于十六进制 `203C` 的的十进制值。这个代表了 `DOUBLE EXCLAMATION MARK` 字符的 Unicode 标量值 `U+203C`。这个字符在 UTF-16 中可以用一个代码单元表示。
|
||||
|
||||
第五和第六个 `codeUnit` 值(`55357` 和 `56374`)是 `DOG FACE` 字符的 UTF-16 表示。
|
||||
第一个值为 `U+D83D`(十进制值为 `55357`),第二个值为 `U+DC36`(十进制值为 `56374`)。
|
||||
第五和第六个 `codeUnit` 值(`55357` 和 `56374`)是 `DOG FACE` 字符的 UTF-16 表示。第一个值为 `U+D83D`(十进制值为 `55357`),第二个值为 `U+DC36`(十进制值为 `56374`)。
|
||||
|
||||
<a name="unicode_scalars_representation"></a>
|
||||
### Unicode 标量表示
|
||||
|
||||
您可以通过遍历 `String` 值的 `unicodeScalars` 属性来访问它的 Unicode 标量表示。
|
||||
其为 `UnicodeScalarView` 类型的属性,`UnicodeScalarView` 是 `UnicodeScalar` 类型的值的集合。
|
||||
`UnicodeScalar` 是21位的 Unicode 代码点。
|
||||
你可以通过遍历 `String` 值的 `unicodeScalars` 属性来访问它的 Unicode 标量表示。其为 `UnicodeScalarView` 类型的属性,`UnicodeScalarView` 是 `UnicodeScalar` 类型的值的集合。
|
||||
|
||||
每一个 `UnicodeScalar` 拥有一个 `value` 属性,可以返回对应的21位数值,用 `UInt32` 来表示:
|
||||
每一个 `UnicodeScalar` 拥有一个 `value` 属性,可以返回对应的 21 位数值,用 `UInt32` 来表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
|
||||
@ -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 == b` 意味着 `b == a`(对称性)
|
||||
|
||||
@ -4,7 +4,7 @@ Swift 提供了多种流程控制结构,包括可以多次执行任务的 `whi
|
||||
|
||||
Swift 还提供了 `for-in` 循环,用来更简单地遍历数组(Array),字典(Dictionary),区间(Range),字符串(String)和其他序列类型。
|
||||
|
||||
Swift 的 `switch` 语句比 C 语言中更加强大。case 还可以匹配很多不同的模式,包括范围匹配,元组(tuple)和特定类型匹配。`switch` 语句的 case 中匹配的值可以声明为临时常量或变量,在 case 作用域内使用,也可以配合 `where` 来描述更复杂的匹配条件。
|
||||
Swift 的 `switch` 语句比许多类 C 语言要更加强大。case 还可以匹配很多不同的模式,包括范围匹配,元组(tuple)和特定类型匹配。`switch` 语句的 case 中匹配的值可以声明为临时常量或变量,在 case 作用域内使用,也可以配合 `where` 来描述更复杂的匹配条件。
|
||||
|
||||
<a name="for_in_loops"></a>
|
||||
## For-In 循环
|
||||
@ -70,7 +70,7 @@ print("\(base) to the power of \(power) is \(answer)")
|
||||
|
||||
这个例子计算 base 这个数的 power 次幂(本例中,是 `3` 的 `10` 次幂),从 `1`(`3` 的 `0` 次幂)开始做 `3` 的乘法, 进行 `10` 次,使用 `1` 到 `10` 的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号 `_` (替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。
|
||||
|
||||
在某些情况下,你可能不想使用闭区间,包括两个端点。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅[区间运算符](./02_Basic_Operators.html#range_operators)。
|
||||
在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅[区间运算符](./02_Basic_Operators.html#range_operators)。
|
||||
|
||||
```swift
|
||||
let minutes = 60
|
||||
@ -171,7 +171,7 @@ print("Game over!")
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果没有这个检测(`square < board.count`),`board[square]` 可能会越界访问 `board` 数组,导致错误。
|
||||
> 如果没有这个检测(`square < board.count`),`board[square]` 可能会越界访问 `board` 数组,导致运行时错误。
|
||||
|
||||
当本轮 `while` 循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为 `false`,此时游戏结束。
|
||||
|
||||
@ -331,17 +331,16 @@ default:
|
||||
// 输出 "The last letter of the alphabet"
|
||||
```
|
||||
|
||||
在这个例子中,第一个 case 分支用于匹配第一个英文字母 `a`,第二个 case 分支用于匹配最后一个字母 `z`。
|
||||
因为 `switch` 语句必须有一个 case 分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以 switch 语句使用 `default` 分支来匹配除了 `a` 和 `z` 外的所有值,这个分支保证了 swith 语句的完备性。
|
||||
在这个例子中,第一个 case 分支用于匹配第一个英文字母 `a`,第二个 case 分支用于匹配最后一个字母 `z`。因为 `switch` 语句必须有一个 case 分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以 switch 语句使用 `default` 分支来匹配除了 `a` 和 `z` 外的所有值,这个分支保证了 swith 语句的完备性。
|
||||
|
||||
<a name="no_implicit_fallthrough"></a>
|
||||
#### 不存在隐式的贯穿
|
||||
|
||||
与 C 和 Objective-C 中的 `switch` 语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止 `switch` 语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 `break` 语句。这使得 `switch` 语句更安全、更易用,也避免了因忘记写 `break` 语句而产生的错误。
|
||||
与 C 和 Objective-C 中的 `switch` 语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止 `switch` 语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 `break` 语句。这使得 `switch` 语句更安全、更易用,也避免了漏写 `break` 语句导致多个语言被执行的错误。
|
||||
|
||||
> 注意
|
||||
>
|
||||
虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。
|
||||
> 虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。
|
||||
|
||||
每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:
|
||||
|
||||
@ -515,6 +514,7 @@ default:
|
||||
```
|
||||
|
||||
这个 `switch` 语句中的第一个 case,匹配了英语中的五个小写元音字母。相似的,第二个 case 匹配了英语中所有的小写辅音字母。最终,`default` 分支匹配了其它所有字符。
|
||||
|
||||
复合匹配同样可以包含值绑定。复合匹配里所有的匹配模式,都必须包含相同的值绑定。并且每一个绑定都必须获取到相同类型的值。这保证了,无论复合匹配中的哪个模式发生了匹配,分支体内的代码,都能获取到绑定的值,并且绑定的值都有一样的类型。
|
||||
|
||||
```swift
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
# 闭包
|
||||
|
||||
*闭包*是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。
|
||||
*闭包*是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。
|
||||
|
||||
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为*包裹*常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你不熟悉捕获(capturing)这个概念也不用担心,你可以在[值捕获](#capturing_values)章节对其进行详细了解。
|
||||
> 如果你不熟悉捕获(capturing)这个概念也不用担心,在[值捕获](#capturing_values)章节有它更详细的介绍。
|
||||
|
||||
在[函数](./06_Functions.md)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
|
||||
在[函数](./06_Functions.md)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:
|
||||
|
||||
* 全局函数是一个有名字但不会捕获任何值的闭包
|
||||
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
|
||||
@ -24,14 +24,14 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进
|
||||
<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>
|
||||
### 排序方法
|
||||
|
||||
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,`sorted(by:)` 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 `sorted(by:)` 方法修改。
|
||||
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会基于你提供的排序闭包表达式的判断结果对数组中的值(类型确定)进行排序。一旦它完成排序过程,`sorted(by:)` 方法会返回一个与旧数组类型大小相同类型的新数组,该数组的元素有着正确的排序顺序。原数组不会被 `sorted(by:)` 方法修改。
|
||||
|
||||
下面的闭包表达式示例使用 `sorted(by:)` 方法对一个 `String` 类型的数组进行字母逆序排序。以下是初始数组:
|
||||
|
||||
@ -104,7 +104,9 @@ reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
|
||||
尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
|
||||
|
||||
<a name="implicit_returns_from_single_expression_closures"></a>
|
||||
### 单表达式闭包隐式返回
|
||||
|
||||
### 单表达式闭包的隐式返回
|
||||
|
||||
单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
|
||||
|
||||
```swift
|
||||
@ -129,7 +131,7 @@ reversedNames = names.sorted(by: { $0 > $1 } )
|
||||
<a name="operator_methods"></a>
|
||||
### 运算符方法
|
||||
|
||||
实际上还有一种更简短的方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断出你想使用大于号的字符串函数实现:
|
||||
实际上还有一种更*简短的*方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: >)
|
||||
@ -140,7 +142,7 @@ reversedNames = names.sorted(by: >)
|
||||
<a name="trailing_closures"></a>
|
||||
## 尾随闭包
|
||||
|
||||
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用*尾随闭包*来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
|
||||
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
|
||||
|
||||
```swift
|
||||
func someFunctionThatTakesAClosure(closure: () -> Void) {
|
||||
@ -158,7 +160,7 @@ someFunctionThatTakesAClosure() {
|
||||
}
|
||||
```
|
||||
|
||||
在[闭包表达式语法](#closure_expression_syntax)一节中作为 `sorted(by:)` 方法参数的字符串排序闭包可以改写为:
|
||||
在[闭包表达式语法](#closure_expression_syntax)上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted() { $0 > $1 }
|
||||
|
||||
@ -14,7 +14,6 @@ subscript(index: Int) -> Int {
|
||||
get {
|
||||
// 返回一个适当的 Int 类型的值
|
||||
}
|
||||
|
||||
set(newValue) {
|
||||
// 执行适当的赋值操作
|
||||
}
|
||||
@ -67,7 +66,7 @@ numberOfLegs["bird"] = 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` 实例:
|
||||
|
||||
@ -117,7 +116,7 @@ var matrix = Matrix(rows: 2, columns: 2)
|
||||
|
||||
上例中创建了一个 `Matrix` 实例来表示两行两列的矩阵。该 `Matrix` 实例的 `grid` 数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
|
||||
|
||||

|
||||

|
||||
|
||||
将 `row` 和 `column` 的值传入下标来为矩阵设值,下标的入参使用逗号分隔:
|
||||
|
||||
@ -128,7 +127,7 @@ matrix[1, 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` 的值是否在矩阵范围内:
|
||||
|
||||
|
||||
@ -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
|
||||
class Vehicle {
|
||||
@ -31,7 +31,7 @@ class Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
您可以用初始化语法创建一个 `Vehicle` 的新实例,即类名后面跟一个空括号:
|
||||
可以用初始化语法创建一个 `Vehicle` 的新实例,即类名后面跟一个空括号:
|
||||
|
||||
```swift
|
||||
let someVehicle = Vehicle()
|
||||
@ -44,7 +44,7 @@ print("Vehicle: \(someVehicle.description)")
|
||||
// 打印 "Vehicle: traveling at 0.0 miles per hour"
|
||||
```
|
||||
|
||||
`Vehicle` 类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要完善它从而能够描述一个更加具体类型的车辆。
|
||||
`Vehicle` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。
|
||||
|
||||
<a name="subclassing"></a>
|
||||
## 子类生成
|
||||
@ -59,7 +59,7 @@ class SomeClass: SomeSuperclass {
|
||||
}
|
||||
```
|
||||
|
||||
下一个例子,定义一个叫 `Bicycle` 的子类,继承成父类 `Vehicle`:
|
||||
下一个例子,定义了一个叫 `Bicycle` 的子类,继承自父类 `Vehicle`:
|
||||
|
||||
```swift
|
||||
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
|
||||
let bicycle = Bicycle()
|
||||
@ -110,9 +110,9 @@ print("Tandem: \(tandem.description)")
|
||||
<a name="overriding"></a>
|
||||
## 重写
|
||||
|
||||
子类可以为继承来的实例方法,类方法,实例属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。
|
||||
子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。
|
||||
|
||||
如果要重写某个特性,你需要在重写定义的前面加上 `override` 关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少 `override` 关键字的重写都会在编译时被诊断为错误。
|
||||
如果要重写某个特性,你需要在重写定义的前面加上 `override` 关键字。这么做,就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少 `override` 关键字的重写都会在编译时被认定为错误。
|
||||
|
||||
`override` 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
|
||||
|
||||
@ -150,11 +150,11 @@ train.makeNoise()
|
||||
|
||||
### 重写属性
|
||||
|
||||
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
|
||||
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器,使重写的属性可以观察到底层的属性值什么时候发生改变。
|
||||
|
||||
#### 重写属性的 Getters 和 Setters
|
||||
|
||||
你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
|
||||
你可以提供定制的 getter(或 setter)来重写任何一个继承来的属性,无论这个属性是存储型还是计算型属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必须将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
|
||||
|
||||
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
|
||||
|
||||
@ -188,14 +188,14 @@ print("Car: \(car.description)")
|
||||
<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 中就可以观察到任何值变化了。
|
||||
|
||||
下面的例子定义了一个新类叫 `AutomaticCar`,它是 `Car` 的子类。`AutomaticCar` 表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位:
|
||||
下面的例子定义了一个新类叫 `AutomaticCar`,它是 `Car` 的子类。`AutomaticCar` 表示自动档汽车,它可以根据当前的速度自动选择合适的档位:
|
||||
|
||||
```swift
|
||||
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
|
||||
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` 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final。
|
||||
|
||||
你可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||
可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||
|
||||
@ -1,26 +1,27 @@
|
||||
# 构造过程
|
||||
|
||||
*构造过程*是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。
|
||||
*构造过程*是使用类、结构体或枚举类型的实例之前的准备过程。在新实例使用前有个过程是必须的,它包括设置实例中每个存储属性的初始值和执行其他必须的设置或构造过程。
|
||||
|
||||
通过定义*构造器*来实现构造过程,就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
|
||||
你要通过定义*构造器*来实现构造过程,它就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器没有返回值。它们的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。
|
||||
|
||||
类的实例也可以通过定义*析构器*在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。
|
||||
类的实例也可以通过实现*析构器*来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。
|
||||
|
||||
<a name="setting_initial_values_for_stored_properties"></a>
|
||||
|
||||
## 存储属性的初始赋值
|
||||
|
||||
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
|
||||
|
||||
你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下小节将详细介绍这两种方法。
|
||||
你可以在构造器中为存储型属性设置初始值,也可以在定义属性时分配默认值。以下小节将详细介绍这两种方法。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。
|
||||
> 当你为存储型属性分配默认值或者在构造器中为设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
|
||||
|
||||
<a name="initializers"></a>
|
||||
### 构造器
|
||||
|
||||
构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字 `init` 命名:
|
||||
构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何形参的实例方法,以关键字 `init` 命名:
|
||||
|
||||
```swift
|
||||
init() {
|
||||
@ -42,7 +43,7 @@ print("The default temperature is \(f.temperature)° Fahrenheit")
|
||||
// 打印 "The default temperature is 32.0° Fahrenheit"
|
||||
```
|
||||
|
||||
这个结构体定义了一个不带参数的构造器 `init`,并在里面将存储型属性 `temperature` 的值初始化为 `32.0`(华氏温度下水的冰点)。
|
||||
这个结构体定义了一个不带形参的构造器 `init`,并在里面将存储型属性 `temperature` 的值初始化为 `32.0`(华氏温度下水的冰点)。
|
||||
|
||||
<a name="default_property_values"></a>
|
||||
### 默认属性值
|
||||
@ -51,9 +52,9 @@ print("The default temperature is \(f.temperature)° Fahrenheit")
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。
|
||||
> 如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的最终结果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。它能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。
|
||||
|
||||
你可以使用更简单的方式在定义结构体 `Fahrenheit` 时为属性 `temperature` 设置默认值:
|
||||
你可以通过在属性声明时为 `temperature` 提供默认值来使用更简单的方式定义结构体 `Fahrenheit` :
|
||||
|
||||
```swift
|
||||
struct Fahrenheit {
|
||||
@ -62,16 +63,17 @@ struct Fahrenheit {
|
||||
```
|
||||
|
||||
<a name="customizing_initialization"></a>
|
||||
|
||||
## 自定义构造过程
|
||||
|
||||
你可以通过输入参数和可选类型的属性来自定义构造过程,也可以在构造过程中给常量属性赋初值。这些都将在后面章节中提到。
|
||||
你可以通过输入形参和可选属性类型来自定义构造过程,也可以在构造过程中分配常量属性。这些都将在后面章节中提到。
|
||||
|
||||
<a name="initialization_parameters"></a>
|
||||
### 构造参数
|
||||
### 形参的构造过程
|
||||
|
||||
自定义构造过程时,可以在定义中提供*构造参数*,指定参数值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
|
||||
自定义构造过程时,可以在定义中提供*构造形参*,指定其值的类型和名字。构造形参的功能和语法跟函数和方法的形参相同。
|
||||
|
||||
下面例子中定义了一个包含摄氏度温度的结构体 `Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)` 和 `init(fromKelvin:)`,二者分别通过接受不同温标下的温度值来创建新的实例:
|
||||
下面例子中定义了一个用来保存摄氏温度的结构体 `Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)` 和 `init(fromKelvin:)`,二者分别通过接受不同温标下的温度值来创建新的实例:
|
||||
|
||||
```swift
|
||||
struct Celsius {
|
||||
@ -90,18 +92,18 @@ let freezingPointOfWater = Celsius(fromKelvin: 273.15)
|
||||
// freezingPointOfWater.temperatureInCelsius 是 0.0
|
||||
```
|
||||
|
||||
第一个构造器拥有一个构造参数,其外部名字为 `fromFahrenheit`,内部名字为 `fahrenheit`;第二个构造器也拥有一个构造参数,其外部名字为 `fromKelvin`,内部名字为 `kelvin`。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性 `temperatureInCelsius` 中。
|
||||
第一个构造器拥有一个构造形参,其实参标签为 `fromFahrenheit`,形参命名为 `fahrenheit`;第二个构造器也拥有一个构造形参,其实参标签为 `fromKelvin`,形参命名为 `kelvin`。这两个构造器都将单一的实参转换成摄氏温度值,并保存在属性 `temperatureInCelsius` 中。
|
||||
|
||||
<a name="parameter_names_and_argument_labels"></a>
|
||||
### 参数名和参数标签
|
||||
### 形参命名和实参标签
|
||||
|
||||
跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名和一个在调用构造器时使用的参数标签。
|
||||
跟函数和方法形参相同,构造形参可以同时使用在构造器里使用的形参命名和一个外部调用构造器时使用的实参标签。
|
||||
|
||||
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。因此在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数标签,Swift 会为构造器的*每个*参数自动生成一个参数标签。
|
||||
然而,构造器并不像函数和方法那样在括号前有一个可辨别的方法名。因此在调用构造器时,主要通过构造器中形参命名和类型来确定应该被调用的构造器。正因如此,如果你在定义构造器时没有提供实参标签,Swift 会为构造器的*每个*形参自动生成一个实参标签。
|
||||
|
||||
以下例子中定义了一个结构体 `Color`,它包含了三个常量:`red`、`green` 和 `blue`。这些属性可以存储 `0.0` 到 `1.0` 之间的值,用来指示颜色中红、绿、蓝成分的含量。
|
||||
以下例子中定义了一个结构体 `Color`,它包含了三个常量:`red`、`green` 和 `blue`。这些属性可以存储 `0.0` 到 `1.0` 之间的值,用来表明颜色中红、绿、蓝成分的含量。
|
||||
|
||||
`Color` 提供了一个构造器,其中包含三个 `Double` 类型的构造参数。`Color` 也提供了第二个构造器,它只包含名为 `white` 的 `Double` 类型的参数,它被用于给上述三个构造参数赋予同样的值。
|
||||
`Color` 提供了一个构造器,为红蓝绿提供三个合适 `Double` 类型的形参命名。`Color` 也提供了第二个构造器,它只包含名为 `white` 的 `Double` 类型的形参,它为三个颜色的属性提供相同的值。
|
||||
|
||||
```swift
|
||||
struct Color {
|
||||
@ -119,26 +121,26 @@ struct Color {
|
||||
}
|
||||
```
|
||||
|
||||
两种构造器都能通过提供的初始参数值来创建一个新的 `Color` 实例:
|
||||
两种构造器都能通过为每一个构造器形参提供命名值来创建一个新的 `Color` 实例:
|
||||
|
||||
```swift
|
||||
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
|
||||
let halfGray = Color(white: 0.5)
|
||||
```
|
||||
|
||||
注意,如果不通过参数标签传值,你是没法调用这个构造器的。只要构造器定义了某个参数标签,你就必须使用它,忽略它将导致编译错误:
|
||||
注意,如果不通过实参标签传值,这个构造器是没法调用的。如果构造器定义了某个实参标签,就必须使用它,忽略它将导致编译期错误:
|
||||
|
||||
```swift
|
||||
let veryGreen = Color(0.0, 1.0, 0.0)
|
||||
// 报编译时错误,需要外部名称
|
||||
// 报编译期错误-需要实参标签
|
||||
```
|
||||
|
||||
<a name="initializer_parameters_without_external_names"></a>
|
||||
### 不带参数标签的构造器参数
|
||||
### 不带实参标签的构造器形参
|
||||
|
||||
如果你不希望为构造器的某个参数提供参数标签,你可以使用下划线(`_`)来显式描述它的外部名,以此重写上面所说的默认行为。
|
||||
如果你不希望构造器的某个形参使用实参标签,可以使用下划线(`_`)来代替显式的实参标签来重写默认行为。
|
||||
|
||||
下面是之前 `Celsius` 例子的扩展,跟之前相比添加了一个带有 `Double` 类型参数的构造器,其外部名用 `_` 代替:
|
||||
下面是之前[形参的构造过程](#initialization_parameters)中 `Celsius` 例子的扩展,多了一个用已经的摄氏表示的 `Double` 类型值来创建新的 `Celsius` 实例的额外构造器:
|
||||
|
||||
```swift
|
||||
struct Celsius {
|
||||
@ -158,14 +160,15 @@ let bodyTemperature = Celsius(37.0)
|
||||
// bodyTemperature.temperatureInCelsius 为 37.0
|
||||
```
|
||||
|
||||
调用 `Celsius(37.0)` 意图明确,不需要参数标签。因此适合使用 `init(_ celsius: Double)` 这样的构造器,从而可以通过提供未命名的 `Double` 值调用构造器,而不需要加上参数标签。
|
||||
构造器调用 `Celsius(37.0)` 意图明确,不需要实参标签。因此适合使用 `init(_ celsius: Double)` 这样的构造器,从而可以通过提供未命名的 `Double` 值来调用构造器。
|
||||
|
||||
<a name="optional_property_types"></a>
|
||||
|
||||
### 可选属性类型
|
||||
|
||||
如果你定制的类型包含一个逻辑上允许取值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时间点可以赋值为空——你都需要将它定义为 `可选类型`。可选类型的属性将自动初始化为 `nil`,表示这个属性是有意在初始化时设置为空的。
|
||||
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它y声明为 `可选类型`。可选类型的属性将自动初始化为 `nil`,表示这个属性是特意在构造过程设置为空。
|
||||
|
||||
下面例子中定义了类 `SurveyQuestion`,它包含一个可选字符串属性 `response`:
|
||||
下面例子中定义了类 `SurveyQuestion`,它包含一个可选 ` String` 属性 `response`:
|
||||
|
||||
```swift
|
||||
class SurveyQuestion {
|
||||
@ -185,12 +188,12 @@ cheeseQuestion.ask()
|
||||
cheeseQuestion.response = "Yes, I do like cheese."
|
||||
```
|
||||
|
||||
调查问题的答案在回答前是无法确定的,因此我们将属性 `response` 声明为 `String?` 类型,或者说是 `可选字符串类型`。当 `SurveyQuestion` 实例化时,它将自动赋值为 `nil`,表明此字符串暂时还没有值。
|
||||
调查问题的答案在询问前是无法确定的,因此我们将属性 `response` 声明为 `String?` 类型,或者说是 “可选类型 `String`“。当 `SurveyQuestion` 的实例初始化时,它将自动赋值为 `nil`,表明“暂时还没有字符“。
|
||||
|
||||
<a name="assigning_constant_properties_during_initialization"></a>
|
||||
### 构造过程中常量属性的赋值
|
||||
|
||||
你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。
|
||||
你可以在构造过程中的任意时间点给常量属性赋值,只要在构造过程结束时它设置成确定的值。一旦常量属性被赋值,它将永远不可更改。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -218,9 +221,9 @@ beetsQuestion.response = "I also like beets. (But not with cheese.)"
|
||||
<a name="default_initializers"></a>
|
||||
## 默认构造器
|
||||
|
||||
如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个*默认构造器(default initializers)*。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。
|
||||
如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个*默认构造器*。这个默认构造器将简单地创建一个所有属性值都设置为它们默认值的实例。
|
||||
|
||||
下面例子中创建了一个类 `ShoppingListItem`,它封装了购物清单中的某一物品的属性:名字(`name`)、数量(`quantity`)和购买状态 `purchase state`:
|
||||
下面例子中定义了一个类 `ShoppingListItem`,它封装了购物清单中的某一物品的名字(`name`)、数量(`quantity`)和购买状态 `purchase state`:
|
||||
|
||||
```swift
|
||||
class ShoppingListItem {
|
||||
@ -231,16 +234,16 @@ class ShoppingListItem {
|
||||
var item = ShoppingListItem()
|
||||
```
|
||||
|
||||
由于 `ShoppingListItem` 类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器(尽管代码中没有显式为 `name` 属性设置默认值,但由于 `name` 是可选字符串类型,它将默认设置为 `nil`)。上面例子中使用默认构造器创造了一个 `ShoppingListItem` 类的实例(使用 `ShoppingListItem()` 形式的构造器语法),并将其赋值给变量 `item`。
|
||||
由于 `ShoppingListItem` 类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个将为所有属性设置默认值的并创建实例的默认构造器(由于 `name` 属性是可选 `String` 类型,它将接收一个默认 `nil` 的默认值,尽管代码中没有写出这个值)。上面例子中使用默认构造器创造了一个 `ShoppingListItem` 类的实例(使用 `ShoppingListItem()` 形式的构造器语法),并将其赋值给变量 `item`。
|
||||
|
||||
<a name="memberwise_initializers_for_structure_types"></a>
|
||||
### 结构体的逐一成员构造器
|
||||
|
||||
除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个*逐一成员构造器(memberwise initializer)*,即使结构体的存储型属性没有默认值。
|
||||
结构体如果没有定义任何自定义构造器,它们将自动获得一个*逐一成员构造器(memberwise initializer)*。不像默认构造器,即使存储型属性没有默认值,结构体也能会获得逐一成员构造器。
|
||||
|
||||
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
|
||||
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。新实例的属性初始值可以通过名字传入逐一成员构造器中。
|
||||
|
||||
下面例子中定义了一个结构体 `Size`,它包含两个属性 `width` 和 `height`。Swift 可以根据这两个属性的初始赋值 `0.0` 自动推导出它们的类型为 `Double`。
|
||||
下面例子中定义了一个结构体 `Size`,它包含两个属性 `width` 和 `height`。根据这两个属性默认赋值为 `0.0` ,它们的类型被推断出来为 `Double`。
|
||||
|
||||
结构体 `Size` 自动获得了一个逐一成员构造器 `init(width:height:)`。你可以用它来创建新的 Size 实例:
|
||||
|
||||
@ -256,17 +259,17 @@ let twoByTwo = Size(width: 2.0, height: 2.0)
|
||||
|
||||
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为*构造器代理*,它能避免多个构造器间的代码重复。
|
||||
|
||||
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.html)),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
|
||||
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.html))。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
|
||||
|
||||
对于值类型,你可以使用 `self.init` 在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 `self.init`。
|
||||
|
||||
请注意,如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器
|
||||
请注意,如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制避免了在一个更复杂的构造器中做了额外的重要设置,但有人不小心使用自动生成的构造器而导致错误的情况。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./20_Extensions.html)章节。
|
||||
|
||||
下面例子将定义一个结构体 `Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体 `Size` 和 `Point`,它们各自为其所有的属性提供了默认初始值 `0.0`。
|
||||
下面例子定义一个自定义结构体 `Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体 `Size` 和 `Point`,它们各自为其所有的属性提供了默认初始值 `0.0`。
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
@ -299,14 +302,14 @@ struct Rect {
|
||||
}
|
||||
```
|
||||
|
||||
第一个 `Rect` 构造器 `init()`,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是一个空函数,使用一对大括号 `{}` 来表示。调用这个构造器将返回一个 `Rect` 实例,它的 `origin` 和 `size` 属性都使用定义时的默认值 `Point(x: 0.0, y: 0.0)` 和 `Size(width: 0.0, height: 0.0)`:
|
||||
第一个 `Rect` 构造器 `init()`,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是函数体是空的,使用一对大括号 `{}` 来表示。调用这个构造器将返回一个 `Rect` 实例,它的 `origin` 和 `size` 属性都使用定义时的默认值 `Point(x: 0.0, y: 0.0)` 和 `Size(width: 0.0, height: 0.0)`:
|
||||
|
||||
```swift
|
||||
let basicRect = Rect()
|
||||
// basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)
|
||||
```
|
||||
|
||||
第二个 `Rect` 构造器 `init(origin:size:)`,在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只是简单地将 `origin` 和 `size` 的参数值赋给对应的存储型属性:
|
||||
第二个 `Rect` 构造器 `init(origin:size:)`,在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只是简单地将 `origin` 和 `size` 的实参值赋给对应的存储型属性:
|
||||
|
||||
```swift
|
||||
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
@ -322,7 +325,7 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
// centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)
|
||||
```
|
||||
|
||||
构造器 `init(center:size:)` 可以直接将 `origin` 和 `size` 的新值赋值到对应的属性中。然而,构造器 `init(center:size:)` 通过使用提供了相关功能的现有构造器将会更加便捷。
|
||||
构造器 `init(center:size:)` 可以直接将 `origin` 和 `size` 的新值赋值到对应的属性中。然而,构造器 `init(center:size:)` 通过使用提供了相关功能的现有构造器将会更加便捷(而且意图更清晰)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -332,19 +335,18 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
## 类的继承和构造过程
|
||||
|
||||
类里面的所有存储型属性——包括所有继承自父类的属性——都*必须*在构造过程中设置初始值。
|
||||
[]()
|
||||
Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
|
||||
Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们被称为指定构造器和便利构造器。
|
||||
|
||||
<a name="designated_initializers_and_convenience_initializers"></a>
|
||||
### 指定构造器和便利构造器
|
||||
|
||||
*指定构造器*是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类合适的构造器来实现父类的初始化。
|
||||
*指定构造器*是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着父类链继续往上进行。
|
||||
|
||||
类倾向于拥有少量指定构造器,普遍的是一个类拥有一个指定构造器。指定构造器在初始化的地方通过“管道”将初始化过程持续到父类链。
|
||||
类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器。指定构造器像一个个“漏斗”放在构造过程发生的地方,让构造过程沿着父类链继续往上进行。
|
||||
|
||||
每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
|
||||
|
||||
*便利构造器*是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
|
||||
*便利构造器*是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
|
||||
|
||||
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
|
||||
|
||||
@ -368,9 +370,9 @@ convenience init(parameters) {
|
||||
```
|
||||
|
||||
<a name="initializer_delegation_for_class_types"></a>
|
||||
### 类的构造器代理规则
|
||||
### 类类型的构造器代理
|
||||
|
||||
为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:
|
||||
为了简化指定构造器和便利构造器之间的调用关系,Swift 构造器之间的代理调用遵循以下三条规则:
|
||||
|
||||
##### 规则 1
|
||||
|
||||
@ -391,7 +393,7 @@ convenience init(parameters) {
|
||||
|
||||
这些规则可以通过下面图例来说明:
|
||||
|
||||

|
||||

|
||||
|
||||
如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则 2 和 3。这个父类没有自己的父类,所以规则 1 没有用到。
|
||||
|
||||
@ -401,14 +403,15 @@ convenience init(parameters) {
|
||||
>
|
||||
> 这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类的构造器如何实现。
|
||||
|
||||
下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。
|
||||
下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“漏斗”的作用,在类的构造器链上简化了类之间的相互关系。
|
||||
|
||||

|
||||

|
||||
|
||||
<a name="two_phase_initialization"></a>i
|
||||
|
||||
<a name="two_phase_initialization"></a>
|
||||
### 两段式构造过程
|
||||
|
||||
Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性。
|
||||
Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性。
|
||||
|
||||
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。
|
||||
|
||||
@ -426,41 +429,41 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
|
||||
##### 安全检查 2
|
||||
|
||||
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器,如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
|
||||
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
|
||||
|
||||
##### 安全检查 3
|
||||
|
||||
便利构造器必须为任意属性(包括同类中定义的)赋新值之前代理调用同一类中的其它构造器,如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
|
||||
便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。
|
||||
|
||||
##### 安全检查 4
|
||||
|
||||
构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 `self` 作为一个值。
|
||||
|
||||
类实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。
|
||||
类的实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,类的实例才是有效的,才能访问属性和调用方法。
|
||||
|
||||
以下是两段式构造过程中基于上述安全检查的构造流程展示:
|
||||
以下是基于上述安全检查的两段式构造过程展示:
|
||||
|
||||
##### 阶段 1
|
||||
|
||||
- 某个指定构造器或便利构造器被调用。
|
||||
- 完成新实例内存的分配,但此时内存还没有被初始化。
|
||||
- 类的某个指定构造器或便利构造器被调用。
|
||||
- 完成类的新实例内存的分配,但此时内存还没有被初始化。
|
||||
- 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
|
||||
- 指定构造器将调用父类的构造器,完成父类属性的初始化。
|
||||
- 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
|
||||
- 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
|
||||
- 指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
|
||||
- 这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。
|
||||
- 当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
|
||||
|
||||
##### 阶段 2
|
||||
|
||||
- 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问 `self`、修改它的属性并调用实例方法等等。
|
||||
- 最终,任意构造器链中的便利构造器可以有机会定制实例和使用 `self`。
|
||||
- 从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问 `self`、修改它的属性并调用实例方法等等。
|
||||
- 最终,继承链中任意的便利构造器有机会自定义实例和使用 `self`。
|
||||
|
||||
下图展示了在假定的子类和父类之间的构造阶段 1:
|
||||
|
||||

|
||||

|
||||
|
||||
在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。
|
||||
在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时还不能修改任何属性,它会代理到该类中的指定构造器。
|
||||
|
||||
如安全检查 1 所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着构造器链一直往上完成父类的构造过程。
|
||||
如安全检查 1 所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着继承链一直往上完成父类的构造过程。
|
||||
|
||||
父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要初始化,也就无需继续向上代理。
|
||||
|
||||
@ -468,28 +471,28 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
|
||||
以下展示了相同构造过程的阶段 2:
|
||||
|
||||

|
||||

|
||||
|
||||
父类中的指定构造器现在有机会进一步来定制实例(尽管这不是必须的)。
|
||||
父类中的指定构造器现在有机会进一步自定义实例(尽管这不是必须的)。
|
||||
|
||||
一旦父类中的指定构造器完成调用,子类中的指定构造器可以执行更多的定制操作(这也不是必须的)。
|
||||
一旦父类中的指定构造器完成调用,子类中的指定构造器可以执行更多的自定义操作(这也不是必须的)。
|
||||
|
||||
最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的定制操作。
|
||||
最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的自定义操作。
|
||||
|
||||
<a name="initializer_inheritance_and_overriding"></a>
|
||||
### 构造器的继承和重写
|
||||
|
||||
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。
|
||||
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,而在用来创建子类时的新实例时没有完全或错误被初始化。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 父类的构造器仅会在安全和适当的情况下被继承。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
|
||||
> 父类的构造器仅会在安全和适当的某些情况下被继承。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
|
||||
|
||||
假如你希望自定义的子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。
|
||||
|
||||
当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上 `override` 修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上 `override` 修饰符,具体内容请参考[默认构造器](#default_initializers)。
|
||||
|
||||
正如重写属性,方法或者是下标,`override` 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否正确。
|
||||
正如重写属性,方法或者是下标,`override` 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否被按预想中被指定。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -497,7 +500,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
|
||||
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。
|
||||
|
||||
在下面的例子中定义了一个叫 `Vehicle` 的基类。基类中声明了一个存储型属性 `numberOfWheels`,它是默认值为 `0` 的 `Int` 类型的存储型属性。`numberOfWheels` 属性用于创建名为 `descrpiption` 的 `String` 类型的计算型属性:
|
||||
在下面的例子中定义了一个叫 Vehicle 的基类。基类中声明了一个存储型属性 numberOfWheels,它是默认值为 Int 类型的 0。numberOfWheels 属性用在一个描述车辆特征 String 类型为 descrpiption 的计算型属性中:
|
||||
|
||||
```swift
|
||||
class Vehicle {
|
||||
@ -508,7 +511,7 @@ class Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。自动获得的默认构造器总是类中的指定构造器,它可以用于创建 `numberOfWheels` 为 `0` 的 `Vehicle`
|
||||
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels` 为 `0` 的 `Vehicle`
|
||||
实例:
|
||||
|
||||
```swift
|
||||
@ -528,7 +531,7 @@ class Bicycle: Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
子类 `Bicycle` 定义了一个自定义指定构造器 `init()`。这个指定构造器和父类的指定构造器相匹配,所以 `Bicycle` 中的指定构造器需要带上 `override` 修饰符。
|
||||
子类 `Bicycle` 定义了一个自定义指定构造器 `init()`。这个指定构造器和父类的指定构造器相匹配,所以 `Bicycle` 中这个版本的构造器需要带上 `override` 修饰符。
|
||||
|
||||
`Bicycle` 的构造器 `init()` 以调用 `super.init()` 方法开始,这个方法的作用是调用 `Bicycle` 的父类 `Vehicle` 的默认构造器。这样可以确保 `Bicycle` 在修改属性之前,它所继承的属性 `numberOfWheels` 能被 `Vehicle` 类初始化。在调用 `super.init()` 之后,属性 `numberOfWheels` 的原值被新值 `2` 替换。
|
||||
|
||||
@ -540,16 +543,42 @@ print("Bicycle: \(bicycle.description)")
|
||||
// 打印 "Bicycle: 2 wheel(s)"
|
||||
```
|
||||
|
||||
如果父类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的自定义构造器。你可以省略 `super.init()` 的调用在所有父类的存储属性赋值之后。
|
||||
|
||||
这个例子定义了另一个 `Vehicle` 的子类 `Hoverboard` ,只设置它的 `color` 属性。这个构造器依赖隐式调用父类的构造器来完成,而不是显示调用 `super.init()`。
|
||||
|
||||
```swift
|
||||
class Hoverboard: Vehicle {
|
||||
var color: String
|
||||
init(color: String) {
|
||||
self.color = color
|
||||
// super.init() 在这里被隐式调用
|
||||
}
|
||||
override var description: String {
|
||||
return "\(super.description) in a beautiful \(color)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Hoverboard` 的实例用 `Vehicle` 构造器里默认的轮子数量。
|
||||
|
||||
```swift
|
||||
let hoverboard = Hoverboard(color: "silver")
|
||||
print("Hoverboard: \(hoverboard.description)")
|
||||
// Hoverboard: 0 wheel(s) in a beautiful silver
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。
|
||||
>
|
||||
> 子类可以在构造过程修改继承来的变量属性,但是不能修改继承来的常量属性。
|
||||
|
||||
<a name="automatic_initializer_inheritance"></a>
|
||||
|
||||
### 构造器的自动继承
|
||||
|
||||
如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。事实上,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。
|
||||
|
||||
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:
|
||||
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则将适用:
|
||||
|
||||
##### 规则 1
|
||||
|
||||
@ -563,12 +592,12 @@ print("Bicycle: \(bicycle.description)")
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 对于规则 2,子类可以将父类的指定构造器实现为便利构造器。
|
||||
> 子类可以将父类的指定构造器实现为便利构造器来满足规则 2。
|
||||
|
||||
<a name="designated_and_convenience_initializers_in_action"></a>
|
||||
### 指定构造器和便利构造器实践
|
||||
|
||||
接下来的例子将在实践中展示指定构造器、便利构造器以及构造器的自动继承。这个例子定义了包含三个类 `Food`、`RecipeIngredient` 以及 `ShoppingListItem` 的类层次结构,并将演示它们的构造器是如何相互作用的。
|
||||
接下来的例子将在实践中展示指定构造器、便利构造器以及构造器的自动继承。这个例子定义了包含三个类 `Food`、`RecipeIngredient` 以及 `ShoppingListItem` 的层级结构,并将演示它们的构造器是如何相互作用的。
|
||||
|
||||
类层次中的基类是 `Food`,它是一个简单的用来封装食物名字的类。`Food` 类引入了一个叫做 `name` 的 `String` 类型的属性,并且提供了两个构造器来创建 `Food` 实例:
|
||||
|
||||
@ -587,7 +616,7 @@ class Food {
|
||||
|
||||
下图中展示了 `Food` 的构造器链:
|
||||
|
||||

|
||||

|
||||
|
||||
类类型没有默认的逐一成员构造器,所以 `Food` 类提供了一个接受单一参数 `name` 的指定构造器。这个构造器可以使用一个特定的名字来创建新的 `Food` 实例:
|
||||
|
||||
@ -605,7 +634,7 @@ let mysteryMeat = Food()
|
||||
// mysteryMeat 的名字是 [Unnamed]
|
||||
```
|
||||
|
||||
类层级中的第二个类是 `Food` 的子类 `RecipeIngredient`。`RecipeIngredient` 类用来表示食谱中的一项原料。它引入了 `Int` 类型的属性 `quantity`(以及从 `Food` 继承过来的 `name` 属性),并且定义了两个构造器来创建 `RecipeIngredient` 实例:
|
||||
层级中的第二个类是 `Food` 的子类 `RecipeIngredient`。`RecipeIngredient` 类用来表示食谱中的一项原料。它引入了 `Int` 类型的属性 `quantity`(以及从 `Food` 继承过来的 `name` 属性),并且定义了两个构造器来创建 `RecipeIngredient` 实例:
|
||||
|
||||
```swift
|
||||
class RecipeIngredient: Food {
|
||||
@ -622,13 +651,13 @@ class RecipeIngredient: Food {
|
||||
|
||||
下图中展示了 `RecipeIngredient` 类的构造器链:
|
||||
|
||||

|
||||

|
||||
|
||||
`RecipeIngredient` 类拥有一个指定构造器 `init(name: String, quantity: Int)`,它可以用来填充 `RecipeIngredient` 实例的所有属性值。这个构造器一开始先将传入的 `quantity` 参数赋值给 `quantity` 属性,这个属性也是唯一在 `RecipeIngredient` 中新引入的属性。随后,构造器向上代理到父类 `Food` 的 `init(name: String)`。这个过程满足[两段式构造过程](#two_phase_initialization)中的安全检查 1。
|
||||
`RecipeIngredient` 类拥有一个指定构造器 `init(name: String, quantity: Int)`,它可以用来填充 `RecipeIngredient` 实例的所有属性值。这个构造器一开始先将传入的 `quantity` 实参赋值给 `quantity` 属性,这个属性也是唯一在 `RecipeIngredient` 中新引入的属性。随后,构造器向上代理到父类 `Food` 的 `init(name: String)`。这个过程满足[两段式构造过程](#two_phase_initialization)中的安全检查 1。
|
||||
|
||||
`RecipeIngredient` 也定义了一个便利构造器 `init(name: String)`,它只通过 `name` 来创建 `RecipeIngredient` 的实例。这个便利构造器假设任意 `RecipeIngredient` 实例的 `quantity` 为 `1`,所以不需要显式指明数量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个 `quantity` 为 `1` 的 `RecipeIngredient` 实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为 `quantity` 参数传递 `1`。
|
||||
`RecipeIngredient` 也定义了一个便利构造器 `init(name: String)`,它只通过 `name` 来创建 `RecipeIngredient` 的实例。这个便利构造器假设任意 `RecipeIngredient` 实例的 `quantity` 为 `1`,所以不需要显式的质量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个 `quantity` 为 `1` 的 `RecipeIngredient` 实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为 `quantity` 参数传递 `1`。
|
||||
|
||||
注意,`RecipeIngredient` 的便利构造器 `init(name: String)` 使用了跟 `Food` 中指定构造器 `init(name: String)` 相同的参数。由于这个便利构造器重写了父类的指定构造器 `init(name: String)`,因此必须在前面使用 `override` 修饰符(参见[构造器的继承和重写](#initializer_inheritance_and_overriding))。
|
||||
`RecipeIngredient` 的便利构造器 `init(name: String)` 使用了跟 `Food` 中指定构造器 `init(name: String)` 相同的形参。由于这个便利构造器重写了父类的指定构造器 `init(name: String)`,因此必须在前面使用 `override` 修饰符(参见[构造器的继承和重写](#initializer_inheritance_and_overriding))。
|
||||
|
||||
尽管 `RecipeIngredient` 将父类的指定构造器重写为了便利构造器,但是它依然提供了父类的所有指定构造器的实现。因此,`RecipeIngredient` 会自动继承父类的所有便利构造器。
|
||||
|
||||
@ -661,11 +690,11 @@ class ShoppingListItem: RecipeIngredient {
|
||||
>
|
||||
> `ShoppingListItem` 没有定义构造器来为 `purchased` 提供初始值,因为添加到购物单的物品的初始状态总是未购买。
|
||||
|
||||
由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,`ShoppingListItem` 将自动继承所有父类中的指定构造器和便利构造器。
|
||||
因为它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,`ShoppingListItem` 将自动继承所有父类中的指定构造器和便利构造器。
|
||||
|
||||
下图展示了这三个类的构造器链:
|
||||
|
||||

|
||||

|
||||
|
||||
你可以使用三个继承来的构造器来创建 `ShoppingListItem` 的新实例:
|
||||
|
||||
@ -690,7 +719,7 @@ for item in breakfastList {
|
||||
<a name="failable_initializers"></a>
|
||||
## 可失败构造器
|
||||
|
||||
如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器是很有用的。这里所指的“失败” 指的是,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
|
||||
有时,定义一个构造器可失败的类,结构体或者枚举是很有用的。这里所指的“失败” 指的是,如给构造器传入无效的形参,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
|
||||
|
||||
为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在 `init` 关键字后面添加问号(`init?`)。
|
||||
|
||||
@ -724,7 +753,7 @@ if valueChanged == nil {
|
||||
// 打印 "3.14159 conversion to Int does not maintain value"
|
||||
```
|
||||
|
||||
下例中,定义了一个名为 `Animal` 的结构体,其中有一个名为 `species` 的 `String` 类型的常量属性。同时该结构体还定义了一个接受一个名为 `species` 的 `String` 类型参数的可失败构造器。这个可失败构造器检查传入的参数是否为一个空字符串。如果为空字符串,则构造失败。否则,`species` 属性被赋值,构造成功。
|
||||
下例中,定义了一个名为 `Animal` 的结构体,其中有一个名为 `species` 的 `String` 类型的常量属性。同时该结构体还定义了一个接受一个名为 `species` 的 `String` 类型形参的可失败构造器。这个可失败构造器检查传入的`species` 值是否为一个空字符串。如果为空字符串,则构造失败。否则,`species` 属性被赋值,构造成功。
|
||||
|
||||
```swift
|
||||
struct Animal {
|
||||
@ -750,7 +779,7 @@ if let giraffe = someCreature {
|
||||
// 打印 "An animal was initialized with a species of Giraffe"
|
||||
```
|
||||
|
||||
如果你给该可失败构造器传入一个空字符串作为其参数,则会导致构造失败:
|
||||
如果你给该可失败构造器传入一个空字符串到形参 `species`,则会导致构造失败:
|
||||
|
||||
```swift
|
||||
let anonymousCreature = Animal(species: "")
|
||||
@ -764,14 +793,14 @@ if anonymousCreature == nil {
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 空字符串(如 `""`,而不是 `"Giraffe"` )和一个值为 `nil` 的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们之所以让 `Animal` 的可失败构造器构造失败,只是因为对于 `Animal` 这个类的 `species` 属性来说,它更适合有一个具体的值,而不是空字符串。
|
||||
> 检查空字符串的值(如 `""`,而不是 `"Giraffe"` )和检查值为 `nil` 的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们之所以让 `Animal` 的可失败构造器构造失败,只是因为对于 `Animal` 这个类的 `species` 属性来说,它更适合有一个具体的值,而不是空字符串。
|
||||
|
||||
<a name="failable_nitializers_for_enumerations"></a>
|
||||
### 枚举类型的可失败构造器
|
||||
|
||||
你可以通过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败。
|
||||
你可以通过一个带一个或多个形参的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的形参无法匹配任何枚举成员,则构造失败。
|
||||
|
||||
下例中,定义了一个名为 `TemperatureUnit` 的枚举类型。其中包含了三个可能的枚举成员(`Kelvin`、`Celsius` 和 `Fahrenheit`),以及一个根据 `Character` 值找出所对应的枚举成员的可失败构造器:
|
||||
下例中,定义了一个名为 `TemperatureUnit` 的枚举类型。其中包含了三个可能的枚举状态(`Kelvin`、`Celsius` 和 `Fahrenheit`),以及一个根据表示温度单位的 `Character` 值找出合适的枚举成员的可失败构造器:
|
||||
|
||||
```swift
|
||||
enum TemperatureUnit {
|
||||
@ -791,7 +820,7 @@ enum TemperatureUnit {
|
||||
}
|
||||
```
|
||||
|
||||
你可以利用该可失败构造器在三个枚举成员中获取一个相匹配的枚举成员,当参数的值不能与任何枚举成员相匹配时,则构造失败:
|
||||
你可以利用该可失败构造器在三个枚举成员中选择合适的枚举成员,当形参不能和任何枚举成员相匹配时,则构造失败:
|
||||
|
||||
```swift
|
||||
let fahrenheitUnit = TemperatureUnit(symbol: "F")
|
||||
@ -810,9 +839,9 @@ if unknownUnit == nil {
|
||||
<a name="failable_initializers_for_enumerations_with_raw_values"></a>
|
||||
### 带原始值的枚举类型的可失败构造器
|
||||
|
||||
带原始值的枚举类型会自带一个可失败构造器 `init?(rawValue:)`,该可失败构造器有一个名为 `rawValue` 的参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和某个枚举成员的原始值匹配,则该构造器会构造相应的枚举成员,否则构造失败。
|
||||
带原始值的枚举类型会自带一个可失败构造器 `init?(rawValue:)`,该可失败构造器有一个合适的原始值类型的 `rawValue` 形参,选择找到的相匹配的枚举成员,找不到则构造失败。
|
||||
|
||||
因此上面的 `TemperatureUnit` 的例子可以重写为:
|
||||
因此上面的 `TemperatureUnit` 的例子可以用原始值类型的 `Character` 和进阶的 `init?(rawValue:)` 构造器重写为:
|
||||
|
||||
```swift
|
||||
enum TemperatureUnit: Character {
|
||||
@ -835,13 +864,13 @@ if unknownUnit == nil {
|
||||
<a name="propagation_of_initialization_failure"></a>
|
||||
### 构造失败的传递
|
||||
|
||||
类,结构体,枚举的可失败构造器可以横向代理到同类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
|
||||
类,结构体,枚举的可失败构造器可以横向代理到它们自己其他的可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
|
||||
|
||||
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。
|
||||
> 可失败构造器也可以代理到其它的不可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。
|
||||
|
||||
下面这个例子,定义了一个名为 `CartItem` 的 `Product` 类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为 `quantity` 的常量存储型属性,并确保该属性的值至少为 `1`:
|
||||
|
||||
@ -908,7 +937,7 @@ if let oneUnnamed = CartItem(name: "", quantity: 1) {
|
||||
>
|
||||
> 你可以用非可失败构造器重写可失败构造器,但反过来却不行。
|
||||
|
||||
下例定义了一个名为 `Document` 的类,`name` 属性的值必须为一个非空字符串或 `nil`,但不能是一个空字符串:
|
||||
下例定义了一个名为 `Document` 的类。这个类模拟一个文档并可以用 `name` 属性来构造,属性的值必须为一个非空字符串或 `nil`,但不能是一个空字符串:
|
||||
|
||||
```swift
|
||||
class Document {
|
||||
@ -917,13 +946,13 @@ class Document {
|
||||
init() {}
|
||||
// 该构造器创建了一个 name 属性的值为非空字符串的 document 实例
|
||||
init?(name: String) {
|
||||
self.name = name
|
||||
if name.isEmpty { return nil }
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面这个例子,定义了一个 `Document` 类的子类 `AutomaticallyNamedDocument`。这个子类重写了父类的两个指定构造器,确保了无论是使用 `init()` 构造器,还是使用 `init(name:)` 构造器并为参数传递空字符串,生成的实例中的 `name` 属性总有初始 `"[Untitled]"`:
|
||||
下面这个例子,定义了一个 `Document` 类的子类 `AutomaticallyNamedDocument`。这个子类重写了所有父类引入的指定构造器。这些重写确保了无论是使用 `init()` 构造器,还是使用 `init(name:)` 构造器,在没有名字或者形参传入空字符串时,生成的实例中的 `name` 属性总有初始值 `"[Untitled]"`:
|
||||
|
||||
```swift
|
||||
class AutomaticallyNamedDocument: Document {
|
||||
@ -942,9 +971,9 @@ class AutomaticallyNamedDocument: Document {
|
||||
}
|
||||
```
|
||||
|
||||
`AutomaticallyNamedDocument` 用一个非可失败构造器 `init(name:)` 重写了父类的可失败构造器 `init?(name:)`。因为子类用另一种方式处理了空字符串的情况,所以不再需要一个可失败构造器,因此子类用一个非可失败构造器代替了父类的可失败构造器。
|
||||
`AutomaticallyNamedDocument` 用一个不可失败构造器 `init(name:)` 重写了父类的可失败构造器 `init?(name:)`。因为子类用另一种方式处理了空字符串的情况,所以不再需要一个可失败构造器,因此子类用一个不可失败构造器代替了父类的可失败构造器。
|
||||
|
||||
你可以在子类的非可失败构造器中使用强制解包来调用父类的可失败构造器。比如,下面的 `UntitledDocument` 子类的 `name` 属性的值总是 `"[Untitled]"`,它在构造过程中使用了父类的可失败构造器 `init?(name:)`:
|
||||
你可以在子类的不可失败构造器中使用强制解包来调用父类的可失败构造器。比如,下面的 `UntitledDocument` 子类的 `name` 属性的值总是 `"[Untitled]"`,它在构造过程中使用了父类的可失败构造器 `init?(name:)`:
|
||||
|
||||
```swift
|
||||
class UntitledDocument: Document {
|
||||
@ -954,12 +983,12 @@ class UntitledDocument: Document {
|
||||
}
|
||||
```
|
||||
|
||||
在这个例子中,如果在调用父类的可失败构造器 `init?(name:)` 时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过非空的字符串常量来调用它,所以并不会发生运行时错误。
|
||||
在这个例子中,如果在调用父类的可失败构造器 `init?(name:)` 时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过字符串常量来调用它,构造器不会失败,所以并不会发生运行时错误。
|
||||
|
||||
<a name="the_init!_failable_initializer"></a>
|
||||
### init!可失败构造器
|
||||
### init! 可失败构造器
|
||||
|
||||
通常来说我们通过在 `init` 关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以通过在 `init` 后面添加惊叹号的方式来定义一个可失败构造器(`init!`),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。
|
||||
通常来说我们通过在 `init` 关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以通过在 `init` 后面添加感叹号的方式来定义一个可失败构造器(`init!`),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。
|
||||
|
||||
你可以在 `init?` 中代理到 `init!`,反之亦然。你也可以用 `init?` 重写 `init!`,反之亦然。你还可以用 `init` 代理到 `init!`,不过,一旦 `init!` 构造失败,则会触发一个断言。
|
||||
|
||||
@ -993,7 +1022,7 @@ class SomeSubclass: SomeClass {
|
||||
<a name="setting_a_default_property_value_with_a_closure_or_function"></a>
|
||||
## 通过闭包或函数设置属性的默认值
|
||||
|
||||
如果某个存储型属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
|
||||
如果某个存储型属性的默认值需要一些自定义或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被构造时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
|
||||
|
||||
这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。
|
||||
|
||||
@ -1017,7 +1046,7 @@ class SomeClass {
|
||||
|
||||
下面例子中定义了一个结构体 `Chessboard`,它构建了西洋跳棋游戏的棋盘,西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行的:
|
||||
|
||||

|
||||

|
||||
|
||||
为了呈现这副游戏棋盘,`Chessboard` 结构体定义了一个属性 `boardColors`,它是一个包含 `64` 个 `Bool` 值的数组。在数组中,值为 `true` 的元素表示一个黑格,值为 `false` 的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。
|
||||
|
||||
@ -1048,7 +1077,7 @@ struct Chessboard {
|
||||
```swift
|
||||
let board = Chessboard()
|
||||
print(board.squareIsBlackAt(row: 0, column: 1))
|
||||
// Prints "true"
|
||||
// 打印 "true"
|
||||
print(board.squareIsBlackAt(row: 7, column: 7))
|
||||
// Prints "false”
|
||||
// 打印 "false”
|
||||
```
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。
|
||||
|
||||
特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。
|
||||
这里需要特别指出,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。
|
||||
|
||||
下面几段代码将解释可选链式调用和强制展开的不同。
|
||||
|
||||
@ -31,7 +31,7 @@ class Residence {
|
||||
|
||||
`Residence` 有一个 `Int` 类型的属性 `numberOfRooms`,其默认值为 `1`。`Person` 具有一个可选的 `residence` 属性,其类型为 `Residence?`。
|
||||
|
||||
假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是是可选型而将初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil` 的 `residence` 属性:
|
||||
假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是可选类型而将被初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil` 的 `residence` 属性:
|
||||
|
||||
```swift
|
||||
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` 的方式,使用问号(`?`)来替代原来的叹号(`!`):
|
||||
|
||||
@ -83,7 +83,7 @@ if let roomCount = john.residence?.numberOfRooms {
|
||||
<a name="defining_model_classes_for_optional_chaining"></a>
|
||||
## 为可选链式调用定义模型类
|
||||
|
||||
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。
|
||||
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法和下标。
|
||||
|
||||
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在 `Person` 和 `Residence` 的基础上增加了 `Room` 类和 `Address` 类,以及相关的属性、方法以及下标。
|
||||
|
||||
@ -135,7 +135,7 @@ class Room {
|
||||
}
|
||||
```
|
||||
|
||||
最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示某个大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称:
|
||||
最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称:
|
||||
|
||||
```swift
|
||||
class Address {
|
||||
@ -145,7 +145,7 @@ class Address {
|
||||
func buildingIdentifier() -> String? {
|
||||
if buildingName != nil {
|
||||
return buildingName
|
||||
} else if buildingNumber != nil && street != nil {
|
||||
} else if let buildingNumber = buildingNumber, let street = street {
|
||||
return "\(buildingNumber) \(street)"
|
||||
} else {
|
||||
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>
|
||||
## 通过可选链式调用访问属性
|
||||
|
||||
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
||||
|
||||
下面的代码创建了一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性:
|
||||
使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
@ -186,7 +186,7 @@ john.residence?.address = someAddress
|
||||
|
||||
在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`。
|
||||
|
||||
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行。
|
||||
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印 “Function was called”,这使你能验证等号右侧的代码是否被执行。
|
||||
|
||||
```swift
|
||||
func createAddress() -> Address {
|
||||
@ -299,7 +299,7 @@ testScores["Brian"]?[0] = 72
|
||||
// "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>
|
||||
## 连接多层可选链式调用
|
||||
@ -367,6 +367,7 @@ if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
||||
|
||||
```swift
|
||||
if let beginsWithThe =
|
||||
|
||||
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
||||
if beginsWithThe {
|
||||
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>
|
||||
## 表示并抛出错误
|
||||
## 表示与抛出错误
|
||||
|
||||
在 Swift 中,错误用符合 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
|
||||
在 Swift 中,错误用遵循 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
|
||||
|
||||
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态:
|
||||
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,在游戏中操作自动贩卖机时,你可以这样表示可能会出现的错误状态:
|
||||
|
||||
```swift
|
||||
enum VendingMachineError: Error {
|
||||
@ -25,7 +25,7 @@ enum VendingMachineError: Error {
|
||||
}
|
||||
```
|
||||
|
||||
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币:
|
||||
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 语句。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币:
|
||||
|
||||
```swift
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
||||
@ -47,10 +47,11 @@ Swift 中有 `4` 种处理错误的方式。你可以把函数抛出的错误传
|
||||
<a name="propagating_errors_using_throwing_functions"></a>
|
||||
### 用 throwing 函数传递错误
|
||||
|
||||
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作*throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在箭头(`->`)的前面。
|
||||
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作 *throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在返回箭头(`->`)的前面。
|
||||
|
||||
```swift
|
||||
func canThrowErrors() throws -> String
|
||||
|
||||
func cannotThrowErrors() -> String
|
||||
```
|
||||
|
||||
@ -64,8 +65,8 @@ func cannotThrowErrors() -> String
|
||||
|
||||
```swift
|
||||
struct Item {
|
||||
var price: Int
|
||||
var count: Int
|
||||
var price: Int
|
||||
var count: Int
|
||||
}
|
||||
|
||||
class VendingMachine {
|
||||
@ -75,9 +76,6 @@ class VendingMachine {
|
||||
"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 {
|
||||
@ -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:)` 函数被调用的地方。
|
||||
|
||||
@ -147,29 +145,57 @@ do {
|
||||
statements
|
||||
} catch pattern 2 where condition {
|
||||
statements
|
||||
} catch {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
在 `catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
|
||||
|
||||
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的 `do-catch` 错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理:
|
||||
举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部三种情况:
|
||||
|
||||
```swift
|
||||
var vendingMachine = VendingMachine()
|
||||
vendingMachine.coinsDeposited = 8
|
||||
do {
|
||||
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
||||
print("Success! Yum.")
|
||||
} catch VendingMachineError.invalidSelection {
|
||||
print("Invalid Selection.")
|
||||
} catch VendingMachineError.outOfStock {
|
||||
print("Out of Stock.")
|
||||
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
} catch {
|
||||
print("Unexpected error: \(error).")
|
||||
}
|
||||
// 打印 “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` 语句捕获。
|
||||
|
||||
### 将错误转换成可选值
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
*类型转换*可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
|
||||
|
||||
类型转换在 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>
|
||||
## 为类型转换定义类层次
|
||||
@ -77,11 +77,11 @@ for item in library {
|
||||
}
|
||||
|
||||
print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
// 打印 “Media library contains 2 movies and 3 songs”
|
||||
// 打印 "Media library contains 2 movies and 3 songs"
|
||||
```
|
||||
|
||||
示例迭代了数组 `library` 中的所有项。每一次,`for-in` 循环设置
|
||||
`item` 为数组中的下一个 `MediaItem`。
|
||||
`item` 常量为数组中的下一个 `MediaItem` 实例。
|
||||
|
||||
若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回
|
||||
`true`,否则返回 `false`。同样的,`item is Song` 检查 `item` 是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到的属于各自类型的实例的数量。
|
||||
@ -89,7 +89,7 @@ print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
<a name="downcasting"></a>
|
||||
## 向下转型
|
||||
|
||||
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用*类型转换操作符*(`as?` 或 `as!`)。
|
||||
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试用*类型转换操作符*(`as?` 或 `as!`)向下转到它的子类型。
|
||||
|
||||
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式 `as?` 返回一个你试图向下转成的类型的可选值。强制形式 `as!` 把试图向下转型和强制解包转换结果结合为一个操作。
|
||||
|
||||
@ -104,17 +104,17 @@ print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
```swift
|
||||
for item in library {
|
||||
if let movie = item as? Movie {
|
||||
print("Movie: '\(movie.name)', dir. \(movie.director)")
|
||||
print("Movie: \(movie.name), dir. \(movie.director)")
|
||||
} else if let song = item as? Song {
|
||||
print("Song: '\(song.name)', by \(song.artist)")
|
||||
print("Song: \(song.name), by \(song.artist)")
|
||||
}
|
||||
}
|
||||
|
||||
// Movie: 'Casablanca', dir. Michael Curtiz
|
||||
// Song: 'Blue Suede Shoes', by Elvis Presley
|
||||
// Movie: 'Citizen Kane', dir. Orson Welles
|
||||
// Song: 'The One And Only', by Chesney Hawkes
|
||||
// Song: 'Never Gonna Give You Up', by Rick Astley
|
||||
// Movie: Casablanca, dir. Michael Curtiz
|
||||
// Song: Blue Suede Shoes, by Elvis Presley
|
||||
// Movie: Citizen Kane, dir. Orson Welles
|
||||
// Song: The One And Only, by Chesney Hawkes
|
||||
// Song: Never Gonna Give You Up, by Rick Astley
|
||||
```
|
||||
|
||||
示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem`
|
||||
@ -140,7 +140,7 @@ Swift 为不确定类型提供了两种特殊的类型别名:
|
||||
* `Any` 可以表示任何类型,包括函数类型。
|
||||
* `AnyObject` 可以表示任何类类型的实例。
|
||||
|
||||
只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。当你期望你的代码可以工作,最好还是要确定类型。
|
||||
只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。最好还是在代码中指明需要使用的类型。
|
||||
|
||||
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 `Any` 类型的数组 `things`:
|
||||
|
||||
@ -179,7 +179,7 @@ for thing in things {
|
||||
case let (x, y) as (Double, Double):
|
||||
print("an (x, y) point at \(x), \(y)")
|
||||
case let movie as Movie:
|
||||
print("a movie called '\(movie.name)', dir. \(movie.director)")
|
||||
print("a movie called \(movie.name), dir. \(movie.director)")
|
||||
case let stringConverter as (String) -> String:
|
||||
print(stringConverter("Michael"))
|
||||
default:
|
||||
@ -193,7 +193,7 @@ for thing in things {
|
||||
// a positive double value of 3.14159
|
||||
// a string value of "hello"
|
||||
// an (x, y) point at 3.0, 5.0
|
||||
// a movie called 'Ghostbusters', dir. Ivan Reitman
|
||||
// a movie called Ghostbusters, dir. Ivan Reitman
|
||||
// Hello, Michael
|
||||
```
|
||||
|
||||
@ -208,3 +208,4 @@ let optionalNumber: Int? = 3
|
||||
things.append(optionalNumber) // 警告
|
||||
things.append(optionalNumber as Any) // 没有警告
|
||||
```
|
||||
|
||||
|
||||
@ -77,7 +77,7 @@ let john = Person(fullName: "John Appleseed")
|
||||
|
||||
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。
|
||||
|
||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。)
|
||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好遵循了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地遵循了协议。(如果协议要求未被完全遵循,在编译时会报错。)
|
||||
|
||||
下面是一个更为复杂的类,它适配并遵循了 `FullyNamed` 协议:
|
||||
|
||||
@ -124,7 +124,7 @@ protocol RandomNumberGenerator {
|
||||
|
||||
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
|
||||
|
||||
如下所示,下边是一个遵循并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。
|
||||
如下所示,下边是一个遵循 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。
|
||||
|
||||
```swift
|
||||
class LinearCongruentialGenerator: RandomNumberGenerator {
|
||||
@ -209,7 +209,7 @@ class SomeClass: SomeProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
|
||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议。
|
||||
|
||||
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
|
||||
|
||||
@ -392,7 +392,7 @@ class DiceGameTracker: DiceGameDelegate {
|
||||
|
||||
`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` 的运行情况如下所示:
|
||||
|
||||
@ -413,11 +413,11 @@ game.play()
|
||||
<a name="adding_protocol_conformance_with_an_extension"></a>
|
||||
## 在扩展里添加协议遵循
|
||||
|
||||
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。
|
||||
即便无法修改源代码,依然可以通过扩展令已有类型遵循协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以遵循协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
|
||||
> 通过扩展令已有类型遵循协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
|
||||
|
||||
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
|
||||
|
||||
@ -427,7 +427,7 @@ protocol TextRepresentable {
|
||||
}
|
||||
```
|
||||
|
||||
可以通过扩展,令先前提到的 `Dice` 类遵循并符合 `TextRepresentable` 协议:
|
||||
可以通过扩展,令先前提到的 `Dice` 类遵循 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension Dice: TextRepresentable {
|
||||
@ -437,7 +437,7 @@ extension Dice: TextRepresentable {
|
||||
}
|
||||
```
|
||||
|
||||
通过扩展遵循并符合协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
|
||||
通过扩展遵循协议,和在原始定义中遵循协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
|
||||
|
||||
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
|
||||
|
||||
@ -447,7 +447,7 @@ print(d12.textualDescription)
|
||||
// 打印 “A 12-sided dice”
|
||||
```
|
||||
|
||||
同样,`SnakesAndLadders` 类也可以通过扩展遵循并符合 `TextRepresentable` 协议:
|
||||
同样,`SnakesAndLadders` 类也可以通过扩展遵循 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: TextRepresentable {
|
||||
@ -462,7 +462,7 @@ print(game.textualDescription)
|
||||
<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` 协议。
|
||||
|
||||
@ -479,9 +479,9 @@ print(myDice.textualDescription)
|
||||
```
|
||||
|
||||
<a name="declaring_protocol_adoption_with_an_extension"></a>
|
||||
## 在扩展里声明采纳协议
|
||||
## 在扩展里声明遵循协议
|
||||
|
||||
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展采纳该协议:
|
||||
当一个类型已经遵循了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展遵循该协议:
|
||||
|
||||
```swift
|
||||
struct Hamster {
|
||||
@ -549,7 +549,7 @@ protocol PrettyTextRepresentable: TextRepresentable {
|
||||
|
||||
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
|
||||
|
||||
如下所示,扩展 `SnakesAndLadders`,使其遵循并符合 `PrettyTextRepresentable` 协议:
|
||||
如下所示,扩展 `SnakesAndLadders`,使其遵循 `PrettyTextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: PrettyTextRepresentable {
|
||||
@ -587,7 +587,7 @@ print(game.prettyTextualDescription)
|
||||
<a name="class_only_protocol"></a>
|
||||
## 类专属的协议
|
||||
|
||||
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。
|
||||
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型遵循(以及非结构体或者非枚举的类型)。
|
||||
|
||||
```swift
|
||||
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!”
|
||||
```
|
||||
|
||||
`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 协议进行组合:
|
||||
|
||||
@ -667,12 +667,12 @@ beginConcert(in: seattle)
|
||||
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是一个 Location 的子类。就像,如果你新建一个类继承与 Location,但是没有遵循 Named 协议,你用这个类的实例去调用 `beginConcert(in:)` 函数也是不合法的。
|
||||
|
||||
<a name="checking_for_protocol_conformance"></a>
|
||||
## 检查协议一致性
|
||||
## 检查协议遵循
|
||||
|
||||
你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
|
||||
你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议遵循,即是否遵循了某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
|
||||
|
||||
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`。
|
||||
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。
|
||||
* `is` 用来检查实例是否遵循某个协议,若遵循则返回 `true`,否则返回 `false`。
|
||||
* `as?` 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。
|
||||
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
|
||||
|
||||
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`:
|
||||
@ -698,7 +698,7 @@ class Country: HasArea {
|
||||
}
|
||||
```
|
||||
|
||||
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地符合了 `HasArea` 协议。
|
||||
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地遵循了 `HasArea` 协议。
|
||||
|
||||
如下所示,`Animal` 是一个未遵循 `HasArea` 协议的类:
|
||||
|
||||
@ -721,7 +721,7 @@ let objects: [AnyObject] = [
|
||||
|
||||
`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。
|
||||
|
||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议:
|
||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否遵循 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
for object in objects {
|
||||
@ -736,7 +736,7 @@ for object in objects {
|
||||
// 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` 属性能够被访问。
|
||||
|
||||
@ -900,7 +900,7 @@ extension PrettyTextRepresentable {
|
||||
|
||||
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.html#where_clauses)中所描述的。
|
||||
|
||||
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。
|
||||
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵循 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。
|
||||
|
||||
```swift
|
||||
extension Collection where Element: Equatable {
|
||||
|
||||
@ -235,11 +235,11 @@ if let topItem = stackOfStrings.topItem {
|
||||
<a name="type_constraints"></a>
|
||||
## 类型约束
|
||||
|
||||
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
|
||||
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者遵循一个特定的协议或协议组合。
|
||||
|
||||
例如,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>
|
||||
### 类型约束实践
|
||||
@ -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
|
||||
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>
|
||||
## 关联类型
|
||||
|
||||
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
|
||||
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被遵循时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
|
||||
|
||||
<a name="associated_types_in_action"></a>
|
||||
### 关联类型实践
|
||||
@ -344,21 +344,21 @@ protocol Container {
|
||||
}
|
||||
```
|
||||
|
||||
`Container` 协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:
|
||||
`Container` 协议定义了三个任何遵循了该协议的类型(即容器)必须提供的功能:
|
||||
|
||||
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
|
||||
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
|
||||
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
|
||||
|
||||
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵从 `Container` 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵循 `Container` 协议的类型必须提供的功能。遵循协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||
|
||||
任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
|
||||
任何遵循 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
|
||||
|
||||
为了定义这三个条件,`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
|
||||
struct IntStack: Container {
|
||||
@ -388,9 +388,9 @@ struct IntStack: Container {
|
||||
|
||||
此外,`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
|
||||
struct Stack<Element>: Container {
|
||||
@ -420,9 +420,9 @@ struct Stack<Element>: Container {
|
||||
<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
|
||||
extension Array: Container {}
|
||||
@ -433,7 +433,7 @@ extension Array: Container {}
|
||||
<a name="using_type_annotations_to_constrain_an_associated_type"></a>
|
||||
### 给关联类型添加约束
|
||||
|
||||
你可以给协议里的关联类型添加类型注释,让遵守协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` 的 `Container` 类型:
|
||||
你可以给协议里的关联类型添加类型注释,让遵循协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` 的 `Container` 类型:
|
||||
|
||||
```swift
|
||||
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>
|
||||
### 在关联类型约束里使用协议
|
||||
@ -502,7 +502,7 @@ extension IntStack: SuffixableContainer {
|
||||
|
||||
[类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。
|
||||
|
||||
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
|
||||
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵循某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
|
||||
|
||||
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。
|
||||
|
||||
@ -534,10 +534,10 @@ func allItemsMatch<C1: Container, C2: Container>
|
||||
|
||||
这个函数的类型参数列表还定义了对两个类型参数的要求:
|
||||
|
||||
- `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
|
||||
- `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
|
||||
- `C1` 必须遵循 `Container` 协议(写作 `C1: Container`)。
|
||||
- `C2` 必须遵循 `Container` 协议(写作 `C2: Container`)。
|
||||
- `C1` 的 `Item` 必须和 `C2` 的 `Item` 类型相同(写作 `C1.Item == C2.Item`)。
|
||||
- `C1` 的 `Item` 必须符合 `Equatable` 协议(写作 `C1.Item: Equatable`)。
|
||||
- `C1` 的 `Item` 必须遵循 `Equatable` 协议(写作 `C1.Item: Equatable`)。
|
||||
|
||||
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
|
||||
|
||||
@ -576,7 +576,7 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
||||
// 打印 “All items match.”
|
||||
```
|
||||
|
||||
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
||||
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵循 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
||||
|
||||
<a name="extensions_with_a_generic_where_clause"></a>
|
||||
## 具有泛型 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(_:)` 方法的调用方式:
|
||||
|
||||
@ -607,7 +607,7 @@ if stackOfStrings.isTop("tres") {
|
||||
// 打印 "Top element is tres."
|
||||
```
|
||||
|
||||
如果尝试在其元素不符合 Equatable 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
|
||||
如果尝试在其元素未遵循 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
|
||||
|
||||
```swift
|
||||
struct NotEquatable { }
|
||||
@ -627,7 +627,7 @@ extension Container where Item: Equatable {
|
||||
}
|
||||
```
|
||||
|
||||
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 Equatable 协议的。
|
||||
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何遵循 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是遵循 `Equatable` 协议的。
|
||||
|
||||
```swift
|
||||
if [9, 9, 9].startsWith(42) {
|
||||
@ -638,7 +638,7 @@ if [9, 9, 9].startsWith(42) {
|
||||
// 打印 "Starts with something else."
|
||||
```
|
||||
|
||||
上述示例中的泛型 `where` 子句要求 `Item` 符合协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
|
||||
上述示例中的泛型 `where` 子句要求 `Item` 遵循协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
|
||||
|
||||
```swift
|
||||
extension Container where Item == Double {
|
||||
@ -705,7 +705,7 @@ extension Container {
|
||||
|
||||
这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:
|
||||
|
||||
- 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。
|
||||
- 在尖括号中的泛型参数 `Indices`,必须是遵循标准库中的 `Sequence` 协议的类型。
|
||||
- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
|
||||
- 泛型 `where` 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ Swift 使用*自动引用计数(ARC)*机制来跟踪和管理你的应用程
|
||||
> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
|
||||
|
||||
<a name="how_arc_works"></a>
|
||||
|
||||
## 自动引用计数的工作机制
|
||||
|
||||
当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
|
||||
@ -39,7 +40,7 @@ class Person {
|
||||
}
|
||||
```
|
||||
|
||||
`Person` 类有一个构造函数,此构造函数为实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息。
|
||||
`Person` 类有一个构造器,此构造器为实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构器,这个析构器会在实例被销毁时打印一条消息。
|
||||
|
||||
接下来的代码片段定义了三个类型为 `Person?` 的变量,用来按照代码片段中的顺序,为新的 `Person` 实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是 `Person`),它们的值会被自动初始化为 `nil`,目前还不会引用到 `Person` 类的实例。
|
||||
|
||||
@ -56,7 +57,7 @@ reference1 = Person(name: "John Appleseed")
|
||||
// 打印 "John Appleseed is being initialized"
|
||||
```
|
||||
|
||||
应当注意到当你调用 `Person` 类的构造函数的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造函数被执行。
|
||||
应当注意到当你调用 `Person` 类的构造器的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造器被执行。
|
||||
|
||||
由于 `Person` 类的新实例被赋值给了 `reference1` 变量,所以 `reference1` 到 `Person` 类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证 `Person` 实例被保持在内存中不被销毁。
|
||||
|
||||
@ -114,7 +115,7 @@ class Apartment {
|
||||
|
||||
类似的,每个 `Apartment` 实例有一个叫 `unit`,类型为 `String` 的属性,并有一个可选的初始化为 `nil` 的 `tenant` 属性。`tenant` 属性是可选的,因为一栋公寓并不总是有居民。
|
||||
|
||||
这两个类都定义了析构函数,用以在类实例被析构的时候输出信息。这让你能够知晓 `Person` 和 `Apartment` 的实例是否像预期的那样被销毁。
|
||||
这两个类都定义了析构器,用以在类实例被析构的时候输出信息。这让你能够知晓 `Person` 和 `Apartment` 的实例是否像预期的那样被销毁。
|
||||
|
||||
接下来的代码片段定义了两个可选类型的变量 `john` 和 `unit4A`,并分别被设定为下面的 `Apartment` 和 `Person` 的实例。这两个变量都被初始化为 `nil`,这正是可选类型的优点:
|
||||
|
||||
@ -132,7 +133,7 @@ unit4A = Apartment(unit: "4A")
|
||||
|
||||
在两个实例被创建和赋值后,下图表现了强引用的关系。变量 `john` 现在有一个指向 `Person` 实例的强引用,而变量 `unit4A` 有一个指向 `Apartment` 实例的强引用:
|
||||
|
||||

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||
|
||||
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁:
|
||||
`Person` 实例依然保持对 `Apartment` 实例的强引用,但是 `Apartment` 实例只持有对 `Person` 实例的弱引用。这意味着当你通过把 `john` 变量赋值为 `nil` 而断开其所保持的强引用时,再也没有指向 `Person` 实例的强引用了:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// 打印 "John Appleseed is being deinitialized"
|
||||
```
|
||||
|
||||
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁,且 `tenant` 属性会被赋值为 `nil`:
|
||||
|
||||

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

|
||||
|
||||
由于再也没有指向 `Apartment` 实例的强引用,该实例也会被销毁:
|
||||
|
||||
```swift
|
||||
unit4A = nil
|
||||
// 打印 "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` 属性。
|
||||
|
||||
此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造函数的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。
|
||||
此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造器的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。
|
||||
|
||||
由于信用卡总是关联着一个客户,因此将 `customer` 属性定义为无主引用,用以避免循环强引用:
|
||||
|
||||
@ -306,13 +306,13 @@ john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
|
||||
|
||||
在你关联两个实例后,它们的引用关系如下图所示:
|
||||
|
||||

|
||||

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

|
||||

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

|
||||

|
||||
|
||||
实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name` 和 `self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。
|
||||
|
||||
@ -474,7 +474,7 @@ print(paragraph!.asHTML())
|
||||
paragraph = nil
|
||||
```
|
||||
|
||||
注意,`HTMLElement` 的析构函数中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。
|
||||
注意,`HTMLElement` 的析构器中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。
|
||||
|
||||
<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
|
||||
paragraph = nil
|
||||
|
||||
@ -322,9 +322,9 @@ public struct TrackedString {
|
||||
<a name="protocols"></a>
|
||||
## 协议
|
||||
|
||||
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。
|
||||
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被遵循。
|
||||
|
||||
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。
|
||||
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意遵循者都将可用。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -336,17 +336,17 @@ public struct TrackedString {
|
||||
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
|
||||
|
||||
<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>
|
||||
## Extension
|
||||
@ -374,7 +374,7 @@ protocol SomeProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
你可以使用 extension 来遵守协议,就想这样:
|
||||
你可以使用 extension 来遵循协议,就像这样:
|
||||
|
||||
```swift
|
||||
struct SomeStruct {
|
||||
@ -400,4 +400,4 @@ extension SomeStruct: SomeProtocol {
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。
|
||||
> 这条规则也适用于为满足协议遵循而将类型别名用于关联类型的情况。
|
||||
|
||||
Reference in New Issue
Block a user