diff --git a/source/chapter2/01_The_Basics.md b/source/chapter2/01_The_Basics.md index b1b7c5a9..1cce7140 100755 --- a/source/chapter2/01_The_Basics.md +++ b/source/chapter2/01_The_Basics.md @@ -19,6 +19,9 @@ > 校对:[CMB](https://github.com/chenmingbiao),版本时间2016-09-13 > > 3.0.1, 2016-11-11,shanks + +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io) 本页包含内容: @@ -60,7 +63,7 @@ Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int`表示整 Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示 “那儿有一个值,并且它等于 *x* ” 或者 “那儿没有值” 。可选有点像在 Objective-C 中使用 `nil` ,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 `nil` 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。 -Swift 是一门*类型安全*的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码期望得到一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码期望得到一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。类型安全可以帮助你在开发阶段尽早发现并修正错误。 +Swift 是一门*类型安全*的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码需要一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码需要一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。类型安全可以帮助你在开发阶段尽早发现并修正错误。 ## 常量和变量 @@ -787,17 +790,17 @@ do { ### 使用断言进行调试 -你可以使用全局 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: +你可以调用 Swift 标准库的 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: ```swift let age = -3 -assert(age >= 0, "A person's age cannot be less than zero") +assert(age >= 0, "A person's age cannot be less than zero") // 因为 age < 0,所以断言会触发 ``` -在这个例子中,只有 `age >= 0` 为 `true` 的时候,即 `age` 的值非负的时候,代码才会继续执行。如果 `age` 的值是负数,就像代码中那样,`age >= 0` 为 `false`,断言被触发,终止应用。 +在这个例子中,只有 `age >= 0` 为 `true` 时,即 `age` 的值非负的时候,代码才会继续执行。如果 `age` 的值是负数,就像代码中那样,`age >= 0` 为 `false`,断言被触发,终止应用。 -如果不需要断言信息,可以省略,就像这样: +如果不需要断言信息,可以就像这样忽略掉: ```swift assert(age >= 0) @@ -822,12 +825,13 @@ if age > 10 { 你可以使用全局 `precondition(_:_:file:line:)` 函数来写一个先决条件。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: ```swift -// In the implementation of a subscript... +// 在一个下标的实现里... precondition(index > 0, "Index must be greater than zero.") ``` -你可以调用 `precondition(_:_:file:line:)`方法来表明出现了一个错误,例如,switch进入了default分支,但是所有的有效值应该被任意一个其他分支(非default分支)处理。 +你可以调用 `precondition(_:_:file:line:)`方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。 > 注意: > 如果你使用unchecked模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为true(真),他将优化你的代码。然而,`fatalError(_:file:line:)`函数总是中断执行,无论你怎么进行优化设定。 ->你能使用 `fatalError(_:file:line:)`函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上fatalError("Unimplemented")作为具体实现。因为fatalError不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。 \ No newline at end of file +>你能使用 `fatalError(_:file:line:)`函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上fatalError("Unimplemented")作为具体实现。因为fatalError不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。 + diff --git a/source/chapter2/02_Basic_Operators.md b/source/chapter2/02_Basic_Operators.md index 9f2b2b7c..13dd7322 100755 --- a/source/chapter2/02_Basic_Operators.md +++ b/source/chapter2/02_Basic_Operators.md @@ -14,6 +14,9 @@ > 2.2 > 翻译+校对:[Cee](https://github.com/Cee) 校对:[SketchK](https://github.com/SketchK),2016-05-11 > 3.0.1,shanks,2016-11-11 + +> 4.0 +> 翻译+校对:[kemchenj](https://kemchenj.github.io) 本页包含内容: @@ -31,7 +34,7 @@ Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+`,`-`,`*`,`/`,`%`等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。 -Swift 还提供了 C 语言没有的表达两数之间的值的区间运算符(`a.. 注意: -求余运算符(`%`)在其他语言也叫*取模运算符*。然而严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。 +求余运算符(`%`)在其他语言也叫*取模运算符*。但是严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。 我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中: @@ -184,7 +187,7 @@ a += 2 > 注意: 复合赋值运算没有返回值,`let b = a += 2`这类代码是错误。这不同于上面提到的自增和自减运算符。 -在[Swift 标准库运算符参考](https://developer.apple.com/reference/swift/1851035-swift_standard_library_operators)章节里有复合运算符的完整列表。 +更多 Swift 标准库运算符的信息,请看[运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。 ‌ ## 比较运算符(Comparison Operators) @@ -226,9 +229,7 @@ if name == "world" { 关于 `if` 语句,请看[控制流](../chapter2/05_Control_Flow.html)。 -当元组中的值可以比较时,你也可以使用这些运算符来比较它们的大小。例如,因为 `Int` 和 `String` 类型的值可以比较,所以类型为 `(Int, String)` 的元组也可以被比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。 - -比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如: +如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如: ```swift (1, "zebra") < (2, "apple") // true,因为 1 小于 2 @@ -236,7 +237,14 @@ if name == "world" { (4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog ``` -在上面的例子中,你可以看到,在第一行中从左到右的比较行为。因为`1`小于`2`,所以`(1, "zebra")`小于`(2, "apple")`,不管元组剩下的值如何。所以`"zebra"`大于`"apple"`对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。不过,当元组的第一个元素相同时候,第二个元素将会用作比较-第二行和第三行代码就发生了这样的比较。 +在上面的例子中,你可以看到,在第一行中从左到右的比较行为。因为`1`小于`2`,所以`(1, "zebra")`小于`(2, "apple")`,不管元组剩下的值如何。所以`"zebra"`大于`"apple"`对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。不过,当元组的第一个元素相同时候,第二个元素将会用作比较-第二行和第三行代码就发生了这样的比较。 + +当元组中的元素都可以被比较时,你也可以使用这些运算符来比较它们的大小。例如,像下面展示的代码,你可以比较两个类型为 `(String, Int)` 的元组,因为 `Int` 和 `String` 类型的值可以比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。 + +```swift +("blue", -1) < ("purple", 1) // 正常,比较的结果为 true +("blue", false) < ("purple", true) // 错误,因为 < 不能比较布尔类型 +``` >注意: Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。 @@ -323,7 +331,7 @@ colorNameToUse = userDefinedColorName ?? defaultColorName ## 区间运算符(Range Operators) -Swift 提供了两个方便表达一个区间的值的*区间运算符*。 +Swift 提供了几种方便表达一个区间的值的*区间运算符*。 ### 闭区间运算符 *闭区间运算符*(`a...b`)定义一个包含从 `a` 到 `b`(包括 `a` 和 `b`)的所有值的区间。`a` 的值不能超过 `b`。 @@ -363,6 +371,44 @@ for i in 0.. ## 逻辑运算符(Logical Operators) @@ -465,3 +511,5 @@ if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword ``` 这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧! + + diff --git a/source/chapter2/03_Strings_and_Characters.md b/source/chapter2/03_Strings_and_Characters.md index f806680b..8b21488b 100755 --- a/source/chapter2/03_Strings_and_Characters.md +++ b/source/chapter2/03_Strings_and_Characters.md @@ -18,11 +18,14 @@ > 3.0 > 校对:[CMB](https://github.com/chenmingbiao),版本日期:2016-09-13 > 3.0.1, shanks, 2016-11-11 + +> 4.0 +> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21 本页包含内容: - [字符串字面量](#string_literals) -- [多行字符串字面量](#multiline_string_literals) +- [字面量中的特殊字符](#special_characters_in_string_literals) - [初始化空字符串](#initializing_an_empty_string) - [字符串可变性](#string_mutability) - [字符串是值类型](#strings_are_value_types) @@ -32,6 +35,7 @@ - [Unicode](#unicode) - [计算字符数量](#counting_characters) - [访问和修改字符串](#accessing_and_modifying_a_string) +- [子字符串](#substrings) - [比较字符串](#comparing_strings) - [字符串的 Unicode 表示形式](#unicode_representations_of_strings) @@ -46,13 +50,14 @@ Swift 的`String`和`Character`类型提供了快速和兼容 Unicode 的方式 > 注意: > Swift 的`String`类型与 Foundation `NSString`类进行了无缝桥接。Foundation 也可以对`String`进行扩展,暴露在`NSString`中定义的方法。 这意味着,如果你在`String`中调用这些`NSString`的方法,将不用进行转换。 -> 更多关于在 Foundation 和 Cocoa 中使用`String`的信息请查看 *[Using Swift with Cocoa and Objective-C (Swift 3.0.1)](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)*。 +> 更多关于在 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)*。 ## 字符串字面量 -您可以在您的代码中包含一段预定义的字符串值作为字符串字面量。字符串字面量是由双引号 (`""`) 包裹着的具有固定顺序的文本字符集。 +你可以在代码里使用一段预定义的字符串值作为字符串字面量。字符串字面量是由一对双引号包裹着的具有固定顺序的字符集。 + 字符串字面量可以用于为常量和变量提供初始值: ```swift @@ -61,13 +66,10 @@ let someString = "Some string literal value" 注意`someString`常量通过字符串字面量进行初始化,Swift 会推断该常量为`String`类型。 -> 注意: -更多关于在字符串字面量中使用特殊字符的信息,请查看 [字符串字面量的特殊字符](#special_characters_in_string_literals) 。 - -## 多行字符串字面量 +### 多行字符串字面量 -如果你需要一个字符串是跨越多行的,你可以使用多行字符串字面量,它是由三个双引号(`"""`)包裹着的具有固定顺序的文本字符集。 +如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 —— 由一对三个双引号包裹着的具有固定顺序的文本字符集: ```swift let quotation = """ @@ -79,7 +81,7 @@ till you come to the end; then stop." """ ``` -一个多行字符串字面量包含了所有的在开启和关闭引号(`"""`)中的行。这个字符从开启引号(`"""`)之后的第一行开始,到关闭引号(`"""`)之前为止。这就意味着字符串开启引号之后(`"""`)或者结束引号(`"""`)之前都没有一个换行符号。(译者:下面两个字符串其实是一样的,虽然第二个使用了多行字符串的形势) +一个多行字符串字面量包含了所有的在开启和关闭引号(`"""`)中的行。这个字符从开启引号(`"""`)之后的第一行开始,到关闭引号(`"""`)之前为止。这就意味着字符串开启引号之后(`"""`)或者结束引号(`"""`)之前都没有换行符号。(译者:下面两个字符串其实是一样的,虽然第二个使用了多行字符串的形式) ```swift let singleLineString = "These are the same." @@ -88,7 +90,7 @@ These are the same. """ ``` -如果你的代码中,多行字符串字面量包含换行符的话,则多行字符串字面量中也会包含换行符。如果你想使用换行,使得你的代码获得好的可读性,但是你又不想在你的多行字符串字面量中出现换行符的话,你可以用在行尾写一个反斜杠(`\`)作为续行符。 +如果你的代码中,多行字符串字面量包含换行符的话,则多行字符串字面量中也会包含换行符。如果你想换行,以便加强代码的可读性,但是你又不想在你的多行字符串字面量中出现换行符的话,你可以用在行尾写一个反斜杠(`\`)作为续行符。 ```swift let softWrappedQuotation = """ @@ -115,8 +117,36 @@ It also ends with a line break. ![](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Art/multilineStringWhitespace_2x.png) -在上面的例子中,尽管整个多行字符串字面量都是缩进的(源代码缩进),第一行和最后一行没有以空白字符串开始(实际的变量值)。中间一行的缩进用空白字符串(源代码缩进)比关闭引号(`"""`)之前的空白字符串多,所以,它的行首将有4个空白字符串(实际的变量值)。 +在上面的例子中,尽管整个多行字符串字面量都是缩进的(源代码缩进),第一行和最后一行没有以空白字符串开始(实际的变量值)。中间一行的缩进用空白字符串(源代码缩进)比关闭引号(`"""`)之前的空白字符串多,所以,它的行首将有4个空格。 + +### 字符串字面量的特殊字符 + +字符串字面量可以包含以下特殊字符: + +* 转义字符`\0`(空字符)、`\\`(反斜线)、`\t`(水平制表符)、`\n`(换行符)、`\r`(回车符)、`\"`(双引号)、`\'`(单引号)。 +* Unicode 标量,写成`\u{n}`(u为小写),其中`n`为任意一到八位十六进制数且可用的 Unicode 位码。 + +下面的代码为各种特殊字符的使用示例。 +`wiseWords`常量包含了两个双引号。 +`dollarSign`、`blackHeart`和`sparklingHeart`常量演示了三种不同格式的 Unicode 标量: + +```swift +let wiseWords = "\"Imagination is more important than knowledge\" - Einstein" +// "Imageination is more important than knowledge" - Enistein +let dollarSign = "\u{24}" // $, Unicode 标量 U+0024 +let blackHeart = "\u{2665}" // ♥, Unicode 标量 U+2665 +let sparklingHeart = "\u{1F496}" // 💖, Unicode 标量 U+1F496 +``` + +由于多行字符串字面量使用了三个双引号,而不是一个,所以你可以在多行字符串字面量里直接使用双引号(`"`)而不必加上转义符(`\`)。要在多行字符串字面量中使用 `"""` 的话,就需要使用至少一个转义符(`\`): + +```swift +let threeDoubleQuotes = """ +Escaping the first quote \""" +Escaping all three quotes \"\"\" +""" +``` ## 初始化空字符串 @@ -176,10 +206,10 @@ Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字 ## 使用字符 -您可通过`for-in`循环来遍历字符串中的`characters`属性来获取每一个字符的值: +您可通过`for-in`循环来遍历字符串,获取字符串中每一个字符的值: ```swift -for character in "Dog!🐶".characters { +for character in "Dog!🐶" { print(character) } // D @@ -205,7 +235,6 @@ print(catString) // 打印输出:"Cat!🐱" ``` - ## 连接字符串和字符 @@ -237,11 +266,39 @@ welcome.append(exclamationMark) > 注意: 您不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。 +如果你需要使用多行字符串字面量来拼接字符串,并且你需要字符串每一行都以换行符结尾,包括最后一行: + +```swift +let badStart = """ +one +two +""" +let end = """ +three +""" +print(badStart + end) +// 打印两行: +// one +// twothree + +let goodStart = """ +one +two + +""" +print(goodStart + end) +// 打印三行: +// one +// two +// three +``` + +上面的代码,把 `badStart` 和 `end` 拼接起来的字符串非我们想要的结果。因为 `badStart` 最后一行没有换行符,它与 `end` 的第一行结合到了一起。相反的,`goodStart` 的每一行都以换行符结尾,所以它与 `end` 拼接的字符串总共有三行,正如我们期望的那样。 ## 字符串插值 -*字符串插值*是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。 +*字符串插值*是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。**字符串字面量**和**多行字符串字面量**都可以使用字符串插值。 您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中: ```swift @@ -281,24 +338,7 @@ Unicode 标量是对应字符或者修饰符的唯一的21位数字,例如`U+0 注意不是所有的21位 Unicode 标量都代表一个字符,因为有一些标量是留作未来分配的。已经代表一个典型字符的标量都有自己的名字,例如上面例子中的`LATIN SMALL LETTER A`和`FRONT-FACING BABY CHICK`。 -### 字符串字面量的特殊字符 -字符串字面量可以包含以下特殊字符: - -* 转义字符`\0`(空字符)、`\\`(反斜线)、`\t`(水平制表符)、`\n`(换行符)、`\r`(回车符)、`\"`(双引号)、`\'`(单引号)。 -* Unicode 标量,写成`\u{n}`(u为小写),其中`n`为任意一到八位十六进制数且可用的 Unicode 位码。 - -下面的代码为各种特殊字符的使用示例。 -`wiseWords`常量包含了两个双引号。 -`dollarSign`、`blackHeart`和`sparklingHeart`常量演示了三种不同格式的 Unicode 标量: - -```swift -let wiseWords = "\"Imagination is more important than knowledge\" - Einstein" -// "Imageination is more important than knowledge" - Enistein -let dollarSign = "\u{24}" // $, Unicode 标量 U+0024 -let blackHeart = "\u{2665}" // ♥, Unicode 标量 U+2665 -let sparklingHeart = "\u{1F496}" // 💖, Unicode 标量 U+1F496 -``` ### 可扩展的字形群集 @@ -346,11 +386,11 @@ let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}" ## 计算字符数量 -如果想要获得一个字符串中`Character`值的数量,可以使用字符串的`characters`属性的`count`属性: +如果想要获得一个字符串中`Character`值的数量,可以使用`count`属性: ```swift let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪" -print("unusualMenagerie has \(unusualMenagerie.characters.count) characters") +print("unusualMenagerie has \(unusualMenagerie.count) characters") // 打印输出 "unusualMenagerie has 40 characters" ``` @@ -360,19 +400,19 @@ print("unusualMenagerie has \(unusualMenagerie.characters.count) characters") ```swift var word = "cafe" -print("the number of characters in \(word) is \(word.characters.count)") +print("the number of characters in \(word) is \(word.count)") // 打印输出 "the number of characters in cafe is 4" -word += "\u{301}" // COMBINING ACUTE ACCENT, U+0301 +word += "\u{301}" // 拼接一个重音, U+0301 -print("the number of characters in \(word) is \(word.characters.count)") +print("the number of characters in \(word) is \(word.count)") // 打印输出 "the number of characters in café is 4" ``` > 注意: -> 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果您正在处理一个长字符串,需要注意`characters`属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。 +> 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果您正在处理一个长字符串,需要注意`count`属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。 > -> 另外需要注意的是通过`characters`属性返回的字符数量并不总是与包含相同字符的`NSString`的`length`属性相同。`NSString`的`length`属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。 +> 另外需要注意的是通过`count`属性返回的字符数量并不总是与包含相同字符的`NSString`的`length`属性相同。`NSString`的`length`属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。 @@ -413,10 +453,10 @@ greeting[greeting.endIndex] // error greeting.index(after: endIndex) // error ``` -使用 `characters` 属性的 `indices` 属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。 +使用 `indices` 属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。 ```swift -for index in greeting.characters.indices { +for index in greeting.indices { print("\(greeting[index]) ", terminator: "") } // 打印输出 "G u t e n T a g ! " @@ -435,7 +475,7 @@ var welcome = "hello" welcome.insert("!", at: welcome.endIndex) // welcome 变量现在等于 "hello!" -welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome.endIndex)) +welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex)) // welcome 变量现在等于 "hello there!" ``` @@ -453,6 +493,29 @@ welcome.removeSubrange(range) > 注意: > 您可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set` 中。 + +## 子字符串 + +当你从字符串中获取一个子字符串 —— 例如,使用下标或者 `prefix(_:)` 之类的方法 —— 就可以得到一个 `SubString` 的实例,而非另外一个 `String`。Swift 里的 `SubString` 绝大部分函数都跟 `String` 一样,意味着你可以使用同样的方式去操作 `SubString` 和 `String`。然而,跟 `String` 不同的是,你只有在短时间内需要操作字符串时,才会使用 `SubString`。当你需要长时间保存结果时,就把 `SubString` 转化为 `String` 的实例: + +```swift +let greeting = "Hello, world!" +let index = greeting.index(of: ",") ?? greeting.endIndex +let beginning = greeting[.. 注意 +`String` 和 `SubString` 都遵循 `StringProtocol` 协议,这意味着操作字符串的函数使用 `StringProtocol` 会更加方便。你可以传入 `String` 或 `SubString` 去调用函数。 ## 比较字符串 @@ -755,3 +818,5 @@ for scalar in dogString.unicodeScalars { // ‼ // 🐶 ``` + + diff --git a/source/chapter2/05_Control_Flow.md b/source/chapter2/05_Control_Flow.md index 668a6eb2..e56d1578 100755 --- a/source/chapter2/05_Control_Flow.md +++ b/source/chapter2/05_Control_Flow.md @@ -23,6 +23,9 @@ > 3.1 > 翻译:[qhd](https://github.com/qhd) 2017-04-17 +> 4.0 +> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [For-In 循环](#for_in_loops) @@ -34,14 +37,14 @@ Swift提供了多种流程控制结构,包括可以多次执行任务的`while`循环,基于特定条件选择执行不同代码分支的`if`、`guard`和`switch`语句,还有控制流程跳转到其他代码位置的`break`和`continue`语句。 -Swift 还提供了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。 +Swift 还提供了`for-in`循环,用来更简单地遍历数组(Array),字典(Dictionary),区间(Range),字符串(String)和其他序列类型。 -Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿的情况。case 还可以匹配很多不同的模式,包括间隔匹配(interval match),元组(tuple)和转换到特定类型。`switch`语句的 case 中匹配的值可以绑定成临时常量或变量,在case体内使用,也可以用`where`来描述更复杂的匹配条件。 +Swift 的`switch`语句比 C 语言中更加强大。case 还可以匹配很多不同的模式,包括范围匹配,元组(tuple)和特定类型匹配。`switch`语句的 case 中匹配的值可以声明为临时常量或变量,在 case 作用域内使用,也可以配合`where`来描述更复杂的匹配条件。 ## For-In 循环 -你可以使用 `for-in` 循环来遍历一个集合中的所有元素,例如数组中的元素、数字范围或者字符串中的字符。 +你可以使用 `for-in` 循环来遍历一个集合中的所有元素,例如数组中的元素、范围内的数字或者字符串中的字符。 以下例子使用 `for-in` 遍历一个数组所有元素: @@ -56,7 +59,7 @@ for name in names { // Hello, Jack! ``` -你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以 `(key, value)` 元组的形式返回,你可以在 `for-in` 循环中使用显式的常量名称来解读 `(key, value)` 元组。下面的例子中,字典的键解读为常量 `animalName`,字典的值会被解读为常量 `legCount`: +你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以 `(key, value)` 元组的形式返回,你可以在 `for-in` 循环中使用显式的常量名称来解读 `(key, value)` 元组。下面的例子中,字典的键声明会为 `animalName` 常量,字典的值会声明为 `legCount` 常量: ```swift let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] @@ -68,9 +71,9 @@ for (animalName, legCount) in numberOfLegs { // cats have 4 legs ``` -字典的内容本质上是无序的,遍历元素时不能保证顺序。特别地,将元素插入一个字典的顺序并不会决定它们被遍历的顺序。关于数组和字典,详情参见[集合类型](./04_Collection_Types.html)。 +字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见[集合类型](./04_Collection_Types.html)。 -`for-in` 循环还可以使用数字范围。下面的例子用来输出乘 5 乘法表前面一部分内容: +`for-in` 循环还可以使用数字范围。下面的例子用来输出乘法表的一部分内容: ```swift for index in 1...5 { @@ -102,12 +105,12 @@ 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)。 ``` let minutes = 60 for tickMark in 0.. 注意: -> 如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。 +> 如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。 当本轮`while`循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为`false`,此时游戏结束。 @@ -255,7 +258,7 @@ print("Game over!") 检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动`diceRoll`个方格,本轮循环结束。 -循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while`表现得比`while`循环更好。`repeat-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以去掉`while`版本中的数组越界判断。 +循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while`表现得比`while`循环更好。`repeat-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以比起前面 `while` 循环的版本,可以省去数组越界的检查。 ## 条件语句 @@ -416,7 +419,7 @@ case 分支的模式也可以是一个值的区间。下面的例子展示了如 ```swift let approximateCount = 62 let countedThings = "moons orbiting Saturn" -var naturalCount: String +let naturalCount: String switch approximateCount { case 0: naturalCount = "no" @@ -450,15 +453,15 @@ print("There are \(naturalCount) \(countedThings).") let somePoint = (1, 1) switch somePoint { case (0, 0): - print("(0, 0) is at the origin") + print("\(somePoint) is at the origin") case (_, 0): - print("(\(somePoint.0), 0) is on the x-axis") + print("\(somePoint) is on the x-axis") case (0, _): - print("(0, \(somePoint.1)) is on the y-axis") + print("\(somePoint) is on the y-axis") case (-2...2, -2...2): - print("(\(somePoint.0), \(somePoint.1)) is inside the box") + print("\(somePoint) is inside the box") default: - print("(\(somePoint.0), \(somePoint.1)) is outside of the box") + print("\(somePoint) is outside of the box") } // 输出 "(1, 1) is inside the box" ``` @@ -473,9 +476,9 @@ default: #### 值绑定(Value Bindings) -case 分支允许将匹配的值绑定到一个临时的常量或变量,并且在case分支体内使用 —— 这种行为被称为*值绑定*(value binding),因为匹配的值在case分支体内,与临时的常量或变量绑定。 +case 分支允许将匹配的值声明为临时常量或变量,并且在case分支体内使用 —— 这种行为被称为*值绑定*(value binding),因为匹配的值在case分支体内,与临时的常量或变量绑定。 -下面的例子展示了如何在一个`(Int, Int)`类型的元组中使用值绑定来分类下图中的点(x, y): +下面的例子将下图中的点(x, y),使用`(Int, Int)`类型的元组表示,然后分类表示: ```swift let anotherPoint = (2, 0) @@ -589,7 +592,7 @@ default: ```swift let puzzleInput = "great minds think alike" var puzzleOutput = "" -for character in puzzleInput.characters { +for character in puzzleInput { switch character { case "a", "e", "i", "o", "u", " ": continue @@ -606,7 +609,7 @@ print(puzzleOutput) ### Break -`break`语句会立刻结束整个控制流的执行。当你想要更早的结束一个`switch`代码块或者一个循环体时,你都可以使用`break`语句。 +`break`语句会立刻结束整个控制流的执行。`break` 可以在 `switch` 或循环语句中使用,用来提前结束`switch`或循环语句。 #### 循环语句中的 break @@ -657,7 +660,7 @@ if let integerValue = possibleIntegerValue { ### 贯穿 -Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显式地插入`break`语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。 +在 Swift 里,`switch`语句不会从上一个 case 分支跳转到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显式地插入`break`语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。 如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。 @@ -801,9 +804,9 @@ if #available(iOS 10, macOS 10.12, *) { } ``` -以上可用性条件指定,在iOS中,`if`语句的代码块仅仅在 iOS 10 及更高的系统下运行;在 macOS中,仅在 macOS 10.12 及更高才会运行。最后一个参数,`*`,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行。 +以上可用性条件指定,`if`语句的代码块仅仅在 iOS 10 或 macOS 10.12 及更高版本才运行。最后一个参数,`*`,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行。 -在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是`iOS`,`macOS`,`watchOS`和`tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8的主板本号,我们可以指定像iOS 8.3 以及 macOS 10.10.3的子版本号。 +在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是`iOS`,`macOS`,`watchOS`和`tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 8.3 以及 macOS 10.10.3 的小版本号。 ```swift if #available(platform name version, ..., *) { diff --git a/source/chapter2/06_Functions.md b/source/chapter2/06_Functions.md index 492f5858..6d506922 100755 --- a/source/chapter2/06_Functions.md +++ b/source/chapter2/06_Functions.md @@ -20,6 +20,9 @@ > 校对: [shanks](http://codebuild.me) 2016-09-27 > 3.0.1,shanks,2016-11-12 +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [函数定义与调用](#Defining_and_Calling_Functions) - [函数参数与返回值](#Function_Parameters_and_Return_Values) @@ -146,7 +149,7 @@ greet(person: "Dave") ```swift func printAndCount(string: String) -> Int { print(string) - return string.characters.count + return string.count } func printWithoutCounting(string: String) { let _ = printAndCount(string: string) diff --git a/source/chapter2/07_Closures.md b/source/chapter2/07_Closures.md index 984f6010..4df68dfe 100755 --- a/source/chapter2/07_Closures.md +++ b/source/chapter2/07_Closures.md @@ -19,6 +19,9 @@ > 翻译:[Lanford](https://github.com/LanfordCai) 2016-09-19 > 3.0.1,shanks,2016-11-12 +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [闭包表达式](#closure_expressions) diff --git a/source/chapter2/08_Enumerations.md b/source/chapter2/08_Enumerations.md index 02ad61cc..f05d2ef8 100755 --- a/source/chapter2/08_Enumerations.md +++ b/source/chapter2/08_Enumerations.md @@ -20,6 +20,9 @@ > 翻译+校对:[shanks](https://codebuild.me) 2016-09-24 > 3.0.1,shanks,2016-11-12 +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页内容包含: - [枚举语法](#enumeration_syntax) diff --git a/source/chapter2/09_Classes_and_Structures.md b/source/chapter2/09_Classes_and_Structures.md index f03fb521..dc25a4c2 100755 --- a/source/chapter2/09_Classes_and_Structures.md +++ b/source/chapter2/09_Classes_and_Structures.md @@ -15,6 +15,9 @@ > > 3.0.1, shanks, 2016-11-12 +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [类和结构体对比](#comparing_classes_and_structures) diff --git a/source/chapter2/10_Properties.md b/source/chapter2/10_Properties.md index fe71798e..2b09a390 100755 --- a/source/chapter2/10_Properties.md +++ b/source/chapter2/10_Properties.md @@ -15,12 +15,13 @@ > 校对:[shanks](http://codebuild.me),2015-10-29 -> 2.2 -> 翻译:[saitjr](https://github.com/saitjr),2016-04-11,[SketchK](https://github.com/SketchK) 2016-05-13 +> 2.2 +> 翻译:[saitjr](https://github.com/saitjr),2016-04-11,[SketchK](https://github.com/SketchK) 2016-05-13 > > 3.0.1,shanks,2016-11-12 - +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 本页包含内容: diff --git a/source/chapter2/11_Methods.md b/source/chapter2/11_Methods.md index 6bfcab74..2a611974 100755 --- a/source/chapter2/11_Methods.md +++ b/source/chapter2/11_Methods.md @@ -14,6 +14,9 @@ > 2.2 > 校对:[SketchK](https://github.com/SketchK) 2016-05-13 > 3.0.1,shanks,2016-11-13 + +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 本页包含内容: @@ -278,3 +281,5 @@ if player.tracker.advance(to: 6) { } // 打印 "level 6 has not yet been unlocked" ``` + + diff --git a/source/chapter2/12_Subscripts.md b/source/chapter2/12_Subscripts.md index 56e35e45..80532797 100755 --- a/source/chapter2/12_Subscripts.md +++ b/source/chapter2/12_Subscripts.md @@ -14,6 +14,9 @@ > 2.2 > 校对:[SketchK](https://github.com/SketchK) 2016-05-13 > 3.0.1,shanks,2016-11-13 + +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 本页包含内容: @@ -111,16 +114,16 @@ struct Matrix { self.columns = columns grid = Array(count: rows * columns, repeatedValue: 0.0) } - func indexIsValidForRow(row: Int, column: Int) -> Bool { + func indexIsValid(row: Int, column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } subscript(row: Int, column: Int) -> Double { get { - assert(indexIsValidForRow(row, column: column), "Index out of range") + assert(indexIsValid(row: row, column: column), "Index out of range") return grid[(row * columns) + column] } set { - assert(indexIsValidForRow(row, column: column), "Index out of range") + assert(indexIsValid(row: row, column: column), "Index out of range") grid[(row * columns) + column] = newValue } } @@ -150,10 +153,10 @@ matrix[1, 0] = 3.2 ![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/subscriptMatrix02_2x.png) -`Matrix`下标的 getter 和 setter 中都含有断言,用来检查下标入参`row`和`column`的值是否有效。为了方便进行断言,`Matrix`包含了一个名为`indexIsValidForRow(_:column:)`的便利方法,用来检查入参`row`和`column`的值是否在矩阵范围内: +`Matrix`下标的 getter 和 setter 中都含有断言,用来检查下标入参`row`和`column`的值是否有效。为了方便进行断言,`Matrix`包含了一个名为`indexIsValid(row:column:)`的便利方法,用来检查入参`row`和`column`的值是否在矩阵范围内: ```swift -func indexIsValidForRow(row: Int, column: Int) -> Bool { +func indexIsValid(row: Int, column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } ``` diff --git a/source/chapter2/13_Inheritance.md b/source/chapter2/13_Inheritance.md index 6a5b8712..fea5da01 100755 --- a/source/chapter2/13_Inheritance.md +++ b/source/chapter2/13_Inheritance.md @@ -11,6 +11,9 @@ > 2.2 > 校对:[SketchK](https://github.com/SketchK) 2016-05-13 > 3.0.1,shanks,2016-11-13 + +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 本页包含内容: diff --git a/source/chapter2/15_Deinitialization.md b/source/chapter2/15_Deinitialization.md index d79610d0..a620f38b 100755 --- a/source/chapter2/15_Deinitialization.md +++ b/source/chapter2/15_Deinitialization.md @@ -15,6 +15,9 @@ > 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14 > 3.0.1,shanks,2016-11-13 +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [析构过程原理](#how_deinitialization_works) @@ -95,7 +98,7 @@ print("There are now \(Bank.coinsInBank) coins left in the bank") 创建一个`Player`实例的时候,会向`Bank`对象请求 100 个硬币,如果有足够的硬币可用的话。这个`Player`实例存储在一个名为`playerOne`的可选类型的变量中。这里使用了一个可选类型的变量,因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。 -因为`playerOne`是可选的,所以访问其`coinsInPurse`属性来打印钱包中的硬币数量时,使用感叹号(`!`)来解包: +因为`playerOne`是可选的,所以访问其`coinsInPurse`属性来打印钱包中的硬币数量时,使用感叹号(`!`)强制解包: ```swift playerOne!.win(coins: 2_000) diff --git a/source/chapter2/17_Optional_Chaining.md b/source/chapter2/17_Optional_Chaining.md index a7f2a6e1..1a50cb5d 100755 --- a/source/chapter2/17_Optional_Chaining.md +++ b/source/chapter2/17_Optional_Chaining.md @@ -15,6 +15,9 @@ > 2.2 > 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-15 > 3.0.1,shanks,2016-11-13 + +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 本页包含内容: @@ -371,7 +374,7 @@ if let johnsStreet = john.residence?.address?.street { // 打印 “John's street name is Laurel Street.” ``` -在上面的例子中,因为`john.residence`包含一个有效的`Residence`实例,所以对`john.residence`的`address`属性赋值将会成功。 +在上面的例子中,因为`john.residence`包含一个有效的`Address`实例,所以对`john.residence`的`address`属性赋值将会成功。 ## 在方法的可选返回值上进行可选链式调用 diff --git a/source/chapter2/18_Error_Handling.md b/source/chapter2/18_Error_Handling.md index b0034757..4c5d9bdd 100755 --- a/source/chapter2/18_Error_Handling.md +++ b/source/chapter2/18_Error_Handling.md @@ -12,6 +12,9 @@ > 翻译+校对:[shanks](http://codebuild.me) 2016-09-24 > 3.0.1,shanks,2016-11-13 +> 4.0 +> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [表示并抛出错误](#representing_and_throwing_errors) @@ -25,7 +28,7 @@ 举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。 > 注意 -Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的`NSError`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 3.0.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`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 4)](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)。 ## 表示并抛出错误 @@ -231,9 +234,9 @@ let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg") ## 指定清理操作 -可以使用`defer`语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如`return`或者`break`的语句。例如,你可以用`defer`语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。 +可以使用`defer`语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如`return`、`break`的语句。例如,你可以用`defer`语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。 -`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如`break`或是`return`语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条`defer`语句中的代码会在第二条`defer`语句中的代码被执行之后才执行,以此类推。 +`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如`break`、`return`语句,或是抛出一个错误。延迟执行的操作会按照它们声明的顺序从后往前执行——也就是说,第一条`defer`语句中的代码最后才执行,第二条`defer`语句中的代码倒数第二个执行,以此类推。最后一条语句会第一个执行 ```swift func processFile(filename: String) throws { @@ -254,3 +257,5 @@ func processFile(filename: String) throws { > 注意 > 即使没有涉及到错误处理,你也可以使用`defer`语句。 + + diff --git a/source/chapter2/19_Type_Casting.md b/source/chapter2/19_Type_Casting.md index 486254a5..22276e14 100644 --- a/source/chapter2/19_Type_Casting.md +++ b/source/chapter2/19_Type_Casting.md @@ -15,6 +15,9 @@ > 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16 > 3.0.1,shanks,2016-11-13 +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting) diff --git a/source/chapter2/21_Extensions.md b/source/chapter2/21_Extensions.md index 113dc221..fef872d0 100644 --- a/source/chapter2/21_Extensions.md +++ b/source/chapter2/21_Extensions.md @@ -15,6 +15,9 @@ > 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16 > 3.0.1,shanks,2016-11-13 +> 4.0 +> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [扩展语法](#extension_syntax) diff --git a/source/chapter2/23_Generics.md b/source/chapter2/23_Generics.md index 34a25a93..333a6b6e 100644 --- a/source/chapter2/23_Generics.md +++ b/source/chapter2/23_Generics.md @@ -19,6 +19,9 @@ > 3.1:翻译:[qhd](https://github.com/qhd),2017-04-10 +> 4.0 +> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + 本页包含内容: - [泛型所解决的问题](#the_problem_that_generics_solve) @@ -84,7 +87,7 @@ func swapTwoDoubles(_ a: inout Double, _ b: inout Double) { 在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。) > 注意 -在上面三个函数中,`a` 和 `b` 类型相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。 +在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。 ## 泛型函数 @@ -107,9 +110,9 @@ func swapTwoInts(_ a: inout Int, _ b: inout Int) func swapTwoValues(_ a: inout T, _ b: inout T) ``` -这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int`、`String` 或 `Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a` 和 `b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才能根据所传入的实际类型决定 `T` 所代表的类型。 +这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int`、`String` 或 `Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a` 和 `b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才会根据所传入的实际类型决定 `T` 所代表的类型。 -另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(``)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。 +泛型函数和非泛型函数的另外一个不同之处,在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(``)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。 `swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来。 @@ -164,8 +167,8 @@ swapTwoValues(&someString, &anotherString) 1. 现在有三个值在栈中。 2. 第四个值被压入到栈的顶部。 3. 现在有四个值在栈中,最近入栈的那个值在顶部。 -4. 栈中最顶部的那个值被移除,或称之为出栈。 -5. 移除掉一个值后,现在栈又只有三个值了。 +4. 栈中最顶部的那个值被移除出栈。 +5. 一个值移除出栈后,现在栈又只有三个值了。 下面展示了如何编写一个非泛型版本的栈,以 `Int` 型的栈为例: @@ -461,10 +464,26 @@ extension Array: Container {} 如同上面的泛型 `Stack` 结构体一样,`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `ItemType` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。 - -## 泛型 Where 语句 + +### 约束关联类型 -[类型约束](#type_constraints)让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。 +你可以给协议里的关联类型添加类型注释,让遵守协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` 的 `Container` 类型: + +```swift +protocol Container { + associatedtype Item: Equatable + mutating func append(_ item: Item) + var count: Int { get } + subscript(i: Int) -> Item { get } +} +``` + +为了遵守了 `Container` 协议,Item 类型也必须遵守 `Equatable` 协议。 + + +## 泛型 where 语句 + +[类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。 为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。 @@ -622,11 +641,10 @@ print([1260.0, 1200.0, 98.6, 37.0].average()) 就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。 - -## 具有泛型 Where 子句的关联类型 +## 具有泛型 where 子句的关联类型 -你能够包含一个具有泛型 Where 子句的关联类型。例如建立一个包含迭代器(Iterator)的容器,就像是标准库中使用的Sequence协议那样。下面是你所写的代码: +你可以在关联类型后面加上具有泛型 `where` 的字句。例如,建立一个包含迭代器(Iterator)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写: ```swift protocol Container { @@ -640,9 +658,9 @@ protocol Container { } ``` -迭代器(Iterator)的泛型Where子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。makeIterator()则提供了容器的迭代器的访问接口。 +迭代器(Iterator)的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。 -一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型Where子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个ComparableContainer协议,它要求所有的Item必须是Comparable的。 +一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 `where` 子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 `ComparableContainer` 协议,它要求所有的 `Item` 必须是 `Comparable` 的。 ```swift protocol ComparableContainer: Container where Item: Comparable { } @@ -651,7 +669,7 @@ protocol ComparableContainer: Container where Item: Comparable { } ##泛型下标 -下标能够是泛型的,他们能够包含泛型Where子句。你可以在subscript后面的尖括号中,写下占位符(placeholder)类型名称,在下标代码体开始的标志的花括号之前写下泛型Where子句。例如: +下标能够是泛型的,他们能够包含泛型 `where` 子句。你可以把占位符类型的名称写在 `subscript` 后面的尖括号里,在下标代码体开始的标志的花括号之前写下泛型 `where` 子句。例如: ```swift extension Container { @@ -666,10 +684,14 @@ extension Container { } ``` -这个Container协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下: -- 在尖括号中的泛型参数Indices,必须是符合标准库中的Sequence协议的类型。 -- 下标使用的单一的参数,indices,必须是Indices的实例。 -- 泛型Where子句要求Sequence(Indices)的迭代器,其所有的元素都是Int类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。 +这个 `Container` 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下: + +这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下: + +- 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。 +- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。 +- 泛型 `where` 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。 + +综合一下,这些约束意味着,传入到 `indices` 下标,是一个整型的序列. +(译者:原来的 `Container` 协议,`subscript` 必须是 `Int` 型的,扩展中新的 `subscript`,允许下标是一个的序列,而非单一的值。) -综合一下,这些约束意味着,传入到indices下标,是一个整数的序列. -(译者:原来的Container协议,subscript必须是Int型的,扩展中新的subscript,允许下标是一个的序列,而非单一的值。) \ No newline at end of file diff --git a/source/chapter2/24_Memory_Safe.md b/source/chapter2/24_Memory_Safe.md new file mode 100644 index 00000000..d232f311 --- /dev/null +++ b/source/chapter2/24_Memory_Safe.md @@ -0,0 +1,218 @@ +# 内存安全 + +本页包含内容: + +- [理解内存访问冲突](#understanding_conflicting_access_to_memory) +- [In-Out 参数的访问冲突](#conflicting_access_to_in-out_parameters) +- [函数里 self 的访问冲突](#conflicting_access_to_self_in_methods) +- [属性的访问冲突](#conflicting_access_to_properties) +- [更新历史](#revision_history) + +默认情况下,Swift 会阻止你代码里不安全的行为。例如,Swift 会保证变量在使用之前就完成初始化,在内存被回收之后就无法被访问,并且数组的索引会做越界检查。 + +Swift 也保证同时访问同一块内存时不会冲突,通过约束代码里对于存储地址的写操作,去获取那一块内存的访问独占权。因为 Swift 自动管理内存,所以大部分时候你完全不需要考虑内存访问的事情。然而,理解潜在的冲突也是很重要的,可以避免你写出访问冲突的代码。而如果你的代码确实存在冲突,那在编译时或者运行时就会得到错误。 + + +## 理解内存访问冲突 + +内存的访问,会发生在你给变量赋值,或者传递参数给函数时。例如,下面的代码就包含了读和写的访问: + +```swift +// 向 one 所在的内存区域发起一次写操作 +var one = 1 + +// 向 one 所在的内存区域发起一次读操作 +print("We're number \(one)!") +``` + +内存访问的冲突会发生在你的代码尝试同时访问同一个存储地址的时侯。同一个存储地址的多个访问同时发生会造成不可预计或不一致的行为。在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。 + +你思考一下预算表更新的过程也可以看到同样的问题。更新预算表总共有两步:首先你把预算项的名字和费用加上,然后你再更新总数以体现预算表的现况。在更新之前和之后,你都可以从预算表里读取任何信息并获得正确的答案,就像下面展示的那样。 + +![](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Art/memory_shopping_2x.png) + +而当你添加预算项进入表里的时候,它只是一个临时的,错误的状态,因为总数还没有呗更新。在添加预算项的过程中读取总数就会读取到错误的信息。 + +这个例子也演示了你在修复内存访问冲突时会遇到的问题:有时修复的方式会有很多种,但哪一种是正确的就不总是那么明显了。在这个例子里,根据你是否需要更新后的总数,$5 和 $320 都可能是正确的值。在你修复访问冲突之前,你需要决定它的倾向。 + +> 注意 +> +> 如果你写过并发和多线程的代码,内存访问冲突也许是同样的问题。然而,这里访问冲突的讨论是在单线程的情境下讨论的,并没有使用并发或者多线程。 +> +> 如果你曾经在单线程代码里有访问冲突,Swift 可以保证你在编译或者运行时会得到错误。对于多线程的代码,可以使用 [Thread Sanitizer](https://developer.apple.com/documentation/code_diagnostics/thread_sanitizer) 去帮助检测多线程的冲突。 + +### 内存访问的典型状况 + +内存访问冲突有三种典型的状况:访问是读还是写,访问的时长,以及被访问的存储地址。特别是,当你有两个访问符合下列的情况: + +* 至少有一个是写访问 +* 它们访问的是同一个存储地址 +* 它们的访问在时间线上部分重叠 + +读和写访问的区别很明显:一个写访问会改变存储地址,而读操作不会。存储地址会指向真正访问的位置 —— 例如,一个变量,常量或者属性。内存访问的时长要么是瞬时的,要么是长期的。 + +如果一个访问不可能在其访问期间被其它代码访问,那么就是一个瞬时访问。基于这个特性,两个瞬时访问是不可能同时发生。大多数内存访问都是瞬时的。例如,下面列举的所有读和写访问都是瞬时的: + +```swift +func oneMore(than number: Int) -> Int { + return number + 1 +} + +var myNumber = 1 +myNumber = oneMore(than: myNumber) +print(myNumber) +// 打印 "2" +``` + +然而,有几种被称为长期访问的内存访问方式,会在别的代码执行时持续进行。瞬时访问和长期访问的区别在于别的代码有没有可能在访问期间同时访问,也就是在时间线上的重叠。一个长期访问可以被别的长期访问或瞬时访问重叠。 + +重叠的访问主要出现在使用 in-out 参数的函数和方法或者结构体的 mutating 方法里。Swift 代码里典型的长期访问会在后面进行讨论。 + + +## In-Out 参数的访问冲突 + +一个函数会对它所有的 in-out 参数进行长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。 + +长期访问的存在会造成一个结果,你不能在原变量以 in-out 形式传入后访问原变量,即使作用域原则和访问权限允许 —— 任何访问原变量的行为都会造成冲突。例如: + +```swift +var stepSize = 1 + +func increment(_ number: inout Int) { + number += stepSize +} + +increment(&stepSize) +// 错误:stepSize 访问冲突 +``` + +在上面的代码里,`stepSize` 是一个全局变量,并且它可以在 `increment(_:)` 里正常访问。然而,对于 `stepSize` 的读访问与 `number` 的写访问重叠了。就像下面展示的那样,`number` 和 `stepSize` 都指向了同一个存储地址。同一块内存的读和写访问重叠了,就此产生了冲突。 + +![](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Art/memory_increment_2x.png) + +解决这个冲突的一种方式,是复制一份 `stepSize` 的副本: + +```swift +// 复制一份副本 +var copyOfStepSize = stepSize +increment(©OfStepSize) + +// 更新原来的值 +stepSize = copyOfStepSize +// stepSize 现在的值是 2 +``` + +当你在调用 `increment(_:)` 之前复制一份副本,显然 `copyOfStepSize` 就会根据当前的 `stepSize` 增加。读访问在写操作之前就已经结束了,所以不会有冲突。 + +长期写访问的存在还会造成另一种结果,往同一个函数的多个 in-out 参数里传入同一个变量也会产生冲突,例如: + +```swift +func balance(_ x: inout Int, _ y: inout Int) { + let sum = x + y + x = sum / 2 + y = sum - x +} +var playerOneScore = 42 +var playerTwoScore = 30 +balance(&playerOneScore, &playerTwoScore) // 正常 +balance(&playerOneScore, &playerOneScore) +// 错误:playerOneScore 访问冲突 +``` + +上面的 `balance(_:_:)` 函数会将传入的两个参数平均化。将 `playerOneScore` 和 `playerTwoScore` 作为参数传入不会产生错误 —— 有两个访问重叠了,但它们访问的是不同的内存位置。相反,将 `playerOneScore` 作为参数同时传入就会产生冲突,因为它会发起两个写访问,同时访问同一个的存储地址。 + +> 注意 +因为操作符也是函数,它们也会对 in-out 参数进行长期访问。例如,假设 `balance(_:_:)` 是一个名为 `<^>` 的操作符函数,那么 `playerOneScore <^> playerOneScore` 也会造成像 `balance(&playerOneScore, &playerOneScore)` 一样的冲突。 + + +## 方法里 self 的访问冲突 + +一个结构体的 mutating 方法会在调用期间对 `self` 进行写访问。例如,想象一下这么一个游戏,每一个玩家都有血量,受攻击时血量会下降,并且有敌人的数量,使用特殊技能时会减少敌人数量。 + +```swift +struct Player { + var name: String + var health: Int + var energy: Int + + static let maxHealth = 10 + mutating func restoreHealth() { + health = Player.maxHealth + } +} +``` + +在上面的 `restoreHealth()` 方法里,一个对于 `self` 的写访问会从方法开始直到方法 return。在这种情况下,`restoreHealth()` 里的其它代码不可以对 `Player` 实例的属性发起重叠的访问。下面的 `shareHealth(with:)` 方法接受另一个 `Player` 的实例作为 in-out 参数,产生了访问重叠的可能性。 + +```swift +extension Player { + mutating func shareHealth(with teammate: inout Player) { + balance(&teammate.health, &health) + } +} + +var oscar = Player(name: "Oscar", health: 10, energy: 10) +var maria = Player(name: "Maria", health: 5, energy: 10) +oscar.shareHealth(with: &maria) // 正常 +``` + +上面的例子里,调用 `shareHealth(with:)` 方法去把 `oscar` 玩家的血量分享给 `maria` 玩家并不会造成冲突。在方法调用期间会对 `oscar` 发起写访问,因为在 mutating 方法里 `self` 就是 `oscar`,同时对于 `maria` 也会发起写访问,因为 `maria` 作为 in-out 参数传入。过程如下,它们会访问内存的不同位置。即使两个写访问重叠了,它们也不会冲突。 + +![](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Art/memory_share_health_maria_2x.png) + +当然,如果你将 `oscar` 作为参数传入 `shareHealth(with:)` 里,就会产生冲突: + +```swift +oscar.shareHealth(with: &oscar) +// 错误:oscar 访问冲突 +``` + +mutating 方法在调用期间需要对 `self` 发起写访问,而同时 in-out 参数也需要写访问。在方法里,`self` 和 `teammate` 都指向了同一个存储地址 —— 就像下面展示的那样。对于同一块内存同时进行两个写访问,并且它们重叠了,就此产生了冲突。 + +![](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Art/memory_share_health_oscar_2x.png) + + +## 属性的访问冲突 + +如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问整一个值。例如,元组元素的写访问重叠会产生冲突: + +```swift +var playerInformation = (health: 10, energy: 20) +balance(&playerInformation.health, &playerInformation.energy) +// 错误:playerInformation 的属性访问冲突 +``` + +上面的例子里,传入同一元组的元素对 `balance(_:_:)` 进行调用,产生了冲突,因为 `playerInformation` 的访问产生了写访问重叠。`playerInformation.health` 和 `playerInformation.energy` 都被作为参数传入,意味着 `balance(_:_:)` 需要在函数调用期间对它们发起写访问。任何情况下,对于元组元素的写访问都需要对整个元组发起写访问。这意味着对于 `playerInfomation` 发起的两个写访问重叠了,造成冲突。 + +下面的代码展示了一样的错误,对于一个存储在全局变量里的结构体属性的写访问重叠了。 + +```swift +var holly = Player(name: "Holly", health: 10, energy: 10) +balance(&holly.health, &holly.energy) // 错误 +``` + +在实践中,大多数对于结构体属性的访问都会安全的重叠。例如,将上面例子里的变量 `holly` 改为本地变量而非全局变量,编译器就会可以保证这个重叠访问时安全的: + +```swift +func someFunction() { + var oscar = Player(name: "Oscar", health: 10, energy: 10) + balance(&oscar.health, &oscar.energy) // 正常 +} +``` + +上面的例子里,`oscar` 的 `health` 和 `energy` 都作为 in-out 参数传入了 `balance(_:_:)` 里。编译器可以保证内存安全,因为两个存储属性任何情况下都不会相互影响。 + +限制结构体属性的重叠访问对于内存安全并不总是必要的。内存安全是必要的,但访问独占权的要求比内存安全还要更严格 —— 意味着即使有些代码违反了访问独占权的原则,也是内存安全的。如果编译器可以保证这种非专属的访问是安全的,那 Swift 就会允许这种内存安全的行为。特别是当你遵循下面的原则时,它可以保证结构体属性的重叠访问是安全的: + +* 你访问的是实例的存储属性,而不是计算属性或类的属性 +* 结构体是本地变量的值,而非全局变量 +* 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获了 + +如果编译器无法保证访问的安全性,它就不会允许访问。 + + +## 更新历史 + +> 4.0 +> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + diff --git a/source/chapter2/24_Access_Control.md b/source/chapter2/25_Access_Control.md similarity index 63% rename from source/chapter2/24_Access_Control.md rename to source/chapter2/25_Access_Control.md index 2802483b..671de35c 100644 --- a/source/chapter2/24_Access_Control.md +++ b/source/chapter2/25_Access_Control.md @@ -7,14 +7,19 @@ > 2.0 > 翻译+校对:[mmoaay](https://github.com/mmoaay) - +> > 2.1 > 翻译:[Prayer](https://github.com/futantan) > 校对:[shanks](http://codebuild.me),2015-11-01 -> +> > 2.2 > 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-17 -> 3.0.1, shanks,2016-11-13 + +> 3.0.1 +> 翻译+校对: shanks,2016-11-13 + +> 4.0 +> 翻译:kemchenj,2017-09-23 本页内容包括: @@ -26,15 +31,15 @@ - [常量、变量、属性、下标](#constants_variables_properties_subscripts) - [构造器](#initializers) - [协议](#protocols) -- [扩展](#extensions) +- [Extension](#extensions) - [泛型](#generics) - [类型别名](#type_aliases) -*访问控制*可以限定其他源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。 +*访问控制*可以限定其它源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。 你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。 -Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一目标的应用程序,我们完全可以不用显式声明代码的访问级别。 +Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式声明代码的访问级别。 > 注意 为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。 @@ -45,7 +50,7 @@ Swift 中的访问控制模型基于模块和源文件这两个概念。 模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。 -在 Swift 中,Xcode 的每个目标(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。 +在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。 *源文件*就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。 @@ -53,21 +58,21 @@ Swift 中的访问控制模型基于模块和源文件这两个概念。 ## 访问级别 Swift 为代码中的实体提供了五种不同的*访问级别*。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。 -- *开放访问*和*公开访问*可以访问同一模块源文件中的任何实体,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,框架中的某个接口可以被任何人使用时,你可以将其设置为开放或者公开访问。 -- *内部访问*可以访问同一模块源文件中的任何实体,但是不能从模块外访问该模块源文件中的实体。通常情况下,某个接口只在应用程序或框架内部使用时,你可以将其设置为内部访问。 -- *文件私有访问*限制实体只能被所定义的文件内部访问。当需要把这些细节被整个文件使用的时候,使用文件私有访问隐藏了一些特定功能的实现细节。 -- *私有访问*限制实体只能在所定义的作用域内使用。需要把这些细节被整个作用域使用的时候,使用文件私有访问隐藏了一些特定功能的实现细节。 +- *Open* 和 *Public* 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 Open 或 Public 级别来指定框架的外部接口。Open 和 Public 的区别在后面会提到。 +- *Internal* 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 Internal 级别。 +- *File-private* 限制实体只能在其定义的文件内部访问。如果功能的部分细节只需要在文件内使用时,可以使用 File-private 来将其隐藏。 +- *Private* 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 Private 来将其隐藏。 -开放访问为最高(限制最少)访问级别,私有访问为最低(限制最多)访问级别。 +Open 为最高访问级别(限制最少),Private 为最低访问级别(限制最多)。 -开放访问只作用于类类型和类的成员,它和公开访问的区别如下: +Open 只能作用于类和类的成员,它和 Public 的区别如下: -* 公开访问或者其他更严访问级别的类,只能在它们定义的模块内部被继承。 -* 公开访问或者其他更严访问级别的类成员,只能在它们定义的模块内部的子类中重写。 -* 开放访问的类,可以在它们定义的模块中被继承,也可以在引用它们的模块中被继承。 -* 开放访问的类成员,可以在它们定义的模块中子类中重写,也可以在引用它们的模块中的子类重写。 -* -把一个类标记为开放,显式地表明,你认为其他模块中的代码使用此类作为父类,然后你已经设计好了你的类的代码了。 +* Public 或者其它更严访问级别的类,只能在其定义的模块内部被继承。 +* Public 或者其它更严访问级别的类成员,只能在其定义的模块内部的子类中重写。 +* Open 的类,可以在其定义的模块中被继承,也可以在引用它的模块中被继承。 +* Open 的类成员,可以在其定义的模块中子类中重写,也可以在引用它的模块中的子类重写。 + +把一个类标记为 `open`,明确的表示你已经充分考虑过外部模块使用此类作为父类的影响,并且设计好了你的类的代码了。 ### 访问级别基本原则 @@ -76,33 +81,33 @@ Swift 中的访问级别遵循一个基本原则:*不可以在某个实体中 例如: -- 一个公开访问级别的变量,其类型的访问级别不能是内部,文件私有或是私有类型的。因为无法保证变量的类型在使用变量的地方也具有访问权限。 +- 一个 Public 的变量,其类型的访问级别不能是 Internal,File-private 或是 Private。因为无法保证变量的类型在使用变量的地方也具有访问权限。 - 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为这样就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以的情况。 -关于此原则的各种情况的具体实现,将在下面的细节中体现。 +关于此原则在各种情况下的具体表现,将在下文有所体现。 ### 默认访问级别 -如果你不为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。 +如果你没有为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。 -### 单目标应用程序的访问级别 +### 单 target 应用程序的访问级别 -当你编写一个单目标应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 `internal` 即可。但是,你也可以使用文件私有访问或私有访问级别,用于隐藏一些功能的实现细节。 +当你编写一个单目标应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 Internal 即可。但是,你也可以使用 `fileprivate` 访问或 `private` 访问级别,用于隐藏一些功能的实现细节。 ### 框架的访问级别 -当你开发框架时,就需要把一些对外的接口定义为开放访问或公开访问级别,以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口,就是这个框架的 API。 +当你开发框架时,就需要把一些对外的接口定义为 Open 或 Public,以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口,就是这个框架的 API。 -> 注意 -框架依然会使用默认的内部访问级别,也可以指定为文件私有访问或者私有访问级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定开放访问或公开访问级别。 +> 注意 +框架依然会使用默认的 `internal` ,也可以指定为 `fileprivate` 访问或者 `private` 访问级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定开放访问或公开访问级别。 -### 单元测试目标的访问级别 +### 单元测试 target 的访问级别 -当你的应用程序包含单元测试目标时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有开放访问或公开访问级别级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。 +当你的应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 `open` 或 `public` 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。 ## 访问控制语法 @@ -121,51 +126,51 @@ fileprivate func someFilePrivateFunction() {} private func somePrivateFunction() {} ``` -除非专门指定,否则实体默认的访问级别为内部访问级别,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的内部访问级别: +除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的 `internal` : ```swift -class SomeInternalClass {} // 隐式内部访问级别 -var someInternalConstant = 0 // 隐式内部访问级别 +class SomeInternalClass {} // 隐式 internal +var someInternalConstant = 0 // 隐式 internal ``` ## 自定义类型 -如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个文件私有级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。 +如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `fileprivate` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。 -一个类型的访问级别也会影响到类型*成员*(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为私有或者文件私有级别,那么该类型的所有成员的默认访问级别也会变成私有或者文件私有级别。如果你将类型指定为公开或者内部访问级别(或者不明确指定访问级别,而使用默认的内部访问级别),那么该类型的所有成员的默认访问级别将是内部访问。 +一个类型的访问级别也会影响到类型*成员*(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 或者 `fileprivate` 级别,那么该类型的所有成员的默认访问级别也会变成 `private` 或者 `fileprivate` 级别。如果你将类型指定为公开或者 `internal` (或者不明确指定访问级别,而使用默认的 `internal` ),那么该类型的所有成员的默认访问级别将是内部访问。 > 重要 -上面提到,一个公开类型的所有成员的访问级别默认为内部访问级别,而不是公开级别。如果你想将某个成员指定为公开访问级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。 +上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。 ```swift -public class SomePublicClass { // 显式公开类 - public var somePublicProperty = 0 // 显式公开类成员 - var someInternalProperty = 0 // 隐式内部类成员 - fileprivate func someFilePrivateMethod() {} // 显式文件私有类成员 - private func somePrivateMethod() {} // 显式私有类成员 +public class SomePublicClass { // 显式 public 类 + public var somePublicProperty = 0 // 显式 public 类成员 + var someInternalProperty = 0 // 隐式 internal 类成员 + fileprivate func someFilePrivateMethod() {} // 显式 fileprivate 类成员 + private func somePrivateMethod() {} // 显式 private 类成员 } -class SomeInternalClass { // 隐式内部类 - var someInternalProperty = 0 // 隐式内部类成员 - fileprivate func someFilePrivateMethod() {} // 显式文件私有类成员 - private func somePrivateMethod() {} // 显式私有类成员 +class SomeInternalClass { // 隐式 internal 类 + var someInternalProperty = 0 // 隐式 internal 类成员 + fileprivate func someFilePrivateMethod() {} // 显式 fileprivate 类成员 + private func somePrivateMethod() {} // 显式 private 类成员 } -fileprivate class SomeFilePrivateClass { // 显式文件私有类 - func someFilePrivateMethod() {} // 隐式文件私有类成员 - private func somePrivateMethod() {} // 显式私有类成员 +fileprivate class SomeFilePrivateClass { // 显式 fileprivate 类 + func someFilePrivateMethod() {} // 隐式 fileprivate 类成员 + private func somePrivateMethod() {} // 显式 private 类成员 } -private class SomePrivateClass { // 显式私有类 - func somePrivateMethod() {} // 隐式私有类成员 +private class SomePrivateClass { // 显式 private 类 + func somePrivateMethod() {} // 隐式 private 类成员 } ``` ### 元组类型 -元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为内部访问级别,另一个类型为私有访问级别,那么这个元组的访问级别为私有访问级别。 +元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal`,另一个类型为 `private`,那么这个元组的访问级别为 `private`。 > 注意 元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。 @@ -200,7 +205,7 @@ private func someFunction() -> (SomeInternalClass, SomePrivateClass) { 枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。 -比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public` 级别,那么它的成员 `North`、`South`、`East`、`West` 的访问级别同样也是 `public`: +比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public`,那么它的成员 `North`、`South`、`East`、`West` 的访问级别同样也是 `public`: ```swift public enum CompassPoint { @@ -214,12 +219,12 @@ public enum CompassPoint { #### 原始值和关联值 -枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 访问级别的枚举中定义 `private` 级别的原始值类型。 +枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 的枚举中定义 `private` 的原始值类型。 ### 嵌套类型 -如果在 `private` 级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。 +如果在 `private` 的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。 ## 子类 @@ -292,7 +297,7 @@ struct TrackedString { `TrackedString` 结构体定义了一个用于存储 `String` 值的属性 `value`,并将初始值设为 `""`(一个空字符串)。该结构体还定义了另一个用于存储 `Int` 值的属性 `numberOfEdits`,它用于记录属性 `value` 被修改的次数。这个功能通过属性 `value` 的 `didSet` 观察器实现,每当给 `value` 赋新值时就会调用 `didSet` 方法,然后将 `numberOfEdits` 的值加一。 -结构体 `TrackedString` 和它的属性 `value` 均没有显式指定访问级别,所以它们都拥有默认的访问级别 `internal`。但是该结构体的 `numberOfEdits` 属性使用了 `private(set)` 修饰符,这意味着 `numberOfEdits` 属性只能在定义该结构体的源文件中赋值。`numberOfEdits` 属性的 `Getter` 依然是默认的访问级别 `internal`,但是 `Setter` 的访问级别是 `private`,这表示该属性只有在当前的源文件中是可读写的,而在当前源文件所属的模块中只是一个可读的属性。 +结构体 `TrackedString` 和它的属性 `value` 都没有显式地指定访问级别,所以它们都是用默认的访问级别 `internal`。但是该结构体的 `numberOfEdits` 属性使用了 `private(set)` 修饰符,这意味着 `numberOfEdits` 属性只能在结构体的定义中进行赋值。`numberOfEdits` 属性的 `Getter` 依然是默认的访问级别 `internal`,但是 `Setter` 的访问级别是 `private`,这表示该属性只能在内部修改,而在结构体的外部则表现为一个只读属性。 如果你实例化 `TrackedString` 结构体,并多次对 `value` 属性的值进行修改,你就会看到 `numberOfEdits` 的值会随着修改次数而变化: @@ -370,16 +375,44 @@ public struct TrackedString { Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。 -## 扩展 +## Extension -你可以在访问级别允许的情况下对类、结构体、枚举进行扩展。扩展成员具有和原始类型成员一致的访问级别。例如,你扩展了一个 `public` 或者 `internal` 类型,扩展中的成员具有默认的 `internal` 访问级别,和原始类型中的成员一致 。如果你扩展了一个 `private` 类型,扩展成员则拥有默认的 `private` 访问级别。 +Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的成员具有和原始类型成员一致的访问级别。例如,你使用 extension 扩展了一个 `public` 或者 `internal` 类型,extension 中的成员就默认使用 `internal` 访问级别,和原始类型中的成员一致。如果你使用 extension 扩展了一个 `private` 类型,则 extension 的成员默认使用 `private` 访问级别。 -或者,你可以明确指定扩展的访问级别(例如,`private extension`),从而给该扩展中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。 +或者,你可以明确指定 extension 的访问级别(例如,`private extension`),从而给该 extension 中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。 - -### 通过扩展添加协议一致性 +如果你使用 extension 来遵循协议的话,就不能显式地声明 extension 的访问级别。extension 每个 protocol 要求的实现都默认使用 protocol 的访问级别。 -如果你通过扩展来采纳协议,那么你就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中所有协议要求的实现提供默认的访问级别。 + +### Extension 的私有成员 + +扩展同一文件内的类,结构体或者枚举,extension 里的代码会表现得跟声明在原类型里的一模一样。也就是说你可以这样: + +- 在类型的声明里声明一个私有成员,在同一文件的 extension 里访问。 +- 在 extension 里声明一个私有成员,在同一文件的另一个 extension 里访问。 +- 在 extension 里声明一个私有成员,在同一文件的类型声明里访问。 + +这意味着你可以像组织的代码去使用 extension,而且不受私有成员的影响。例如,给定下面这样一个简单的协议: + +```swift +protocol SomeProtocol { + func doSomething() {} +} +``` + +你可以使用 extension 来遵守协议,就想这样: + +```swift +struct SomeStruct { + private var privateVariable = 12 +} + +extension SomeStruct: SomeProtocol { + func doSomething() { + print(privateVariable) + } +} +``` ## 泛型 @@ -393,3 +426,5 @@ Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说, > 注意 这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。 + +