修改 Access Control(访问控制)章节中,协议-协议遵循部分的内容。 (#833)

* 1.修改 Access Control(访问控制)章节中,协议-协议遵循部分的内容,修改原因的详细解释见:https://github.com/pmtao/TechTalk/issues/2
其中原小标题《协议一致性》,建议改为《协议遵循》,意思更准确也更符合大部分类似技术资料的叫法。
-----
提交人:镜画者,发现有任何问题请联系我:pmtnmd@gmail.com。
Commit by Meler Paine, found any problem please contact: pmtnmd@gmail.com.

* 统一协议遵循的叫法,将翻译中所有类似含义的叫法,如:“协议一致性”、“符合”、“遵从”、“遵守”、“采纳”、“满足”等统一修改为“遵循”。
-----
提交人:镜画者,发现有任何问题请联系我:pmtnmd@gmail.com。
Commit by Meler Paine, found any problem please contact: pmtnmd@gmail.com.
This commit is contained in:
Meler Paine
2018-12-06 11:10:28 +08:00
committed by Jie Liang
parent d1ddc1a8ec
commit 6c3269d13f
5 changed files with 65 additions and 65 deletions

View File

@ -265,9 +265,9 @@ Swift 的所有基本类型(比如 `String`,`Int`,`Double` 和 `Bool`)默认
> 注意 > 注意
> >
> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 Swift 标准库中的 `Hashable` 协议。符合 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 > 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型遵循 Swift 标准库中的 `Hashable` 协议。遵循 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。
> >
> 因为 `Hashable` 协议符合 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何符合 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况: > 因为 `Hashable` 协议遵循 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何遵循 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况:
> * `a == a`(自反性) > * `a == a`(自反性)
> * `a == b` 意味着 `b == a`(对称性) > * `a == b` 意味着 `b == a`(对称性)

View File

@ -4,7 +4,7 @@
类型转换在 Swift 中使用 `is``as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。 类型转换在 Swift 中使用 `is``as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./21_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。 你也可以用它来检查一个类型是否遵循了某个协议,就像在[检验协议遵循](./21_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
<a name="defining_a_class_hierarchy_for_type_casting"></a> <a name="defining_a_class_hierarchy_for_type_casting"></a>
## 为类型转换定义类层次 ## 为类型转换定义类层次

View File

@ -77,7 +77,7 @@ let john = Person(fullName: "John Appleseed")
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。 这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足`FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。) `Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好遵循`FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地遵循了协议。(如果协议要求未被完全遵循,在编译时会报错。)
下面是一个更为复杂的类,它适配并遵循了 `FullyNamed` 协议: 下面是一个更为复杂的类,它适配并遵循了 `FullyNamed` 协议:
@ -124,7 +124,7 @@ protocol RandomNumberGenerator {
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。 `RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
如下所示,下边是一个遵循并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器linear congruential generator* 的伪随机数算法。 如下所示,下边是一个遵循 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器linear congruential generator* 的伪随机数算法。
```swift ```swift
class LinearCongruentialGenerator: RandomNumberGenerator { class LinearCongruentialGenerator: RandomNumberGenerator {
@ -209,7 +209,7 @@ class SomeClass: SomeProtocol {
} }
``` ```
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。 使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议。
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。 关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
@ -392,7 +392,7 @@ class DiceGameTracker: DiceGameDelegate {
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。 `gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice``sides` 属性的值。 无论当前进行的是何种游戏,由于 `game` 遵循 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice``sides` 属性的值。
`DiceGameTracker` 的运行情况如下所示: `DiceGameTracker` 的运行情况如下所示:
@ -413,11 +413,11 @@ game.play()
<a name="adding_protocol_conformance_with_an_extension"></a> <a name="adding_protocol_conformance_with_an_extension"></a>
## 在扩展里添加协议遵循 ## 在扩展里添加协议遵循
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。 即便无法修改源代码,依然可以通过扩展令已有类型遵循协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以遵循协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。
> 注意 > 注意
> >
> 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。 > 通过扩展令已有类型遵循协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述: 例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
@ -427,7 +427,7 @@ protocol TextRepresentable {
} }
``` ```
可以通过扩展,令先前提到的 `Dice` 类遵循并符合 `TextRepresentable` 协议: 可以通过扩展,令先前提到的 `Dice` 类遵循 `TextRepresentable` 协议:
```swift ```swift
extension Dice: TextRepresentable { extension Dice: TextRepresentable {
@ -437,7 +437,7 @@ extension Dice: TextRepresentable {
} }
``` ```
通过扩展遵循并符合协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。 通过扩展遵循协议,和在原始定义中遵循协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型: 现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
@ -447,7 +447,7 @@ print(d12.textualDescription)
// 打印 “A 12-sided dice” // 打印 “A 12-sided dice”
``` ```
同样,`SnakesAndLadders` 类也可以通过扩展遵循并符合 `TextRepresentable` 协议: 同样,`SnakesAndLadders` 类也可以通过扩展遵循 `TextRepresentable` 协议:
```swift ```swift
extension SnakesAndLadders: TextRepresentable { extension SnakesAndLadders: TextRepresentable {
@ -462,7 +462,7 @@ print(game.textualDescription)
<a name="Conditionally_Conforming_to_a_Protocol"></a> <a name="Conditionally_Conforming_to_a_Protocol"></a>
## 有条件地遵循协议 ## 有条件地遵循协议
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见[泛型 Where 分句](./22_Generics.html##where_clauses)。 泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你遵循协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见[泛型 Where 分句](./22_Generics.html##where_clauses)。
下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。 下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。
@ -479,9 +479,9 @@ print(myDice.textualDescription)
``` ```
<a name="declaring_protocol_adoption_with_an_extension"></a> <a name="declaring_protocol_adoption_with_an_extension"></a>
## 在扩展里声明采纳协议 ## 在扩展里声明遵循协议
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展采纳该协议: 当一个类型已经遵循了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展遵循该协议:
```swift ```swift
struct Hamster { struct Hamster {
@ -549,7 +549,7 @@ protocol PrettyTextRepresentable: TextRepresentable {
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。 例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
如下所示,扩展 `SnakesAndLadders`,使其遵循并符合 `PrettyTextRepresentable` 协议: 如下所示,扩展 `SnakesAndLadders`,使其遵循 `PrettyTextRepresentable` 协议:
```swift ```swift
extension SnakesAndLadders: PrettyTextRepresentable { extension SnakesAndLadders: PrettyTextRepresentable {
@ -587,7 +587,7 @@ print(game.prettyTextualDescription)
<a name="class_only_protocol"></a> <a name="class_only_protocol"></a>
## 类专属的协议 ## 类专属的协议
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。 你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型遵循(以及非结构体或者非枚举的类型)。
```swift ```swift
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
@ -595,7 +595,7 @@ protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
} }
``` ```
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳 `SomeClassOnlyProtocol`,则会导致编译时错误。 在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型遵循。如果尝试让结构体或枚举类型遵循 `SomeClassOnlyProtocol` 协议,则会导致编译时错误。
> 注意 > 注意
> >
@ -629,11 +629,11 @@ wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!” // 打印 “Happy birthday Malcolm - you're 21!”
``` ```
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。 `Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体遵循了这两个协议。
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged` 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数符合这两个协议即可。 `wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged` 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数遵循这两个协议即可。
上面的例子创建了一个名为 `birthdayPerson``Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。 上面的例子创建了一个名为 `birthdayPerson``Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时遵循这两个协议,所以这个参数合法,函数将打印生日问候语。
这里有一个例子:将 Location 类和前面的 Named 协议进行组合: 这里有一个例子:将 Location 类和前面的 Named 协议进行组合:
@ -667,12 +667,12 @@ beginConcert(in: seattle)
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是一个 Location 的子类。就像,如果你新建一个类继承与 Location但是没有遵循 Named 协议,你用这个类的实例去调用 `beginConcert(in:)` 函数也是不合法的。 将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是一个 Location 的子类。就像,如果你新建一个类继承与 Location但是没有遵循 Named 协议,你用这个类的实例去调用 `beginConcert(in:)` 函数也是不合法的。
<a name="checking_for_protocol_conformance"></a> <a name="checking_for_protocol_conformance"></a>
## 检查协议一致性 ## 检查协议遵循
你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is``as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同: 你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is``as` 操作符来检查协议遵循,即是否遵循了某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false` * `is` 用来检查实例是否遵循某个协议,若遵循则返回 `true`,否则返回 `false`
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil` * `as?` 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 `nil`
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。 * `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area` 下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`
@ -698,7 +698,7 @@ class Country: HasArea {
} }
``` ```
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地符合`HasArea` 协议。 `Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地遵循`HasArea` 协议。
如下所示,`Animal` 是一个未遵循 `HasArea` 协议的类: 如下所示,`Animal` 是一个未遵循 `HasArea` 协议的类:
@ -721,7 +721,7 @@ let objects: [AnyObject] = [
`objects` 数组使用字面量初始化,数组包含一个 `radius``2``Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs``4``Animal` 实例。 `objects` 数组使用字面量初始化,数组包含一个 `radius``2``Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs``4``Animal` 实例。
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议: 如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否遵循 `HasArea` 协议:
```swift ```swift
for object in objects { for object in objects {
@ -736,7 +736,7 @@ for object in objects {
// Something that doesn't have an area // Something that doesn't have an area
``` ```
当迭代出的元素符合 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea``HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。 当迭代出的元素遵循 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea``HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle``Country``Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。 `objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle``Country``Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
@ -900,7 +900,7 @@ extension PrettyTextRepresentable {
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.html#where_clauses)中所描述的。 在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.html#where_clauses)中所描述的。
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==``!=` 操作符来检查两个元素的等价性和非等价性。 例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==``!=` 操作符来检查两个元素的等价性和非等价性。
```swift ```swift
extension Collection where Element: Equatable { extension Collection where Element: Equatable {

View File

@ -235,11 +235,11 @@ if let topItem = stackOfStrings.topItem {
<a name="type_constraints"></a> <a name="type_constraints"></a>
## 类型约束 ## 类型约束
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。 `swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者遵循一个特定的协议或协议组合。
例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。 例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须符合 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。 为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须遵循 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。 当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
@ -254,7 +254,7 @@ func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
} }
``` ```
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须符合 `SomeProtocol` 协议的类型约束。 上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须遵循 `SomeProtocol` 协议的类型约束。
<a name="type_constraints_in_action"></a> <a name="type_constraints_in_action"></a>
### 类型约束实践 ### 类型约束实践
@ -314,9 +314,9 @@ func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
} }
``` ```
`findIndex(of:in:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T`”。 `findIndex(of:in:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何遵循 `Equatable` 协议的类型 `T`”。
`findIndex(of:in:)` 函数现在可以成功编译了,并且可以作用于任何符合 `Equatable` 的类型,如 `Double``String` `findIndex(of:in:)` 函数现在可以成功编译了,并且可以作用于任何遵循 `Equatable` 协议的类型,如 `Double``String`
```swift ```swift
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25]) let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
@ -328,7 +328,7 @@ let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
<a name="associated_types"></a> <a name="associated_types"></a>
## 关联类型 ## 关联类型
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。 定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被遵循时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
<a name="associated_types_in_action"></a> <a name="associated_types_in_action"></a>
### 关联类型实践 ### 关联类型实践
@ -344,21 +344,21 @@ protocol Container {
} }
``` ```
`Container` 协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能: `Container` 协议定义了三个任何遵循了该协议的类型(即容器)必须提供的功能:
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。 - 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。 - 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。 - 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵 `Container` 协议的类型必须提供的功能。遵协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。 这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵 `Container` 协议的类型必须提供的功能。遵协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
任何遵 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。 任何遵 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。 为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
为了达到这个目的,`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。这个协议无法定义 `Item` 是什么类型的别名,这个信息将留给遵协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。 为了达到这个目的,`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。这个协议无法定义 `Item` 是什么类型的别名,这个信息将留给遵协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。
下面是先前的非泛型的 `IntStack` 类型,这一版本采纳并符合`Container` 协议: 下面是先前的非泛型的 `IntStack` 类型,这一版本遵循`Container` 协议:
```swift ```swift
struct IntStack: Container { struct IntStack: Container {
@ -388,9 +388,9 @@ struct IntStack: Container {
此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item``Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。 此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item``Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。
由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `Item``Int`。因为 `IntStack` 符合 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。 由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `Item``Int`。因为 `IntStack` 遵循 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
你也可以让泛型 `Stack` 结构体遵 `Container` 协议: 你也可以让泛型 `Stack` 结构体遵 `Container` 协议:
```swift ```swift
struct Stack<Element>: Container { struct Stack<Element>: Container {
@ -420,9 +420,9 @@ struct Stack<Element>: Container {
<a name="extending_an_existing_type_to_specify_an_associated_type"></a> <a name="extending_an_existing_type_to_specify_an_associated_type"></a>
### 通过扩展一个存在的类型来指定关联类型 ### 通过扩展一个存在的类型来指定关联类型
[通过扩展添加协议一致性](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。 [通过扩展添加协议遵循](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型遵循一个协议,这包括使用了关联类型的协议。
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 采纳该协议就可以扩展 `Array`,使其遵 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展采纳协议](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述: Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都遵循 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 遵循该协议就可以扩展 `Array`,使其遵 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展遵循协议](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
```swift ```swift
extension Array: Container {} extension Array: Container {}
@ -433,7 +433,7 @@ extension Array: Container {}
<a name="using_type_annotations_to_constrain_an_associated_type"></a> <a name="using_type_annotations_to_constrain_an_associated_type"></a>
### 给关联类型添加约束 ### 给关联类型添加约束
你可以给协议里的关联类型添加类型注释,让遵协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable``Container` 类型: 你可以给协议里的关联类型添加类型注释,让遵协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable``Container` 类型:
```swift ```swift
protocol Container { protocol Container {
@ -444,7 +444,7 @@ protocol Container {
} }
``` ```
为了遵守了 `Container` 协议Item 类型也必须遵 `Equatable` 协议。 为了遵 `Container` 协议Item 类型也必须遵 `Equatable` 协议。
<a name="Using_a_Protocol_in_Its_Associated_Types_Constraints"></a> <a name="Using_a_Protocol_in_Its_Associated_Types_Constraints"></a>
### 在关联类型约束里使用协议 ### 在关联类型约束里使用协议
@ -502,7 +502,7 @@ extension IntStack: SuffixableContainer {
[类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。 [类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。 为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false` 下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`
@ -534,10 +534,10 @@ func allItemsMatch<C1: Container, C2: Container>
这个函数的类型参数列表还定义了对两个类型参数的要求: 这个函数的类型参数列表还定义了对两个类型参数的要求:
- `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。 - `C1` 必须遵循 `Container` 协议(写作 `C1: Container`)。
- `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。 - `C2` 必须遵循 `Container` 协议(写作 `C2: Container`)。
- `C1``Item` 必须和 `C2``Item` 类型相同(写作 `C1.Item == C2.Item`)。 - `C1``Item` 必须和 `C2``Item` 类型相同(写作 `C1.Item == C2.Item`)。
- `C1``Item` 必须符合 `Equatable` 协议(写作 `C1.Item: Equatable`)。 - `C1``Item` 必须遵循 `Equatable` 协议(写作 `C1.Item: Equatable`)。
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。 第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
@ -576,7 +576,7 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
// 打印 “All items match.” // 打印 “All items match.”
``` ```
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。 上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
<a name="extensions_with_a_generic_where_clause"></a> <a name="extensions_with_a_generic_where_clause"></a>
## 具有泛型 Where 子句的扩展 ## 具有泛型 Where 子句的扩展
@ -594,7 +594,7 @@ extension Stack where Element: Equatable {
} }
``` ```
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是符合 Equatable 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 Equatable 协议时,扩展才会添加 `isTop(_:)` 方法。 这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是遵循 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素遵循 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
以下是 `isTop(_:)` 方法的调用方式: 以下是 `isTop(_:)` 方法的调用方式:
@ -607,7 +607,7 @@ if stackOfStrings.isTop("tres") {
// 打印 "Top element is tres." // 打印 "Top element is tres."
``` ```
如果尝试在其元素不符合 Equatable 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。 如果尝试在其元素未遵循 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
```swift ```swift
struct NotEquatable { } struct NotEquatable { }
@ -627,7 +627,7 @@ extension Container where Item: Equatable {
} }
``` ```
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 Equatable 协议的。 这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何遵循 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是遵循 `Equatable` 协议的。
```swift ```swift
if [9, 9, 9].startsWith(42) { if [9, 9, 9].startsWith(42) {
@ -638,7 +638,7 @@ if [9, 9, 9].startsWith(42) {
// 打印 "Starts with something else." // 打印 "Starts with something else."
``` ```
上述示例中的泛型 `where` 子句要求 `Item` 符合协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如: 上述示例中的泛型 `where` 子句要求 `Item` 遵循协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
```swift ```swift
extension Container where Item == Double { extension Container where Item == Double {
@ -705,7 +705,7 @@ extension Container {
这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下: 这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:
- 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。 - 在尖括号中的泛型参数 `Indices`,必须是遵循标准库中的 `Sequence` 协议的类型。
- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。 - 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
- 泛型 `where` 子句要求 SequenceIndices的迭代器其所有的元素都是 `Int` 类型。这样就能确保在序列Sequence中的索引和容器Container里面的索引类型是一致的。 - 泛型 `where` 子句要求 SequenceIndices的迭代器其所有的元素都是 `Int` 类型。这样就能确保在序列Sequence中的索引和容器Container里面的索引类型是一致的。

View File

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