| 发布日期 | +语法变更记录 | +
|---|---|
| 2017-12-04 | +
+
|
+
-### 简化 setter 声明
+### 简化 Setter 声明
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 `newValue`。下面是使用了简化 setter 声明的 `Rect` 结构体代码:
@@ -212,6 +217,7 @@ struct AlternativeRect {
只有 getter 没有 setter 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
> 注意
+>
> 必须使用 `var` 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let` 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
只读计算属性的声明可以去掉 `get` 关键字和花括号:
@@ -233,9 +239,9 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
## 属性观察器
-*属性观察器*监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
+属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
-可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 属性重写请参考[重写](./13_Inheritance.html#overriding)。
+你可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 属性重写请参考[重写](./13_Inheritance.html#overriding)。
可以为属性添加如下的一个或全部观察器:
@@ -247,7 +253,9 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
> 注意
+>
> 父类的属性在子类的构造器中被赋值时,它在父类中的 `willSet` 和 `didSet` 观察器会被调用,随后才会调用子类的观察器。在父类初始化方法调用之前,子类给属性赋值时,观察器不会被调用。
+>
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)。
下面是一个 `willSet` 和 `didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
@@ -294,12 +302,14 @@ stepCounter.totalSteps = 896
计算属性和属性观察器所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
-前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
+前面章节提到的全局或局部变量都属于*存储型变量*,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
-另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
+另外,在全局或局部范围都可以定义*计算型变量*和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
> 注意
+>
> 全局的常量或变量都是延迟计算的,跟[延迟存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记`lazy`修饰符。
+>
> 局部范围的常量或变量从不延迟计算。
@@ -309,12 +319,14 @@ stepCounter.totalSteps = 896
也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
-类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
+类型属性用于定义某个类型所有实例共享的数据,比如*所有*实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
> 注意
+>
> 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
+>
> 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 `lazy` 修饰符。
@@ -349,6 +361,7 @@ class SomeClass {
```
> 注意
+>
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
@@ -407,6 +420,7 @@ struct AudioChannel {
- 如果修正后的 `currentLevel` 值大于静态类型属性 `maxInputLevelForAllChannels` 的值,属性观察器就将新值保存在 `maxInputLevelForAllChannels` 中。
> 注意
+>
> 在第一个检查过程中,`didSet` 属性观察器将 `currentLevel` 设置成了不同的值,但这不会造成属性观察器被再次调用。
可以使用结构体 `AudioChannel` 创建两个声道 `leftChannel` 和 `rightChannel`,用以表示立体声系统的音量:
diff --git a/source/chapter2/11_Methods.md b/source/chapter2/11_Methods.md
index d2a19ca7..47b25f24 100755
--- a/source/chapter2/11_Methods.md
+++ b/source/chapter2/11_Methods.md
@@ -17,6 +17,9 @@
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
@@ -187,7 +190,8 @@ ovenLight.next()
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做*类型方法*。在方法的`func`关键字之前加上关键字`static`,来指定类型方法。类还可以用关键字`class`来允许子类重写父类的方法实现。
-> 注意
+> 注意
+>
> 在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。
类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在`SomeClass`类上调用类型方法的例子:
@@ -240,7 +244,7 @@ struct LevelTracker {
除了类型属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测每个玩家当前的等级。
-为了便于管理`currentLevel`属性,`LevelTracker`定义了实例方法`advance(to:)`。这个方法会在更新`currentLevel`之前检查所请求的新等级是否已经解锁。`advance(to:)`方法返回布尔值以指示是否能够设置`currentLevel`。因为允许在调用`advance(to:)`时候忽略返回值,不会产生编译警告,所以函数被标注为`@ discardableResult`属性,更多关于属性信息,请参考[属性](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID347)章节。
+为了便于管理`currentLevel`属性,`LevelTracker`定义了实例方法`advance(to:)`。这个方法会在更新`currentLevel`之前检查所请求的新等级是否已经解锁。`advance(to:)`方法返回布尔值以指示是否能够设置`currentLevel`。因为允许在调用`advance(to:)`时候忽略返回值,不会产生编译警告,所以函数被标注为`@ discardableResult`属性,更多关于属性信息,请参考[属性](../chapter3/07_Attributes.html)章节。
下面,`Player`类使用`LevelTracker`来监测和更新每个玩家的发展进度:
diff --git a/source/chapter2/12_Subscripts.md b/source/chapter2/12_Subscripts.md
index 0ebbc768..de1c1e0d 100755
--- a/source/chapter2/12_Subscripts.md
+++ b/source/chapter2/12_Subscripts.md
@@ -16,7 +16,10 @@
> 3.0.1,shanks,2016-11-13
> 4.0
-> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
@@ -73,7 +76,8 @@ print("six times three is \(threeTimesTable[6])")
你可以通过下标访问`threeTimesTable`实例,例如上面演示的`threeTimesTable[6]`。这条语句查询了`3`的乘法表中的第六个元素,返回`3`的`6`倍即`18`。
-> 注意
+> 注意
+>
> `TimesTable`例子基于一个固定的数学公式,对`threeTimesTable[someIndex]`进行赋值操作并不合适,因此下标定义为只读的。
@@ -92,7 +96,8 @@ numberOfLegs["bird"] = 2
更多关于`Dictionary`下标的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)
-> 注意
+> 注意
+>
> Swift 的`Dictionary`类型的下标接受并返回可选类型的值。上例中的`numberOfLegs`字典通过下标返回的是一个`Int?`或者说“可选的int”。`Dictionary`类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为`nil`即可。
@@ -129,7 +134,7 @@ struct Matrix {
}
```
-`Matrix`提供了一个接受两个入参的构造方法,入参分别是`rows`和`columns`,创建了一个足够容纳`rows * columns`个`Double`类型的值的数组。通过传入数组长度和初始值`0.0`到数组的构造器,将矩阵中每个位置的值初始化为`0.0`。关于数组的这种构造方法请参考[创建一个空数组](./04_Collection_Types.html#creating_an_empty_array)。
+`Matrix`提供了一个接受两个入参的构造方法,入参分别是`rows`和`columns`,创建了一个足够容纳`rows * columns`个`Double`类型的值的数组。通过传入数组长度和初始值`0.0`到数组的构造器,将矩阵中每个位置的值初始化为`0.0`。关于数组的这种构造方法请参考[创建一个带有默认值的数组](./04_Collection_Types.html#creating_an_array_with_a_default_value)。
你可以通过传入合适的`row`和`column`的数量来构造一个新的`Matrix`实例:
diff --git a/source/chapter2/13_Inheritance.md b/source/chapter2/13_Inheritance.md
index 7458c6a8..f1f9fd5c 100755
--- a/source/chapter2/13_Inheritance.md
+++ b/source/chapter2/13_Inheritance.md
@@ -13,7 +13,10 @@
> 3.0.1,shanks,2016-11-13
> 4.0
-> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
@@ -33,8 +36,9 @@
不继承于其它类的类,称之为*基类*。
-> 注意
-Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
+> 注意
+>
+> Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
下面的例子定义了一个叫`Vehicle`的基类。这个基类声明了一个名为`currentSpeed `,默认值是`0.0`的存储属性(属性类型推断为`Double`)。`currentSpeed`属性的值被一个`String`类型的只读计算型属性`description`使用,用来创建车辆的描述。
@@ -179,8 +183,9 @@ train.makeNoise()
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
-> 注意
-如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过`super.someProperty`来返回继承来的值,其中`someProperty`是你要重写的属性的名字。
+> 注意
+>
+> 如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过`super.someProperty`来返回继承来的值,其中`someProperty`是你要重写的属性的名字。
以下的例子定义了一个新类,叫`Car`,它是`Vehicle`的子类。这个类引入了一个新的存储型属性叫做`gear`,默认值为整数`1`。`Car`类重写了继承自`Vehicle`的`description`属性,提供包含当前档位的自定义描述:
@@ -210,8 +215,9 @@ print("Car: \(car.description)")
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
-> 注意
-你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet`或`didSet`实现是不恰当。
+> 注意
+>
+> 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet`或`didSet`实现是不恰当。
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。
下面的例子定义了一个新类叫`AutomaticCar`,它是`Car`的子类。`AutomaticCar`表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位:
@@ -226,7 +232,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()
@@ -240,6 +246,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 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
diff --git a/source/chapter2/14_Initialization.md b/source/chapter2/14_Initialization.md
index 03ad2256..b7c60669 100755
--- a/source/chapter2/14_Initialization.md
+++ b/source/chapter2/14_Initialization.md
@@ -23,6 +23,9 @@
> 4.0
> 翻译:[muhlenXi](https://github.com/muhlenxi) 2017-09-21
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
+
本页包含内容:
- [存储属性的初始赋值](#setting_initial_values_for_stored_properties)
@@ -38,7 +41,7 @@
通过定义*构造器*来实现构造过程,就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
-类的实例也可以通过定义*析构器*在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。
+类的实例也可以通过定义*析构器*在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。
## 存储属性的初始赋值
@@ -48,7 +51,8 @@
你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下小节将详细介绍这两种方法。
> 注意
-当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。
+>
+> 当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。
### 构造器
@@ -83,7 +87,8 @@ print("The default temperature is \(f.temperature)° Fahrenheit")
如前所述,你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。
> 注意
-如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。
+>
+> 如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。
你可以使用更简单的方式在定义结构体 `Fahrenheit` 时为属性 `temperature` 设置默认值:
@@ -101,7 +106,7 @@ struct Fahrenheit {
### 构造参数
-自定义`构造过程`时,可以在定义中提供构造参数,指定参数值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
+自定义构造过程时,可以在定义中提供*构造参数*,指定参数值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
下面例子中定义了一个包含摄氏度温度的结构体 `Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)`和`init(fromKelvin:)`,二者分别通过接受不同温标下的温度值来创建新的实例:
@@ -124,12 +129,12 @@ let freezingPointOfWater = Celsius(fromKelvin: 273.15)
第一个构造器拥有一个构造参数,其外部名字为`fromFahrenheit`,内部名字为`fahrenheit`;第二个构造器也拥有一个构造参数,其外部名字为`fromKelvin`,内部名字为`kelvin`。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性 `temperatureInCelsius` 中。
-
-### 参数的内部名称和外部名称
+
+### 参数名和参数标签
-跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
+跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名和一个在调用构造器时使用的参数标签。
-然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。因此在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,Swift 会为构造器的每个参数自动生成一个跟内部名字相同的外部名。
+然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。因此在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数标签,Swift 会为构造器的*每个*参数自动生成一个参数标签。
以下例子中定义了一个结构体 `Color`,它包含了三个常量:`red`、`green` 和 `blue`。这些属性可以存储 `0.0` 到 `1.0` 之间的值,用来指示颜色中红、绿、蓝成分的含量。
@@ -158,7 +163,7 @@ 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)
@@ -166,9 +171,9 @@ let veryGreen = Color(0.0, 1.0, 0.0)
```
-### 不带外部名的构造器参数
+### 不带参数标签的构造器参数
-如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(`_`)来显式描述它的外部名,以此重写上面所说的默认行为。
+如果你不希望为构造器的某个参数提供参数标签,你可以使用下划线(`_`)来显式描述它的外部名,以此重写上面所说的默认行为。
下面是之前 `Celsius` 例子的扩展,跟之前相比添加了一个带有 `Double` 类型参数的构造器,其外部名用 `_` 代替:
@@ -190,7 +195,7 @@ let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0
```
-调用 `Celsius(37.0)` 意图明确,不需要外部参数名称。因此适合使用 `init(_ celsius: Double)` 这样的构造器,从而可以通过提供 `Double` 类型的参数值调用构造器,而不需要加上外部名。
+调用 `Celsius(37.0)` 意图明确,不需要参数标签。因此适合使用 `init(_ celsius: Double)` 这样的构造器,从而可以通过提供未命名的`Double`值调用构造器,而不需要加上参数标签。
### 可选属性类型
@@ -225,7 +230,8 @@ cheeseQuestion.response = "Yes, I do like cheese."
你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。
> 注意
-对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
+>
+> 对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
你可以修改上面的 `SurveyQuestion` 示例,用常量属性替代变量属性 `text`,表示问题内容 `text` 在`SurveyQuestion`的实例被创建之后不会再被修改。尽管 `text` 属性现在是常量,我们仍然可以在类的构造器中设置它的值:
@@ -249,7 +255,7 @@ beetsQuestion.response = "I also like beets. (But not with cheese.)"
## 默认构造器
-如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器(default initializers)。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。
+如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个*默认构造器(default initializers)*。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。
下面例子中创建了一个类 `ShoppingListItem`,它封装了购物清单中的某一物品的属性:名字(`name`)、数量(`quantity`)和购买状态 `purchase state`:
@@ -267,7 +273,7 @@ var item = ShoppingListItem()
### 结构体的逐一成员构造器
-除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。
+除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个*逐一成员构造器(memberwise initializer)*,即使结构体的存储型属性没有默认值。
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
@@ -285,7 +291,7 @@ let twoByTwo = Size(width: 2.0, height: 2.0)
## 值类型的构造器代理
-构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能避免多个构造器间的代码重复。
+构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为*构造器代理*,它能避免多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.html)),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
@@ -294,7 +300,8 @@ let twoByTwo = Size(width: 2.0, height: 2.0)
请注意,如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器
> 注意
-假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./21_Extensions.html)章节。
+>
+> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./20_Extensions.html)章节。
下面例子将定义一个结构体 `Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体 `Size` 和 `Point`,它们各自为其所有的属性提供了默认初始值 `0.0`。
@@ -355,13 +362,14 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
构造器 `init(center:size:)` 可以直接将 `origin` 和 `size` 的新值赋值到对应的属性中。然而,构造器 `init(center:size:)` 通过使用提供了相关功能的现有构造器将会更加便捷。
> 注意
-如果你想用另外一种不需要自己定义`init()`和`init(origin:size:)`的方式来实现这个例子,请参考[扩展](./21_Extensions.html)。
+>
+> 如果你想用另外一种不需要自己定义`init()`和`init(origin:size:)`的方式来实现这个例子,请参考[扩展](./21_Extensions.html)。
## 类的继承和构造过程
-类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。
-
+类里面的所有存储型属性——包括所有继承自父类的属性——都*必须*在构造过程中设置初始值。
+[]()
Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
@@ -403,15 +411,15 @@ convenience init(parameters) {
##### 规则 1
-指定构造器必须调用其直接父类的的指定构造器。
+ 指定构造器必须调用其直接父类的的指定构造器。
##### 规则 2
-便利构造器必须调用*同*类中定义的其它构造器。
+ 便利构造器必须调用*同*类中定义的其它构造器。
##### 规则 3
-便利构造器最后必须调用指定构造器。
+ 便利构造器最后必须调用指定构造器。
一个更方便记忆的方法是:
@@ -427,7 +435,8 @@ convenience init(parameters) {
子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则 2 和 3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则 1。
> 注意
-这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类的构造器如何实现。
+>
+> 这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类的构造器如何实现。
下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。
@@ -441,27 +450,28 @@ Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。
> 注意
-Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值 `0` 或空值(比如说`0`或`nil`)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以 `0` 或 `nil` 作为合法默认值的情况。
+>
+> Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值 `0` 或空值(比如说`0`或`nil`)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以 `0` 或 `nil` 作为合法默认值的情况。
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程不出错地完成:
##### 安全检查 1
-指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
+ 指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。
##### 安全检查 2
-指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器,如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
+ 指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器,如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
##### 安全检查 3
-便利构造器必须为任意属性(包括同类中定义的)赋新值之前代理调用同一类中的其它构造器,如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
+ 便利构造器必须为任意属性(包括同类中定义的)赋新值之前代理调用同一类中的其它构造器,如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
##### 安全检查 4
-构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用`self`作为一个值。
+ 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用`self`作为一个值。
类实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。
@@ -509,7 +519,8 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。
> 注意
-父类的构造器仅会在安全和适当的情况下被继承。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
+>
+> 父类的构造器仅会在安全和适当的情况下被继承。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
假如你希望自定义的子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。
@@ -518,7 +529,8 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
正如重写属性,方法或者是下标,`override` 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否正确。
> 注意
-当你重写一个父类的指定构造器时,你总是需要写`override`修饰符,即使是为了实现子类的便利构造器。
+>
+> 当你重写一个父类的指定构造器时,你总是需要写`override`修饰符,即使是为了实现子类的便利构造器。
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。
@@ -566,7 +578,8 @@ print("Bicycle: \(bicycle.description)")
```
> 注意
-子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。
+>
+> 子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。
### 构造器的自动继承
@@ -577,16 +590,17 @@ print("Bicycle: \(bicycle.description)")
##### 规则 1
-如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
+ 如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
##### 规则 2
-如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。
+ 如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。
即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
> 注意
-对于规则 2,子类可以将父类的指定构造器实现为便利构造器。
+>
+> 对于规则 2,子类可以将父类的指定构造器实现为便利构造器。
### 指定构造器和便利构造器实践
@@ -681,7 +695,8 @@ class ShoppingListItem: RecipeIngredient {
```
> 注意
-`ShoppingListItem` 没有定义构造器来为 `purchased` 提供初始值,因为添加到购物单的物品的初始状态总是未购买。
+>
+> `ShoppingListItem` 没有定义构造器来为 `purchased` 提供初始值,因为添加到购物单的物品的初始状态总是未购买。
由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,`ShoppingListItem` 将自动继承所有父类中的指定构造器和便利构造器。
@@ -717,12 +732,14 @@ for item in breakfastList {
为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在 `init` 关键字后面添加问号 (`init?`)。
> 注意
-可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。
+>
+> 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。
可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过 `return nil` 语句来表明可失败构造器在何种情况下应该 “失败”。
> 注意
-严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用`return nil`表明可失败构造器构造失败,而不要用关键字`return`来表明构造成功。
+>
+> 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用`return nil`表明可失败构造器构造失败,而不要用关键字`return`来表明构造成功。
例如,实现针对数字类型转换的可失败构造器。确保数字类型之间的转换能保持精确的值,使用这个 `init(exactly:)` 构造器。如果类型转换不能保持值不变,则这个构造器构造失败。
@@ -783,7 +800,8 @@ if anonymousCreature == nil {
```
> 注意
-空字符串(如 `""`,而不是 `"Giraffe"` )和一个值为 `nil` 的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们之所以让 `Animal` 的可失败构造器构造失败,只是因为对于 `Animal` 这个类的 `species` 属性来说,它更适合有一个具体的值,而不是空字符串。
+>
+> 空字符串(如 `""`,而不是 `"Giraffe"` )和一个值为 `nil` 的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们之所以让 `Animal` 的可失败构造器构造失败,只是因为对于 `Animal` 这个类的 `species` 属性来说,它更适合有一个具体的值,而不是空字符串。
### 枚举类型的可失败构造器
@@ -859,7 +877,8 @@ if unknownUnit == nil {
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。
> 注意
-可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。
+>
+> 可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。
下面这个例子,定义了一个名为`CartItem`的`Product`类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为`quantity`的常量存储型属性,并确保该属性的值至少为`1`:
@@ -923,7 +942,8 @@ if let oneUnnamed = CartItem(name: "", quantity: 1) {
注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。
> 注意
-你可以用非可失败构造器重写可失败构造器,但反过来却不行。
+>
+> 你可以用非可失败构造器重写可失败构造器,但反过来却不行。
下例定义了一个名为 `Document` 的类,`name` 属性的值必须为一个非空字符串或 `nil`,但不能是一个空字符串:
@@ -974,7 +994,7 @@ class UntitledDocument: Document {
在这个例子中,如果在调用父类的可失败构造器 `init?(name:)` 时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过非空的字符串常量来调用它,所以并不会发生运行时错误。
-### 可失败构造器 init!
+### init!可失败构造器
通常来说我们通过在`init`关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以通过在`init`后面添加惊叹号的方式来定义一个可失败构造器(`init!`),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。
@@ -1004,7 +1024,8 @@ class SomeSubclass: SomeClass {
```
> 注意
-如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。
+>
+> 如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。
## 通过闭包或函数设置属性的默认值
@@ -1028,7 +1049,8 @@ class SomeClass {
注意闭包结尾的花括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
> 注意
-如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的 `self` 属性,或者调用任何实例方法。
+>
+> 如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的 `self` 属性,或者调用任何实例方法。
下面例子中定义了一个结构体 `Chessboard`,它构建了西洋跳棋游戏的棋盘,西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行的:
diff --git a/source/chapter2/15_Deinitialization.md b/source/chapter2/15_Deinitialization.md
index a79a34f6..de735757 100755
--- a/source/chapter2/15_Deinitialization.md
+++ b/source/chapter2/15_Deinitialization.md
@@ -18,6 +18,9 @@
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
+
本页包含内容:
- [析构过程原理](#how_deinitialization_works)
@@ -28,7 +31,7 @@
## 析构过程原理
-Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./16_Automatic_Reference_Counting.html)章节中所讲述,Swift 通过`自动引用计数(ARC)`处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
+Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./23_Automatic_Reference_Counting.html)章节中所讲述,Swift 通过*自动引用计数(ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数,如下所示:
diff --git a/source/chapter2/17_Optional_Chaining.md b/source/chapter2/16_Optional_Chaining.md
similarity index 94%
rename from source/chapter2/17_Optional_Chaining.md
rename to source/chapter2/16_Optional_Chaining.md
index d244555b..626dbd3b 100755
--- a/source/chapter2/17_Optional_Chaining.md
+++ b/source/chapter2/16_Optional_Chaining.md
@@ -18,6 +18,9 @@
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
@@ -31,8 +34,9 @@
*可选链式调用*是一种可以在当前值可能为`nil`的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是`nil`,那么调用将返回`nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为`nil`,整个调用链都会失败,即返回`nil`。
-> 注意
-Swift 的可选链式调用和 Objective-C 中向`nil`发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
+> 注意
+>
+> Swift 的可选链式调用和 Objective-C 中向`nil`发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
## 使用可选链式调用代替强制展开
@@ -273,8 +277,9 @@ if (john.residence?.address = someAddress) != nil {
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
-> 注意
-通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
+> 注意
+>
+> 通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
下面这个例子用下标访问`john.residence`属性存储的`Residence`实例的`rooms`数组中的第一个房间的名称,因为`john.residence`为`nil`,所以下标调用失败了:
@@ -404,5 +409,6 @@ if let beginsWithThe =
// 打印 “John's building identifier begins with "The".”
```
-> 注意
-在上面的例子中,在方法的圆括号后面加上问号是因为你要在`buildingIdentifier()`方法的可选返回值上进行可选链式调用,而不是方法本身。
+> 注意
+>
+> 在上面的例子中,在方法的圆括号后面加上问号是因为你要在`buildingIdentifier()`方法的可选返回值上进行可选链式调用,而不是`buildingIdentifier()`方法本身。
diff --git a/source/chapter2/18_Error_Handling.md b/source/chapter2/17_Error_Handling.md
similarity index 88%
rename from source/chapter2/18_Error_Handling.md
rename to source/chapter2/17_Error_Handling.md
index 66353b27..f4ad318c 100755
--- a/source/chapter2/18_Error_Handling.md
+++ b/source/chapter2/17_Error_Handling.md
@@ -15,6 +15,9 @@
> 4.0
> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
+
本页包含内容:
- [表示并抛出错误](#representing_and_throwing_errors)
@@ -28,7 +31,8 @@
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。
> 注意
-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)。
+>
+> 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)。
## 表示并抛出错误
@@ -61,6 +65,7 @@ Swift 中有`4`种处理错误的方式。你可以把函数抛出的错误传
当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上`try`关键字,或者`try?`或`try!`这种变体。这些关键字在下面的小节中有具体讲解。
> 注意
+>
> Swift 中的错误处理和其他语言中用`try`,`catch`和`throw`进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw`语句的性能特性是可以和`return`语句相媲美的。
@@ -76,7 +81,8 @@ func cannotThrowErrors() -> String
一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
> 注意
-只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
+>
+> 只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
下面的例子中,`VendingMachine`类有一个`vend(itemNamed:)`方法,如果请求的物品不存在、缺货或者投入金额小于物品价格,该方法就会抛出一个相应的`VendingMachineError`:
@@ -153,7 +159,7 @@ struct PurchasedSnack {
### 用 Do-Catch 处理错误
-可以使用一个`do-catch`语句运行一段闭包代码来处理错误。如果在`do`子句中的代码抛出了一个错误,这个错误会与`catch`子句做匹配,从而决定哪条子句能处理它。
+你可以使用一个`do-catch`语句运行一段闭包代码来处理错误。如果在`do`子句中的代码抛出了一个错误,这个错误会与`catch`子句做匹配,从而决定哪条子句能处理它。
下面是`do-catch`语句的一般形式:
@@ -233,9 +239,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 {
@@ -255,4 +261,5 @@ func processFile(filename: String) throws {
上面的代码使用一条`defer`语句来确保`open(_:)`函数有一个相应的对`close(_:)`函数的调用。
> 注意
-> 即使没有涉及到错误处理,你也可以使用`defer`语句。
+>
+> 即使没有涉及到错误处理的代码,你也可以使用`defer`语句。
diff --git a/source/chapter2/19_Type_Casting.md b/source/chapter2/18_Type_Casting.md
similarity index 95%
rename from source/chapter2/19_Type_Casting.md
rename to source/chapter2/18_Type_Casting.md
index f3453959..91f10515 100644
--- a/source/chapter2/19_Type_Casting.md
+++ b/source/chapter2/18_Type_Casting.md
@@ -18,6 +18,9 @@
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
+
本页包含内容:
- [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting)
@@ -25,14 +28,14 @@
- [向下转型](#downcasting)
- [`Any` 和 `AnyObject` 的类型转换](#type_casting_for_any_and_anyobject)
-_类型转换_ 可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
+*类型转换*可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
-你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
+你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./21_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
-## 定义一个类层次作为例子
+## 为类型转换定义类层次
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了这些类实例的数组,作为类型转换的例子。
@@ -85,7 +88,7 @@ let library = [
## 检查类型
-用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。
+用*类型检查操作符*(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。
下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量:
@@ -154,6 +157,7 @@ for item in library {
若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director`。相似的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。
> 注意
+>
> 转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。
@@ -164,7 +168,7 @@ Swift 为不确定类型提供了两种特殊的类型别名:
* `Any` 可以表示任何类型,包括函数类型。
* `AnyObject` 可以表示任何类类型的实例。
-只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。在你的代码里使用你期望的明确类型总是更好的。
+只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。当你期望你的代码可以工作,最好还是要确定类型。
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 `Any` 类型的数组 `things`:
@@ -222,8 +226,10 @@ for thing in things {
```
> 注意
+>
> `Any`类型可以表示所有类型的值,包括可选类型。Swift 会在你用`Any`类型来表示一个可选值的时候,给你一个警告。如果你确实想使用`Any`类型来承载可选值,你可以使用`as`操作符显式转换为`Any`,如下所示:
>
+>
```swift
let optionalNumber: Int? = 3
things.append(optionalNumber) // 警告
diff --git a/source/chapter2/20_Nested_Types.md b/source/chapter2/19_Nested_Types.md
similarity index 96%
rename from source/chapter2/20_Nested_Types.md
rename to source/chapter2/19_Nested_Types.md
index fb49d3fd..0c0b8461 100755
--- a/source/chapter2/20_Nested_Types.md
+++ b/source/chapter2/19_Nested_Types.md
@@ -17,6 +17,9 @@
> 4.0
> 翻译+校对:[EyreFree](https://www.eyrefree.org/) 2017-10-19
+
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
diff --git a/source/chapter2/21_Extensions.md b/source/chapter2/20_Extensions.md
similarity index 86%
rename from source/chapter2/21_Extensions.md
rename to source/chapter2/20_Extensions.md
index 913976a7..370f8846 100644
--- a/source/chapter2/21_Extensions.md
+++ b/source/chapter2/20_Extensions.md
@@ -1,5 +1,5 @@
-# 扩展(Extensions)
-----
+# 扩展
+---------------
> 1.0
> 翻译:[lyuka](https://github.com/lyuka)
@@ -18,6 +18,9 @@
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
+
本页包含内容:
- [扩展语法](#extension_syntax)
@@ -38,10 +41,11 @@ Swift 中的扩展可以:
- 定义和使用新的嵌套类型
- 使一个已有类型符合某个协议
-在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的类型拥有这些功能。你可以从[协议扩展](./22_Protocols.html#protocol_extensions)获取更多的细节。
+在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的类型拥有这些功能。你可以从[协议扩展](./21_Protocols.html#protocol_extensions)获取更多的细节。
> 注意
-扩展可以为一个类型添加新的功能,但是不能重写已有的功能。
+>
+> 扩展可以为一个类型添加新的功能,但是不能重写已有的功能。
## 扩展语法
@@ -62,10 +66,11 @@ extension SomeType: SomeProtocol, AnotherProctocol {
}
```
-通过这种方式添加协议一致性的详细描述请参阅[利用扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)。
+通过这种方式添加协议一致性的详细描述请参阅[利用扩展添加协议一致性](./21_Protocols.html#adding_protocol_conformance_with_an_extension)。
> 注意
-如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。
+>
+> 如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。
## 计算型属性
@@ -103,7 +108,8 @@ print("A marathon is \(aMarathon) meters long")
```
> 注意
-扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。
+>
+> 扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。
## 构造器
@@ -113,7 +119,8 @@ print("A marathon is \(aMarathon) meters long")
扩展能为类添加新的便利构造器,但是它们不能为类添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。
> 注意
-如果你使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何定制的构造器且所有存储属性提供了默认值,那么我们就可以在扩展中的构造器里调用默认构造器和逐一成员构造器。
+>
+> 如果你使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何定制的构造器且所有存储属性提供了默认值,那么我们就可以在扩展中的构造器里调用默认构造器和逐一成员构造器。
正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你把定制的构造器写在值类型的原始实现中,上述规则将不再适用。
下面的例子定义了一个用于描述几何矩形的结构体 `Rect`。这个例子同时定义了两个辅助结构体 `Size` 和 `Point`,它们都把 `0.0` 作为所有属性的默认值:
@@ -159,7 +166,8 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
```
> 注意
-如果你使用扩展提供了一个新的构造器,你依旧有责任确保构造过程能够让实例完全初始化。
+>
+> 如果你使用扩展提供了一个新的构造器,你依旧有责任确保构造过程能够让实例完全初始化。
## 方法
@@ -189,16 +197,6 @@ extension Int {
// Hello!
```
-可以使用尾随闭包让调用更加简洁:
-
-```swift
-3.repetitions {
- print("Goodbye!")
-}
-// Goodbye!
-// Goodbye!
-// Goodbye!
-```
### 可变实例方法
@@ -264,16 +262,16 @@ extension Int {
```swift
extension Int {
enum Kind {
- case Negative, Zero, Positive
+ case negative, zero, positive
}
var kind: Kind {
switch self {
case 0:
- return .Zero
+ return .zero
case let x where x > 0:
- return .Positive
+ return .positive
default:
- return .Negative
+ return .negative
}
}
}
@@ -289,11 +287,11 @@ extension Int {
func printIntegerKinds(_ numbers: [Int]) {
for number in numbers {
switch number.kind {
- case .Negative:
+ case .negative:
print("- ", terminator: "")
- case .Zero:
+ case .zero:
print("0 ", terminator: "")
- case .Positive:
+ case .positive:
print("+ ", terminator: "")
}
}
@@ -306,4 +304,5 @@ printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
函数 `printIntegerKinds(_:)` 接受一个 `Int` 数组,然后对该数组进行迭代。在每次迭代过程中,对当前整数的计算型属性 `kind` 的值进行评估,并打印出适当的描述。
> 注意
-由于已知 `number.kind` 是 `Int.Kind` 类型,因此在 `switch` 语句中,`Int.Kind` 中的所有成员值都可以使用简写形式,例如使用 `. Negative` 而不是 `Int.Kind.Negative`。
+>
+> 由于已知 `number.kind` 是 `Int.Kind` 类型,因此在 `switch` 语句中,`Int.Kind` 中的所有成员值都可以使用简写形式,例如使用 `.negative` 而不是 `Int.Kind.negative`。
diff --git a/source/chapter2/22_Protocols.md b/source/chapter2/21_Protocols.md
similarity index 89%
rename from source/chapter2/22_Protocols.md
rename to source/chapter2/21_Protocols.md
index 8f7adc4b..53567b92 100644
--- a/source/chapter2/22_Protocols.md
+++ b/source/chapter2/21_Protocols.md
@@ -17,7 +17,12 @@
>
> 3.0
> 校对:[CMB](https://github.com/chenmingbiao),版本日期:2016-09-13
-> 3.0.1,shanks,2016-11-13
+
+> 3.0.1
+> shanks,2016-11-13
+
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
@@ -72,7 +77,7 @@ class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
## 属性要求
-协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
+协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是*可读可写的*。
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
@@ -158,7 +163,7 @@ protocol RandomNumberGenerator {
}
```
-`RandomNumberGenerator` 协议要求遵循协议的类型必须拥有一个名为 `random`, 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值在 `[0.0,1.0)` 区间内。
+`RandomNumberGenerator` 协议要求遵循协议的类型必须拥有一个名为 `random`, 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值是从0.0到(但不包括)1.0。
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
@@ -183,13 +188,14 @@ print("And another one: \(generator.random())")
```
-## Mutating 方法要求
+## 异变方法要求
-有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。
+有时需要在方法中改变(或*异变*)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。
如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
> 注意
+>
> 实现协议中的 `mutating` 方法时,若是类类型,则不用写 `mutating` 关键字。而对于结构体和枚举,则必须写 `mutating` 关键字。
如下所示,`Togglable` 协议只要求实现一个名为 `toggle` 的实例方法。根据名称的暗示,`toggle()` 方法将改变实例属性,从而切换遵循该协议类型的实例的状态。
@@ -220,7 +226,7 @@ enum OnOffSwitch: Togglable {
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
-// lightSwitch 现在的值为 .On
+// lightSwitch 现在的值为 .on
```
@@ -234,7 +240,7 @@ protocol SomeProtocol {
}
```
-### 构造器要求在类中的实现
+### 协议构造器要求的类实现
你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符:
@@ -251,6 +257,7 @@ class SomeClass: SomeProtocol {
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
> 注意
+>
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required` 和 `override` 修饰符:
@@ -293,6 +300,7 @@ class SomeSubClass: SomeSuperClass, SomeProtocol {
* 作为数组、字典或其他容器中的元素类型
> 注意
+>
> 协议是一种类型,因此协议类型的名称应与其他类型(例如 `Int`,`Double`,`String`)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed` 和 `RandomNumberGenerator`)。
下面是将协议作为类型使用的例子:
@@ -334,7 +342,7 @@ for _ in 1...5 {
```
-## 委托(代理)模式
+## 委托
*委托*是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
@@ -389,7 +397,7 @@ class SnakesAndLadders: DiceGame {
}
```
-关于这个蛇梯棋游戏的详细描述请参阅 [控制流](./05_Control_Flow.html) 章节中的 [Break](./05_Control_Flow.html#break) 部分。
+关于这个*蛇梯棋*游戏的详细描述请参阅 [中断(Break)](./05_Control_Flow.html#break)。
这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类遵循了 `DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。)
@@ -446,11 +454,12 @@ game.play()
```
-## 通过扩展添加协议一致性
+## 在扩展里添加协议遵循
-即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./21_Extensions.html)章节中查看。
+即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。
> 注意
+>
> 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
@@ -493,10 +502,29 @@ print(game.textualDescription)
// 打印 “A game of Snakes and Ladders with 25 squares”
```
-
-## 通过扩展遵循协议
+
+## 有条件地遵循协议
-当一个类型已经符合了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展来遵循该协议:
+泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where`分句。更多关于泛型 `where` 分句,见[泛型Where分句](./22_Generics.html##where_clauses)。
+
+下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable`协议的元素时就遵循 `TextRepresentable` 协议。
+
+```swift
+extension Array: TextRepresentable where Element: TextRepresentable {
+ var textualDescription: String {
+ let itemsAsText = self.map { $0.textualDescription }
+ return "[" + itemsAsText.joined(separator: ", ") + "]"
+ }
+}
+let myDice = [d6, d12]
+print(myDice.textualDescription)
+// 打印 "[A 6-sided dice, A 12-sided dice]"
+```
+
+
+## 在扩展里声明采纳协议
+
+当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展采纳该协议:
```swift
struct Hamster {
@@ -518,12 +546,13 @@ print(somethingTextRepresentable.textualDescription)
```
> 注意
+>
> 即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。
## 协议类型的集合
-协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./22_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
+协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./21_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
```swift
let things: [TextRepresentable] = [game, d12, simonTheHamster]
@@ -599,25 +628,28 @@ print(game.prettyTextualDescription)
```
-## 类类型专属协议
+## 类专属的协议
-你可以在协议的继承列表中,通过添加 `class` 关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。`class` 关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:
+你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。
```swift
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
- // 这里是类类型专属协议的定义部分
+ // 这里是类专属协议的定义部分
}
```
-在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型遵循。如果尝试让结构体或枚举类型遵循该协议,则会导致编译错误。
+在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳`SomeClassOnlyProtocol`,则会导致编译时错误。
> 注意
+>
> 当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)和[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。
## 协议合成
-有时候需要同时遵循多个协议,你可以将多个协议采用 `SomeProtocol & AnotherProtocol` 这样的格式进行组合,称为 *协议合成(protocol composition)*。你可以罗列任意多个你想要遵循的协议,以与符号(`&`)分隔。
+要求一个类型同时遵循多个协议是很有用的。你可以使用*协议组合*来复合多个协议到一个要求里。协议组合行为就和你定义的临时局部协议一样拥有构成中所有协议的需求。协议组合不定义任何新的协议类型。
+
+协议组合使用 `SomeProtocol & AnotherProtocol` 的形式。你可以列举任意数量的协议,用和符号(`&`)分开。除了协议列表,协议组合也能包含类类型,这允许你标明一个需要的父类。
下面的例子中,将 `Named` 和 `Aged` 两个协议按照上述语法组合成一个协议,作为函数参数的类型:
@@ -640,9 +672,9 @@ 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`。这意味着它不关心参数的具体类型,只要参数符合这两个协议即可。
+`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged`, 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数符合这两个协议即可。
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
@@ -680,7 +712,7 @@ beginConcert(in: seattle)
## 检查协议一致性
-你可以使用[类型转换](./20_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
+你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`。
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。
@@ -758,7 +790,7 @@ for object in objects {
使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。
-协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./17_Optional_Chaining.html)章节中查看。
+协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./16_Optional_Chaining.html)章节中查看。
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求:
@@ -796,7 +828,7 @@ class Counter {
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `increment(forCount:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `increment(forCount:)` 方法,因为这个方法是可选的。因此,`increment(forCount:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `increment(forCount:)` 方法后边也加上了 `?`。
-调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅[连接多层可选链式调用](./17_Optional_Chaining)
+调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅[连接多层可选链式调用](./16_Optional_Chaining)
在调用 `increment(forCount:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。
@@ -892,6 +924,7 @@ print("And here's a random Boolean: \(generator.randomBool())")
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
> 注意
+>
> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性,只是简单地返回 `textualDescription` 属性的值:
@@ -907,36 +940,42 @@ extension PrettyTextRepresentable {
### 为协议扩展添加限制条件
-在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[Where子句](./23_Generics.html#where_clauses)中所描述的。
+在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型Where子句](./22_Generics.html#where_clauses)中所描述的。
-例如,你可以扩展 `CollectionType` 协议,但是只适用于集合中的元素遵循了 `TextRepresentable` 协议的情况:
+例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用`==`和`!=`操作符来检查两个元素的等价性和非等价性。
```swift
-extension Collection where Iterator.Element: TextRepresentable {
- var textualDescription: String {
- let itemsAsText = self.map { $0.textualDescription }
- return "[" + itemsAsText.joined(separator: ", ") + "]"
- }
+extension Collection where Element: Equatable {
+ func allEqual() -> Bool {
+ for element in self {
+ if element != self.first {
+ return false
+ }
+ }
+ return true
+ }
}
```
-`textualDescription` 属性返回整个集合的文本描述,它将集合中的每个元素的文本描述以逗号分隔的方式连接起来,包在一对方括号中。
+如果集合中的所有元素都一致,`allEqual()` 方法才返回 `true`。
-现在我们来看看先前的 `Hamster` 结构体,它符合 `TextRepresentable` 协议,同时这里还有个装有 `Hamster` 的实例的数组:
+
+看看两个整数数组,一个数组的所有元素都是一样的,另一个不一样:
```swift
-let murrayTheHamster = Hamster(name: "Murray")
-let morganTheHamster = Hamster(name: "Morgan")
-let mauriceTheHamster = Hamster(name: "Maurice")
-let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
+let equalNumbers = [100, 100, 100, 100, 100]
+let differentNumbers = [100, 100, 200, 100, 200]
```
+由于数组遵循`Collection`而且整数遵循`Equatable`, `equalNumbers` 和 `differentNumbers` 都可以使用 `allEqual()` 方法。
-因为 `Array` 符合 `CollectionType` 协议,而数组中的元素又符合 `TextRepresentable` 协议,所以数组可以使用 `textualDescription` 属性得到数组内容的文本表示:
```swift
-print(hamsters.textualDescription)
-// 打印 “[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]”
+print(equalNumbers.allEqual())
+// 打印 "true"
+print(differentNumbers.allEqual())
+// 打印 "false"
```
> 注意
-> 如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。
+>
+> 如果一个遵循的类型满足了为同一方法或属性提供实现的多个限制型扩展的要求, Swift 使用这个实现方法去匹配那个最特殊的限制。
diff --git a/source/chapter2/23_Generics.md b/source/chapter2/22_Generics.md
similarity index 88%
rename from source/chapter2/23_Generics.md
rename to source/chapter2/22_Generics.md
index 085acd8d..2dfe7fcf 100644
--- a/source/chapter2/23_Generics.md
+++ b/source/chapter2/22_Generics.md
@@ -1,6 +1,5 @@
# 泛型
-
-------
+-------------------
> 1.0
> 翻译:[takalard](https://github.com/takalard)
@@ -22,6 +21,9 @@
> 4.0
> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
+> 4.1
+> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
+
本页包含内容:
- [泛型所解决的问题](#the_problem_that_generics_solve)
@@ -87,7 +89,8 @@ func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
> 注意
-在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
+>
+> 在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
## 泛型函数
@@ -131,7 +134,8 @@ swapTwoValues(&someString, &anotherString)
```
> 注意
-上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
+>
+> 上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
## 类型参数
@@ -148,17 +152,19 @@ swapTwoValues(&someString, &anotherString)
在大多数情况下,类型参数具有一个描述性名字,例如 `Dictionaryhello, world
" ``` -> 注意 -上面的`paragraph`变量定义为可选类型的`HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。 +> 注意 +> +> 上面的`paragraph`变量定义为可选类型的`HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。 不幸的是,上面写的`HTMLElement`类产生了类实例和作为`asHTML`默认值的闭包之间的循环强引用。循环强引用如下图所示: @@ -483,8 +495,9 @@ print(paragraph!.asHTML()) 实例的`asHTML`属性持有闭包的强引用。但是,闭包在其闭包体内使用了`self`(引用了`self.name`和`self.text`),因此闭包捕获了`self`,这意味着闭包又反过来持有了`HTMLElement`实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。 -> 注意 -虽然闭包多次使用了`self`,它只捕获`HTMLElement`实例的一个强引用。 +> 注意 +> +> 虽然闭包多次使用了`self`,它只捕获`HTMLElement`实例的一个强引用。 如果设置`paragraph`变量为`nil`,打破它持有的`HTMLElement`实例的强引用,`HTMLElement`实例和它的闭包都不会被销毁,也是因为循环强引用: @@ -495,12 +508,13 @@ paragraph = nil 注意,`HTMLElement`的析构函数中的消息并没有被打印,证明了`HTMLElement`实例并没有被销毁。 -## 解决闭包引起的循环强引用 +## 解决闭包的循环强引用 在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。 -> 注意 -Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod()`(而不只是`someProperty`或`someMethod()`)。这提醒你可能会一不小心就捕获了`self`。 +> 注意 +> +> Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod()`(而不只是`someProperty`或`someMethod()`)。这提醒你可能会一不小心就捕获了`self`。 ### 定义捕获列表 @@ -532,8 +546,9 @@ lazy var someClosure: Void -> String = { 相反的,在被捕获的引用可能会变为`nil`时,将闭包内的捕获定义为`弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为`nil`。这使我们可以在闭包体内检查它们是否存在。 -> 注意 -如果被捕获的引用绝对不会变为`nil`,应该用无主引用,而不是弱引用。 +> 注意 +> +> 如果被捕获的引用绝对不会变为`nil`,应该用无主引用,而不是弱引用。 前面的`HTMLElement`例子中,无主引用是正确的解决循环强引用的方法。这样编写`HTMLElement`类来避免循环强引用: diff --git a/source/chapter2/24_Memory_Safe.md b/source/chapter2/24_Memory_Safety.md similarity index 97% rename from source/chapter2/24_Memory_Safe.md rename to source/chapter2/24_Memory_Safety.md index f6c90699..b9c5c684 100644 --- a/source/chapter2/24_Memory_Safe.md +++ b/source/chapter2/24_Memory_Safety.md @@ -1,4 +1,12 @@ # 内存安全 +-------------------- + +> 4.0 +> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + +> 4.1 +> 翻译+校对:[mylittleswift](https://github.com/mylittleswift) + 本页包含内容: @@ -122,7 +130,8 @@ balance(&playerOneScore, &playerOneScore) 上面的 `balance(_:_:)` 函数会将传入的两个参数平均化。将 `playerOneScore` 和 `playerTwoScore` 作为参数传入不会产生错误 —— 有两个访问重叠了,但它们访问的是不同的内存位置。相反,将 `playerOneScore` 作为参数同时传入就会产生冲突,因为它会发起两个写访问,同时访问同一个的存储地址。 > 注意 -因为操作符也是函数,它们也会对 in-out 参数进行长期访问。例如,假设 `balance(_:_:)` 是一个名为 `<^>` 的操作符函数,那么 `playerOneScore <^> playerOneScore` 也会造成像 `balance(&playerOneScore, &playerOneScore)` 一样的冲突。 +> +> 因为操作符也是函数,它们也会对 in-out 参数进行长期访问。例如,假设 `balance(_:_:)` 是一个名为 `<^>` 的操作符函数,那么 `playerOneScore <^> playerOneScore` 也会造成像 `balance(&playerOneScore, &playerOneScore)` 一样的冲突。 ## 方法里 self 的访问冲突 @@ -210,8 +219,4 @@ func someFunction() { 如果编译器无法保证访问的安全性,它就不会允许访问。 - -## 更新历史 -> 4.0 -> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21 diff --git a/source/chapter2/25_Access_Control.md b/source/chapter2/25_Access_Control.md index 6240229f..b7285158 100644 --- a/source/chapter2/25_Access_Control.md +++ b/source/chapter2/25_Access_Control.md @@ -21,6 +21,9 @@ > 4.0 > 翻译:kemchenj,2017-09-23 +> 4.1 +> 翻译+校对:[mylittleswift](https://github.com/mylittleswift) + 本页内容包括: - [模块和源文件](#modules_and_source_files) @@ -42,14 +45,15 @@ Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式声明代码的访问级别。 > 注意 -为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。 +> +> 为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。 ## 模块和源文件 Swift 中的访问控制模型基于模块和源文件这两个概念。 -模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。 +*模块*指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。 在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。 @@ -104,7 +108,8 @@ Swift 中的访问级别遵循一个基本原则:*不可以在某个实体中 当你开发框架时,就需要把一些对外的接口定义为 Open 或 Public,以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口,就是这个框架的 API。 > 注意 -框架依然会使用默认的 `internal` ,也可以指定为 `fileprivate` 访问或者 `private` 访问级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定开放访问或公开访问级别。 +> +> 框架依然会使用默认的 `internal` ,也可以指定为 `fileprivate` 访问或者 `private` 访问级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定开放访问或公开访问级别。 ### 单元测试 target 的访问级别 @@ -143,7 +148,8 @@ var someInternalConstant = 0 // 隐式 internal 一个类型的访问级别也会影响到类型*成员*(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 或者 `fileprivate` 级别,那么该类型的所有成员的默认访问级别也会变成 `private` 或者 `fileprivate` 级别。如果你将类型指定为公开或者 `internal` (或者不明确指定访问级别,而使用默认的 `internal` ),那么该类型的所有成员的默认访问级别将是内部访问。 > 重要 -上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。 +> +> 上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。 ```swift public class SomePublicClass { // 显式 public 类 @@ -175,7 +181,8 @@ private class SomePrivateClass { // 显式 private 类 元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal`,另一个类型为 `private`,那么这个元组的访问级别为 `private`。 > 注意 -元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。 +> +> 元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。 ### 函数类型 @@ -282,7 +289,8 @@ private var privateInstance = SomePrivateClass() `Setter` 的访问级别可以低于对应的 `Getter` 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 `var` 或 `subscript` 关键字之前,你可以通过 `fileprivate(set)`,`private(set)` 或 `internal(set)` 为它们的写入权限指定更低的访问级别。 > 注意 -这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`,Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的后备存储。使用 `fileprivate(set)`,`private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。 +> +> 这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`,Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的后备存储。使用 `fileprivate(set)`,`private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。 下面的例子中定义了一个名为 `TrackedString` 的结构体,它记录了 `value` 属性被修改的次数: @@ -357,7 +365,8 @@ public struct TrackedString { 协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。 > 注意 -如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。 +> +> 如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。 ### 协议继承 @@ -374,7 +383,8 @@ public struct TrackedString { 如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 `public` 级别的类型,采纳了 `internal` 级别的协议,那么协议的实现至少也得是 `internal` 级别。 > 注意 -Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。 +> +> Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。 ## Extension @@ -427,4 +437,5 @@ extension SomeStruct: SomeProtocol { 你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `private`,`file-private`,`internal`,`public`或者`open`类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal`,`file-private`,或 `private` 类型的别名。 > 注意 -这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。 +> +> 这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。 diff --git a/source/chapter2/25_Advanced_Operators.md b/source/chapter2/26_Advanced_Operators.md similarity index 96% rename from source/chapter2/25_Advanced_Operators.md rename to source/chapter2/26_Advanced_Operators.md index a581ae33..04afabdd 100644 --- a/source/chapter2/25_Advanced_Operators.md +++ b/source/chapter2/26_Advanced_Operators.md @@ -16,14 +16,19 @@ > > 3.0 > 翻译+校对:[mmoaay](https://github.com/mmoaay) 2016-09-20 -> 3.0.1:shanks,2016-11-13 + +> 3.0.1 +> shanks,2016-11-13 + +> 4.1 +> 翻译+校对:[mylittleswift](https://github.com/mylittleswift) 本页内容包括: - [位运算符](#bitwise_operators) - [溢出运算符](#overflow_operators) - [优先级和结合性](#precedence_and_associativity) -- [运算符函数](#operator_functions) +- [运算符函数](#operator_methods) - [自定义运算符](#custom_operators) 除了在之前介绍过的[基本运算符](./02_Basic_Operators.html),Swift 中还有许多可以对数值进行复杂运算的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。 @@ -308,6 +313,7 @@ signedOverflow = signedOverflow &- 1 如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。如果想查看 Swift 标准库提供所有的运算符,请查看 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。 > 注意 +> > 相对 C 语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。 @@ -397,6 +403,7 @@ original += vectorToAdd ``` > 注意 +> > 不能对默认的赋值运算符(`=`)进行重载。只有组合赋值运算符可以被重载。同样地,也无法对三目条件运算符 (`a ? b : c`) 进行重载。 @@ -404,16 +411,13 @@ original += vectorToAdd 自定义的类和结构体没有对*等价运算符*进行默认实现,等价运算符通常被称为“相等”运算符(`==`)与“不等”运算符(`!=`)。对于自定义类型,Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。 -为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样: +为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样, 并且增加对标准库 `Equatable` 协议的遵循: ```swift -extension Vector2D { +extension Vector2D: Equatable { static func == (left: Vector2D, right: Vector2D) -> Bool { return (left.x == right.x) && (left.y == right.y) } - static func != (left: Vector2D, right: Vector2D) -> Bool { - return !(left == right) - } } ``` @@ -429,6 +433,28 @@ if twoThree == anotherTwoThree { } // 打印 “These two vectors are equivalent.” ``` +Swift 为以下自定义类型提等价运算符供合成实现: + +- 只拥有遵循 `Equatable` 协议存储属性的结构体; +- 只拥有遵循 `Equatable` 协议关联类型的枚举; +- 没有关联类型的枚举。 + +在类型原本的声明中声明遵循 `Equatable` 来接收这些默认实现。 + +下面为三维位置向量 `(x, y, z)` 定义的 `Vector3D` 结构体,与 `Vector2D` 类似,由于 `x` , `y` 和 `z` 属性都是 `Equatable` 类型, `Vector3D` 就收到默认的等价运算符实现了。 + +```swift +struct Vector3D: Equatable { + var x = 0.0, y = 0.0, z = 0.0 +} + +let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) +let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) +if twoThreeFour == anotherTwoThreeFour { + print("These two vectors are also equivalent.") +} +// Prints "These two vectors are also equivalent." +``` ## 自定义运算符 @@ -482,4 +508,5 @@ let plusMinusVector = firstVector +- secondVector 这个运算符把两个向量的 `x` 值相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“相加型”运算符,所以将它放置 `+` 和 `-` 等默认的中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考[运算符声明](#operator_declaration) > 注意 +> > 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。 diff --git a/source/chapter3/01_About_the_Language_Reference.md b/source/chapter3/01_About_the_Language_Reference.md index db6da062..4690d564 100755 --- a/source/chapter3/01_About_the_Language_Reference.md +++ b/source/chapter3/01_About_the_Language_Reference.md @@ -8,13 +8,16 @@ > 2.0 > 翻译+校对:[KYawn](https://github.com/KYawn) +> 4.1 +> 翻译+校对:[mylittleswift](https://github.com/mylittleswift) + 本页内容包括: - [如何阅读语法](#how_to_read_the_grammar) -本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您更详细地了解该语言,而不是让您直接实现一个解析器或编译器。 +本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您了解该语言的更多细节,而不是让您直接实现一个解析器或编译器。 -Swift 语言相对较小,这是由于 Swift 代码中的几乎所有常见类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。 +Swift 语言相对较小,这是由于 Swift 代码中在各种地方出现的常见的类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。 ## 如何阅读语法 @@ -23,18 +26,20 @@ Swift 语言相对较小,这是由于 Swift 代码中的几乎所有常见类 - 箭头(`→`)用来标记语法产式,可以理解为“可由……构成”。 - 斜体文字用来表示句法类型,并出现在一个语法产式规则两侧。 -- 关键字和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。 +- 标记语言和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。 - 可供选择的语法产式由竖线(`|`)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。 -- 少数情况下,语法产式规则的右侧会有用于描述的常规字体文字。 -- 可选的句法类型和字面值用尾标 `opt` 来标记。 +- 少数情况下,标准字体文本被用来描述一个语法产生规则的右手侧内容。 +- 可选的句法类型和文本标记用尾标 `opt` 来标记。 -举个例子,getter-setter 的语法块的定义如下: +举个例子,getter-setter 方法块的语法定义如下: -> getter-setter 方法块语法 +> getter-setter 方法块语法 +> > *getter-setter 方法块* → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)可选 } | { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) } -这个定义表明,一个 getter-setter 方法块可以由一个 getter 子句后跟一个可选的 setter 子句构成,然后用大括号括起来,或者由一个 setter 子句后跟一个 getter 子句构成,然后用大括号括起来。下面的两个语法产式等价于上述的语法产式,并明确指出了如何取舍: +这个定义表明,一个 getter-setter 方法块可以由一个 getter 分句后跟一个可选的 setter 分句构成,然后用大括号括起来,或者由一个 setter 分句后跟一个 getter 分句构成,然后用大括号括起来。上述的语法产式等价于下面的两个语法产式, : -> getter-setter 方法块语法 +> getter-setter 方法块语法 +> > getter-setter 方法块 → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)可选 } > getter-setter 方法块 → { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) } diff --git a/source/chapter3/02_Lexical_Structure.md b/source/chapter3/02_Lexical_Structure.md index 9ec8669c..57c83305 100755 --- a/source/chapter3/02_Lexical_Structure.md +++ b/source/chapter3/02_Lexical_Structure.md @@ -12,7 +12,10 @@ > 翻译:[mmoaay](https://github.com/mmoaay) > 2.2 -> 翻译+校对:[星夜暮晨](https://github.com/semperidem),2016-04-06 +> 翻译+校对:[星夜暮晨](https://github.com/semperidem) + +> 4.1 +> 翻译+校对:[mylittleswift](https://github.com/mylittleswift) 本页包含内容: @@ -25,7 +28,7 @@ - [字符串字面量](#string_literals) - [运算符](#operators) -Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中合法符号 (token) 的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符 (identifier)、关键字 (keyword)、标点符号 (punctuation)、字面量 (literal) 或运算符 (operator) 组成。 +Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中有效符号 (token) 的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符 (identifier)、关键字 (keyword)、标点符号 (punctuation)、字面量 (literal) 或运算符 (operator) 组成。 通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配 (longest match)”*,或者*“最大适合(maximal munch)”*。 @@ -36,7 +39,31 @@ Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中 注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(U+000A)或者回车符(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。 -正如 [*Markup Formatting Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所述,注释可以包含附加的格式和标记。 +> 空白语法 + + +> *空白* → [*空白项*](#whitespace-item) [*空白*](#whitespace)可选 +> *空白项* → [*断行符*](#line-break) +> *空白项* → [*注释*](#comment) +> *空白项* → [*多行注释*](#multiline-comment) +> *空白项* → U+0000,U+0009,U+000B,U+000C 或者 U+0020 + + +> *断行符* → U+000A +> *断行符* → U+000D +> *断行符* → U+000D 接着是 U+000A + + +> *注释* → // [*注释内容 断行*](#comment-text line-break) +> *多行注释* → `/*` [*多行注释内容*](#multiline-commnet-text) `*/` +> *注释内容* → [*注释内容项*](#comment-text-item) [*注释内容*](#comment-text)可选 +> *注释内容项* → 任何Unicode标量值, 除了 U+000A 或者 U+000D +> *多行注释内容* → [*多行注释内容项*](#multiline-comment-text-item) [*多行注释内容*](#multiline-comment-text)可选 +> *多行注释内容项* → [*多行注释*](#multiline-comment). +> *多行注释内容项* → [*注释内容项*](#comment-text-item) +> *多行注释内容项* → 任何Unicode标量值, 除了 `/*` 或者 `*/` + +注释可以包含额外的格式和标记,正如 [*Markup Formatting Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所述。 ## 标识符 @@ -117,7 +144,8 @@ true // 布尔值字面量 当为一个字面量值指定了类型标注的时候,这个标注的类型必须能通过这个字面量值实例化。也就是说,这个类型必须符合这些 Swift 标准库协议中的一个:整数字面量的 `IntegerLiteralConvertible` 协议、浮点数字面量的 `FloatingPointLiteralConvertible` 协议、字符串字面量的 `StringLiteralConvertible` 协议以及布尔值字面量的 `BooleanLiteralConvertible` 协议。比如,`Int8` 符合 `IntegerLiteralConvertible` 协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整数字面量 `42` 的类型标注。 -> 字面量语法 +> 字面量语法 +> > *字面量* → [*数值字面量*](#numeric-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#boolean-literal) | [*nil 字面量*](#nil-literal) @@ -141,7 +169,7 @@ true // 布尔值字面量 除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.html#integers)。 > 整数字面量语法 - +> > *整数字面量* → [*二进制字面量*](#binary-literal) > *整数字面量* → [*八进制字面量*](#octal-literal) @@ -204,7 +232,7 @@ true // 布尔值字面量 除非特别指定,浮点数字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示 64 位浮点数。Swift 标准库也定义了 `Float` 类型,表示 32 位浮点数。 > 浮点数字面量语法 - +> > *浮点数字面量* → [*十进制字面量*](#decimal-literal) [*十进制分数*](#decimal-fraction)可选 [*十进制指数*](#decimal-exponent)可选 > *浮点数字面量* → [*十六进制字面量*](#hexadecimal-literal) [*十六进制分数*](#hexadecimal-fraction)可选 [*十六进制指数*](#hexadecimal-exponent) @@ -229,12 +257,27 @@ true // 布尔值字面量 ### 字符串字面量 -字符串字面量由被包在双引号中的一串字符组成,形式如下: +字符串字面量是被引号包括的一串字符组成。 单行字符串字面量被包在双引号中的一串字符组成,形式如下: > "`字符`" 字符串字面量中不能包含未转义的双引号(`"`)、未转义的反斜线(`\`)、回车符、换行符。 +多行字符串字面量被包在三个双引号中的一串字符组成,形式如下: +> """ +> `字符` +> """ + +与单行字符串字面量不同的是,多行字符串字面量可以包含不转义的双引号( " ),回车以及换行。它不能包含三个非转义的连续双引号。 + +""" 之后的回车或者换行开始多行字符串字面量,不是字符串的一部分。 """ 之前回车或者换行结束字面量,也不是字符串的一部分。要让多行字符串字面量的开始或结束带有换行,就在第一行或者最后一行写一个空行。 + +多行字符串字面量可以使用任何空格或制表符组合进行缩进;这些缩进不会包含在字符串中。 """ 的结束符号决定了缩进:字面量中的任何一个非空行必须起始于多行字符串字面量结束符号的前面;空格和制表符不会被转换。你可以包在缩进后含额外的空格和制表符;这些空格和制表符会在字符串中出现。 + +多行字符串字面量中的一行结束使用规范化的换行符号。尽管你的源代码混用了回车和换行符,字符串中所有的行结束都必须一样. + +在多行字符串字面量里, 在行末用反斜线(`\`)可以省略字符串行间中断。 反斜线之间的空白和行间中断也可以省略。 你可以在你的代码里用这种语法硬包裹多行字符串字面量,不需要改变产生的字符串的值。 + 可以在字符串字面量中使用的转义特殊符号如下: * 空字符 `\0` @@ -246,7 +289,7 @@ true // 布尔值字面量 * 单引号 `\'` * Unicode 标量 `\u{`n`}`,n 为一到八位的十六进制数字 -字符串字面量允许在反斜杠 (`\`) 后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的双引号 (`"`)、未转义的反斜线 (`\`)、回车符以及换行符。 +字符串字面量允许在反斜杠 (`\`) 后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的反斜线 (`\`)、回车符以及换行符。 例如,以下所有字符串字面量的值都是相同的: @@ -258,7 +301,7 @@ true // 布尔值字面量 let x = 3; "1 2 \(x)" ``` -字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及 [*String Structure Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。 +字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及 [*字符串结构参考*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。 用 `+` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA` 和 `textB` 是完全一样的,`textA` 没有任何运行时的连接操作。 @@ -268,7 +311,7 @@ let textB = "Hello world" ``` > 字符串字面量语法 - +> > *字符串字面量* → [*静态字符串字面量*](#static-string-literal) | [*插值字符串字面量*](#interpolated-string-literal) @@ -304,7 +347,8 @@ Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基 虽然您可以用问号 `?` 来自定义运算符,但是这个运算符不能只包含单独的一个问号。此外,虽然运算符可以包含一个惊叹号 `!`,但是前缀运算符不能够以问号或者惊叹号开头。 -> 注意 +> 注意 +> > 以下这些标记 `=`、`->`、`//`、`/*`、`*/`、`.`、`<`(前缀运算符)、`&`、`?`、`?`(中缀运算符)、`>`(后缀运算符)、`!` 、`?` 是被系统保留的。这些符号不能被重载,也不能用于自定义运算符。 运算符两侧的空白被用来区分该运算符是否为前缀运算符、后缀运算符或二元运算符。规则总结如下: @@ -323,7 +367,7 @@ Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基 要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/25_Advanced_Operators.html#operator_functions)。 > 运算符语法 - +> > *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)可选 > *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)可选 diff --git a/source/chapter3/03_Types.md b/source/chapter3/03_Types.md index d3cccf37..5a90b099 100644 --- a/source/chapter3/03_Types.md +++ b/source/chapter3/03_Types.md @@ -10,6 +10,9 @@ > 2.1 > 翻译:[mmoaay](https://github.com/mmoaay) + +> 4.1 +> 翻译+校对:[mylittleswift](https://github.com/mylittleswift) 本页包含内容: @@ -30,13 +33,27 @@ Swift 语言存在两种类型:命名型类型和复合型类型。命名型 那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/21_Extensions.html) 和 [扩展声明](05_Declarations.html#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。 -复合型类型是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。 +复合型类型是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。 + +你可以在命名型类型和复合型类型使用小括号。但是在类型旁加小括号没有任何作用。举个例子,`(Int)`等同于`Int`。 本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。 -> 类型语法 +> 类型语法 +> -> *类型* → [*数组类型*](#array-type) | [*字典类型*](#dictionary-type) | [*函数类型*](#function-type) | [*类型标识*](#type-identifier) | [*元组类型*](#tuple-type) | [*可选类型*](#optional-type) | [*隐式解析可选类型*](#implicitly-unwrapped-optional-type) | [*协议合成类型*](#protocol-composition-type) | [*元型类型*](#metatype-type) | **任意类型** | **自身类型** +> *类型* → [*数组类型*](#array-type) +> *类型* → [*字典类型*](#dictionary-type) +> *类型* → [*函数类型*](#function-type) +> *类型* → [*类型标识*](#type-identifier) +> *类型* → [*元组类型*](#tuple-type) +> *类型* → [*可选类型*](#optional-type) +> *类型* → [*隐式解析可选类型*](#implicitly-unwrapped-optional-type) +> *类型* → [*协议合成类型*](#protocol-composition-type) +> *类型* → [*元型类型*](#metatype-type) +> *类型* → **任意类型** +> *类型* → **自身类型** +> *类型* → [*(类型)*](#type) ## 类型注解 @@ -51,7 +68,8 @@ func someFunction(a: Int) { /* ... */ } 类型注解可以在类型之前包含一个类型特性的可选列表。 -> 类型注解语法 +> 类型注解语法 +> > *类型注解* → **:** [*特性列表*](06_Attributes.html#attributes)可选 **输入输出参数**可选 [*类型*](#type) @@ -75,7 +93,8 @@ let origin: Point = (0, 0) var someValue: ExampleModule.MyType ``` -> 类型标识符语法 +> 类型标识符语法 +> > *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)可选 | [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)可选 **.** [*类型标识符*](#type-identifier) @@ -97,16 +116,17 @@ someTuple = (9, 99) // 正确:命名类型被自动推断 someTuple = (left: 5, right: 5) // 错误:命名类型不匹配 ``` -`Void` 是空元组类型 `()` 的别名。如果括号内只有一个元素,那么该类型就是括号内元素的类型。比如,`(Int)` 的类型是 `Int` 而不是 `(Int)`。所以,只有当元组类型包含的元素个数在两个及以上时才可以命名元组元素。 +所有的元组类型都包含两个及以上元素, 除了`Void`。 `Void` 是空元组类型 `()` 的别名。 -> 元组类型语法 +> 元组类型语法 +> > *元组类型* → **(** [*元组类型元素列表*](#tuple-type-element-list) 可选 **)** > *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) > *元组类型元素* → [*元素名*](#element-name) [*类型注解*](#type-annotation) | [*类型*](#type) - + > *元素名* → [*标识符*](02_Lexical_Structure.html#identifier) @@ -118,13 +138,15 @@ someTuple = (left: 5, right: 5) // 错误:命名类型不匹配 参数类型是由逗号间隔的类型列表。由于参数类型和返回值类型可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。 -你可以对函数参数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被使用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.html#autoclosures) 。 +你可以对函数参数`() - > T `(其中 T 是任何类型)使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.html#autoclosures) 。 函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.html#variadic_parameters)。 -为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。 +为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。 + +如果一个函数类型只有一个形式参数而且形式参数的类型是元组类型,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说, `((Int, Int)) -> Void` 是接收一个元组 `(Int, Int)` 作为形式参数的函数的类型。与此相反,不加括号的`(Int, Int) -> Void` 是一个接收两个 `Int` 形式参数并且不返回任何值的函数的类型。相似地,因为`Void` 是空元组类型 `()` 的别名, 函数类型`(Void)-> Void`与一个空元组的变量的函数类型`(()) -> ()`是一样的。但这些类型和无变量的函数类型`() -> ()`是不一样的。 -函数和方法中的参数名并不是函数类型的一部分。例如: +函数和方法中的变量名并不是函数类型的一部分。例如: ```swift func someFunction(left: Int, right: Int) {} @@ -141,22 +163,55 @@ func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {} f = functionWithDifferentArgumentTypes // 错误 f = functionWithDifferentNumberOfArguments // 错误 ``` + +由于变量标签不是函数类型的一部分,你可以在写函数类型的时候省略它们。 + +``` +var operation: (lhs: Int, rhs: Int) -> Int // 错误 +var operation: (_ lhs: Int, _ rhs: Int) -> Int // 正确 +var operation: (Int, Int) -> Int // 正确 +``` 如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型 `Int -> Int -> Int` 可以理解为 `Int -> (Int -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数类型。 函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](05_Declarations.html#throwing_functions_and_methods) 和 [重抛函数与方法](05_Declarations.html#rethrowing_functions_and_methods)。 + + +### 对非逃逸闭包的限制 +非逃逸闭包函数不能作为参数传递到另一个非逃逸闭包函数的参数。这样的限制可以让Swift在编译时就完成更多的内存访问冲突检查, 而不是在运行时。举个例子: + +``` +let external: (Any) -> Void = { _ in () } +func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) { + first(first) // 错误 + second(second) // 错误 + + first(second) // 错误 + second(first) // 错误 + + first(external) // 正确 + external(first) // 正确 +} +``` +在上面代码里,`takesTwoFunctions(first:second:)`的两个参数都是函数。 它们都没有标记为`@escaping`, 因此它们都是非逃逸的。 + +上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为第一个和第二个参数是非逃逸函数,它们不能够被当作变量被传递到另一个非闭包函数参数。与此相反, 标记“正确”的两个函数不回产生编译错误。这些函数调用不会违反限制, 因为`外部(external)`不是`takesTwoFunctions(first:second:)`里的一个参数。 + +如果你需要避免这个限制, 标记其中之一的参数为逃逸, 或者使用`withoutActuallyEscaping(_:do:)`函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅[内存安全](../chapter2/24_Memory_Safety.html)。 + -> 函数类型语法 +> 函数类型语法 +> -> *函数类型* → [*特性列表*](06_Attributes.html#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **throws**可选 **->** [*类型*](#type) -> *函数类型* → [*特性列表*](06_Attributes.html#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **rethrows** **->** [*类型*](#type) +> *函数类型* → [*特性列表*](06_Attributes.html#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **throws**可选 **->** [*类型*](#type) +> *函数类型* → [*特性列表*](06_Attributes.html#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **rethrows** **->** [*类型*](#type) -> *函数类型子句* → () -> *函数类型子句* → ([*函数类型参数列表*](#function-type-argument-list)*...*可选) +> *函数类型子句* → () +> *函数类型子句* → ([*函数类型参数列表*](#function-type-argument-list)*...*可选) -> *函数类型参数列表* → [*函数类型参数*](function-type-argument) | [*函数类型参数*](function-type-argument), [*函数类型参数列表*](#function-type-argument-list) +> *函数类型参数列表* → [*函数类型参数*](function-type-argument) | [*函数类型参数*](function-type-argument), [*函数类型参数列表*](#function-type-argument-list) -> *函数类型参数* → [*特性列表*](06_Attributes.html#attributes)可选 **输入输出参数**可选 [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation) +> *函数类型参数* → [*特性列表*](06_Attributes.html#attributes)可选 **输入输出参数**可选 [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation) > *参数标签* → [*标识符*](02_Lexical_Structure.html#identifier) @@ -186,7 +241,8 @@ var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] 关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.html#arrays)。 -> 数组类型语法 +> 数组类型语法 +> > *数组类型* → **[** [*类型*](#type) **]** @@ -212,7 +268,8 @@ let someDictionary: Dictionary