From 3acf9a1bc477a89c5b17e555dfcd8774b154c245 Mon Sep 17 00:00:00 2001 From: rsenjoyer Date: Sat, 5 Jan 2019 00:41:18 +0800 Subject: [PATCH] 22-Generics (#849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 19 Nested types for Swift 4.2 * Revert "Update 19 Nested types for Swift 4.2" This reverts commit 7e0fbe87ed370b36bd5134e19241e083e5ef8f4c. * Update 16_Optional_Chaining * Update 16_Optional_Chaining * Update 16_Optional_Chaining * 新增部分翻译 * 增加翻译内容 * 完成初步翻译 * 修改部分内容 * 更新翻译 * 删除标题以及原文链接 * 修正相关的链接 --- source/chapter2/16_Optional_Chaining.md | 766 ++++++++++++------------ source/chapter2/22_Generics.md | 303 +++++----- 2 files changed, 545 insertions(+), 524 deletions(-) diff --git a/source/chapter2/16_Optional_Chaining.md b/source/chapter2/16_Optional_Chaining.md index e9e4945c..33535a1d 100755 --- a/source/chapter2/16_Optional_Chaining.md +++ b/source/chapter2/16_Optional_Chaining.md @@ -1,383 +1,383 @@ -# 可选链式调用 - -*可选链式调用*是一种可以在当前值可能为 `nil` 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 `nil`,那么调用将返回 `nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为 `nil`,整个调用链都会失败,即返回 `nil`。 - -> 注意 -> -> Swift 的可选链式调用和 Objective-C 中向 `nil` 发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。 - - -## 使用可选链式调用代替强制展开 - -通过在想调用的属性、方法,或下标的可选值后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。 - -为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。 - -这里需要特别指出,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。 - -下面几段代码将解释可选链式调用和强制展开的不同。 - -首先定义两个类 `Person` 和 `Residence`: - -```swift -class Person { - var residence: Residence? -} - -class Residence { - var numberOfRooms = 1 -} -``` - -`Residence` 有一个 `Int` 类型的属性 `numberOfRooms`,其默认值为 `1`。`Person` 具有一个可选的 `residence` 属性,其类型为 `Residence?`。 - -假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是可选类型而将被初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil` 的 `residence` 属性: - -```swift -let john = Person() -``` - -如果使用叹号(`!`)强制展开获得这个 `john` 的 `residence` 属性中的 `numberOfRooms` 值,会触发运行时错误,因为这时 `residence` 没有可以展开的值: - -```swift -let roomCount = john.residence!.numberOfRooms -// 这会引发运行时错误 -``` - -`john.residence` 为非 `nil` 值的时候,上面的调用会成功,并且把 `roomCount` 设置为 `Int` 类型的房间数量。正如上面提到的,当 `residence` 为 `nil` 的时候,上面这段代码会触发运行时错误。 - -可选链式调用提供了另一种访问 `numberOfRooms` 的方式,使用问号(`?`)来替代原来的叹号(`!`): - -```swift -if let roomCount = john.residence?.numberOfRooms { - print("John's residence has \(roomCount) room(s).") -} else { - print("Unable to retrieve the number of rooms.") -} -// 打印 “Unable to retrieve the number of rooms.” -``` - -在 `residence` 后面添加问号之后,Swift 就会在 `residence` 不为 `nil` 的情况下访问 `numberOfRooms`。 - -因为访问 `numberOfRooms` 有可能失败,可选链式调用会返回 `Int?` 类型,或称为“可选的 `Int`”。如上例所示,当 `residence` 为 `nil` 的时候,可选的 `Int` 将会为 `nil`,表明无法访问 `numberOfRooms`。访问成功时,可选的 `Int` 值会通过可选绑定展开,并赋值给非可选类型的 `roomCount` 常量。 - -要注意的是,即使 `numberOfRooms` 是非可选的 `Int` 时,这一点也成立。只要使用可选链式调用就意味着 `numberOfRooms` 会返回一个 `Int?` 而不是 `Int`。 - -可以将一个 `Residence` 的实例赋给 `john.residence`,这样它就不再是 `nil` 了: - -```swift -john.residence = Residence() -``` - -`john.residence` 现在包含一个实际的 `Residence` 实例,而不再是 `nil`。如果你试图使用先前的可选链式调用访问 `numberOfRooms`,它现在将返回值为 `1` 的 `Int?` 类型的值: - -```swift -if let roomCount = john.residence?.numberOfRooms { - print("John's residence has \(roomCount) room(s).") -} else { - print("Unable to retrieve the number of rooms.") -} -// 打印 “John's residence has 1 room(s).” -``` - - -## 为可选链式调用定义模型类 - -通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法和下标。 - -下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在 `Person` 和 `Residence` 的基础上增加了 `Room` 类和 `Address` 类,以及相关的属性、方法以及下标。 - -`Person` 类的定义基本保持不变: - -```swift -class Person { - var residence: Residence? -} -``` - -`Residence` 类比之前复杂些,增加了一个名为 `rooms` 的变量属性,该属性被初始化为 `[Room]` 类型的空数组: - -```swift -class Residence { - var rooms = [Room]() - var numberOfRooms: Int { - return rooms.count - } - subscript(i: Int) -> Room { - get { - return rooms[i] - } - set { - rooms[i] = newValue - } - } - func printNumberOfRooms() { - print("The number of rooms is \(numberOfRooms)") - } - var address: Address? -} -``` - -现在 `Residence` 有了一个存储 `Room` 实例的数组,`numberOfRooms` 属性被实现为计算型属性,而不是存储型属性。`numberOfRooms` 属性简单地返回 `rooms` 数组的 `count` 属性的值。 - -`Residence` 还提供了访问 `rooms` 数组的快捷方式,即提供可读写的下标来访问 `rooms` 数组中指定位置的元素。 - -此外,`Residence` 还提供了 `printNumberOfRooms` 方法,这个方法的作用是打印 `numberOfRooms` 的值。 - -最后,`Residence` 还定义了一个可选属性 `address`,其类型为 `Address?`。`Address` 类的定义在下面会说明。 - -`Room` 类是一个简单类,其实例被存储在 `rooms` 数组中。该类只包含一个属性 `name`,以及一个用于将该属性设置为适当的房间名的初始化函数: - -```swift -class Room { - let name: String - init(name: String) { self.name = name } -} -``` - -最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称: - -```swift -class Address { - var buildingName: String? - var buildingNumber: String? - var street: String? - func buildingIdentifier() -> String? { - if buildingName != nil { - return buildingName - } else if let buildingNumber = buildingNumber, let street = street { - return "\(buildingNumber) \(street)" - } else { - return nil - } - } -} -``` - -`Address` 类提供了 `buildingIdentifier()` 方法,返回值为 `String?`。 如果 `buildingName` 有值则返回 `buildingName`。或者,如果 `buildingNumber` 和 `street` 均有值,则返回两者拼接得到的字符串。否则,返回 `nil`。 - - -## 通过可选链式调用访问属性 - -正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。 - -使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性: - -```swift -let john = Person() -if let roomCount = john.residence?.numberOfRooms { - print("John's residence has \(roomCount) room(s).") -} else { - print("Unable to retrieve the number of rooms.") -} -// 打印 “Unable to retrieve the number of rooms.” -``` - -因为 `john.residence` 为 `nil`,所以这个可选链式调用依旧会像先前一样失败。 - -还可以通过可选链式调用来设置属性值: - -```swift -let someAddress = Address() -someAddress.buildingNumber = "29" -someAddress.street = "Acacia Road" -john.residence?.address = someAddress -``` - -在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`。 - -上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印 “Function was called”,这使你能验证等号右侧的代码是否被执行。 - -```swift -func createAddress() -> Address { - print("Function was called.") - - let someAddress = Address() - someAddress.buildingNumber = "29" - someAddress.street = "Acacia Road" - - return someAddress -} -john.residence?.address = createAddress() -``` - -没有任何打印消息,可以看出 `createAddress()` 函数并未被执行。 - - -## 通过可选链式调用来调用方法 - -可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。 - -`Residence` 类中的 `printNumberOfRooms()` 方法打印当前的 `numberOfRooms` 值,如下所示: - -```swift -func printNumberOfRooms() { - print("The number of rooms is \(numberOfRooms)") -} -``` - -这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。 - -如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 `Void?`,而不是 `Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 `if` 语句来判断能否成功调用 `printNumberOfRooms()` 方法,即使方法本身没有定义返回值。通过判断返回值是否为 `nil` 可以判断调用是否成功: - -```swift -if john.residence?.printNumberOfRooms() != nil { - print("It was possible to print the number of rooms.") -} else { - print("It was not possible to print the number of rooms.") -} -// 打印 “It was not possible to print the number of rooms.” -``` - -同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence` 为 `nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功: - -```swift -if (john.residence?.address = someAddress) != nil { - print("It was possible to set the address.") -} else { - print("It was not possible to set the address.") -} -// 打印 “It was not possible to set the address.” -``` - - -## 通过可选链式调用访问下标 - -通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。 - -> 注意 -> -> 通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。 - -下面这个例子用下标访问 `john.residence` 属性存储的 `Residence` 实例的 `rooms` 数组中的第一个房间的名称,因为 `john.residence` 为 `nil`,所以下标调用失败了: - -```swift -if let firstRoomName = john.residence?[0].name { - print("The first room name is \(firstRoomName).") -} else { - print("Unable to retrieve the first room name.") -} -// 打印 “Unable to retrieve the first room name.” -``` - -在这个例子中,问号直接放在 `john.residence` 的后面,并且在方括号的前面,因为 `john.residence` 是可选值。 - -类似的,可以通过下标,用可选链式调用来赋值: - -```swift -john.residence?[0] = Room(name: "Bathroom") -``` - -这次赋值同样会失败,因为 `residence` 目前是 `nil`。 - -如果你创建一个 `Residence` 实例,并为其 `rooms` 数组添加一些 `Room` 实例,然后将 `Residence` 实例赋值给 `john.residence`,那就可以通过可选链和下标来访问数组中的元素: - -```swift -let johnsHouse = Residence() -johnsHouse.rooms.append(Room(name: "Living Room")) -johnsHouse.rooms.append(Room(name: "Kitchen")) -john.residence = johnsHouse - -if let firstRoomName = john.residence?[0].name { - print("The first room name is \(firstRoomName).") -} else { - print("Unable to retrieve the first room name.") -} -// 打印 “The first room name is Living Room.” -``` - - -### 访问可选类型的下标 - -如果下标返回可选类型值,比如 Swift 中 `Dictionary` 类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用: - -```swift -var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] -testScores["Dave"]?[0] = 91 -testScores["Bev"]?[0] += 1 -testScores["Brian"]?[0] = 72 -// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81] -``` - -上面的例子中定义了一个 `testScores` 数组,包含了两个键值对,分别把 `String` 类型的键映射到一个 `Int` 值的数组。这个例子用可选链式调用把 `"Dave"` 数组中第一个元素设为 `91`,把 `"Bev"` 数组的第一个元素 `+1`,然后尝试把 `"Brian"` 数组中的第一个元素设为 `72`。前两个调用成功,因为 `testScores` 字典中包含 `"Dave"` 和 `"Bev"` 这两个键。但是 `testScores` 字典中没有 `"Brian"` 这个键,所以第三个调用失败。 - - -## 连接多层可选链式调用 - -可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。 - -也就是说: - -+ 如果你访问的值不是可选的,可选链式调用将会返回可选值。 -+ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。 - -因此: - -+ 通过可选链式调用访问一个 `Int` 值,将会返回 `Int?`,无论使用了多少层可选链式调用。 -+ 类似的,通过可选链式调用访问 `Int?` 值,依旧会返回 `Int?` 值,并不会返回 `Int??`。 - -下面的例子尝试访问 `john` 中的 `residence` 属性中的 `address` 属性中的 `street` 属性。这里使用了两层可选链式调用,`residence` 以及 `address` 都是可选值: - -```swift -if let johnsStreet = john.residence?.address?.street { - print("John's street name is \(johnsStreet).") -} else { - print("Unable to retrieve the address.") -} -// 打印 “Unable to retrieve the address.” -``` - -`john.residence` 现在包含一个有效的 `Residence` 实例。然而,`john.residence.address` 的值当前为 `nil`。因此,调用 `john.residence?.address?.street` 会失败。 - -需要注意的是,上面的例子中,`street` 的属性为 `String?`。`john.residence?.address?.street` 的返回值也依然是 `String?`,即使已经使用了两层可选链式调用。 - -如果为 `john.residence.address` 赋值一个 `Address` 实例,并且为 `address` 中的 `street` 属性设置一个有效值,我们就能过通过可选链式调用来访问 `street` 属性: - -```swift -let johnsAddress = Address() -johnsAddress.buildingName = "The Larches" -johnsAddress.street = "Laurel Street" -john.residence?.address = johnsAddress - -if let johnsStreet = john.residence?.address?.street { - print("John's street name is \(johnsStreet).") -} else { - print("Unable to retrieve the address.") -} -// 打印 “John's street name is Laurel Street.” -``` - -在上面的例子中,因为 `john.residence` 包含一个有效的 `Address` 实例,所以对 `john.residence` 的 `address` 属性赋值将会成功。 - - -## 在方法的可选返回值上进行可选链式调用 - -上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。 - -在下面的例子中,通过可选链式调用来调用 `Address` 的 `buildingIdentifier()` 方法。这个方法返回 `String?` 类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是 `String?` 类型: - -```swift -if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { - print("John's building identifier is \(buildingIdentifier).") -} -// 打印 “John's building identifier is The Larches.” -``` - -如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可: - -```swift -if let beginsWithThe = - - john.residence?.address?.buildingIdentifier()?.hasPrefix("The") { - if beginsWithThe { - print("John's building identifier begins with \"The\".") - } else { - print("John's building identifier does not begin with \"The\".") - } -} -// 打印 “John's building identifier begins with "The".” -``` - -> 注意 -> -> 在上面的例子中,在方法的圆括号后面加上问号是因为你要在 `buildingIdentifier()` 方法的可选返回值上进行可选链式调用,而不是 `buildingIdentifier()` 方法本身。 +# 可选链式调用 + +*可选链式调用*是一种可以在当前值可能为 `nil` 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 `nil`,那么调用将返回 `nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为 `nil`,整个调用链都会失败,即返回 `nil`。 + +> 注意 +> +> Swift 的可选链式调用和 Objective-C 中向 `nil` 发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。 + + +## 使用可选链式调用代替强制展开 + +通过在想调用的属性、方法,或下标的可选值后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。 + +为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。 + +这里需要特别指出,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。 + +下面几段代码将解释可选链式调用和强制展开的不同。 + +首先定义两个类 `Person` 和 `Residence`: + +```swift +class Person { + var residence: Residence? +} + +class Residence { + var numberOfRooms = 1 +} +``` + +`Residence` 有一个 `Int` 类型的属性 `numberOfRooms`,其默认值为 `1`。`Person` 具有一个可选的 `residence` 属性,其类型为 `Residence?`。 + +假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是可选类型而将被初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil` 的 `residence` 属性: + +```swift +let john = Person() +``` + +如果使用叹号(`!`)强制展开获得这个 `john` 的 `residence` 属性中的 `numberOfRooms` 值,会触发运行时错误,因为这时 `residence` 没有可以展开的值: + +```swift +let roomCount = john.residence!.numberOfRooms +// 这会引发运行时错误 +``` + +`john.residence` 为非 `nil` 值的时候,上面的调用会成功,并且把 `roomCount` 设置为 `Int` 类型的房间数量。正如上面提到的,当 `residence` 为 `nil` 的时候,上面这段代码会触发运行时错误。 + +可选链式调用提供了另一种访问 `numberOfRooms` 的方式,使用问号(`?`)来替代原来的叹号(`!`): + +```swift +if let roomCount = john.residence?.numberOfRooms { + print("John's residence has \(roomCount) room(s).") +} else { + print("Unable to retrieve the number of rooms.") +} +// 打印 “Unable to retrieve the number of rooms.” +``` + +在 `residence` 后面添加问号之后,Swift 就会在 `residence` 不为 `nil` 的情况下访问 `numberOfRooms`。 + +因为访问 `numberOfRooms` 有可能失败,可选链式调用会返回 `Int?` 类型,或称为“可选的 `Int`”。如上例所示,当 `residence` 为 `nil` 的时候,可选的 `Int` 将会为 `nil`,表明无法访问 `numberOfRooms`。访问成功时,可选的 `Int` 值会通过可选绑定展开,并赋值给非可选类型的 `roomCount` 常量。 + +要注意的是,即使 `numberOfRooms` 是非可选的 `Int` 时,这一点也成立。只要使用可选链式调用就意味着 `numberOfRooms` 会返回一个 `Int?` 而不是 `Int`。 + +可以将一个 `Residence` 的实例赋给 `john.residence`,这样它就不再是 `nil` 了: + +```swift +john.residence = Residence() +``` + +`john.residence` 现在包含一个实际的 `Residence` 实例,而不再是 `nil`。如果你试图使用先前的可选链式调用访问 `numberOfRooms`,它现在将返回值为 `1` 的 `Int?` 类型的值: + +```swift +if let roomCount = john.residence?.numberOfRooms { + print("John's residence has \(roomCount) room(s).") +} else { + print("Unable to retrieve the number of rooms.") +} +// 打印 “John's residence has 1 room(s).” +``` + + +## 为可选链式调用定义模型类 + +通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法和下标。 + +下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在 `Person` 和 `Residence` 的基础上增加了 `Room` 类和 `Address` 类,以及相关的属性、方法以及下标。 + +`Person` 类的定义基本保持不变: + +```swift +class Person { + var residence: Residence? +} +``` + +`Residence` 类比之前复杂些,增加了一个名为 `rooms` 的变量属性,该属性被初始化为 `[Room]` 类型的空数组: + +```swift +class Residence { + var rooms = [Room]() + var numberOfRooms: Int { + return rooms.count + } + subscript(i: Int) -> Room { + get { + return rooms[i] + } + set { + rooms[i] = newValue + } + } + func printNumberOfRooms() { + print("The number of rooms is \(numberOfRooms)") + } + var address: Address? +} +``` + +现在 `Residence` 有了一个存储 `Room` 实例的数组,`numberOfRooms` 属性被实现为计算型属性,而不是存储型属性。`numberOfRooms` 属性简单地返回 `rooms` 数组的 `count` 属性的值。 + +`Residence` 还提供了访问 `rooms` 数组的快捷方式,即提供可读写的下标来访问 `rooms` 数组中指定位置的元素。 + +此外,`Residence` 还提供了 `printNumberOfRooms` 方法,这个方法的作用是打印 `numberOfRooms` 的值。 + +最后,`Residence` 还定义了一个可选属性 `address`,其类型为 `Address?`。`Address` 类的定义在下面会说明。 + +`Room` 类是一个简单类,其实例被存储在 `rooms` 数组中。该类只包含一个属性 `name`,以及一个用于将该属性设置为适当的房间名的初始化函数: + +```swift +class Room { + let name: String + init(name: String) { self.name = name } +} +``` + +最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称: + +```swift +class Address { + var buildingName: String? + var buildingNumber: String? + var street: String? + func buildingIdentifier() -> String? { + if buildingName != nil { + return buildingName + } else if let buildingNumber = buildingNumber, let street = street { + return "\(buildingNumber) \(street)" + } else { + return nil + } + } +} +``` + +`Address` 类提供了 `buildingIdentifier()` 方法,返回值为 `String?`。 如果 `buildingName` 有值则返回 `buildingName`。或者,如果 `buildingNumber` 和 `street` 均有值,则返回两者拼接得到的字符串。否则,返回 `nil`。 + + +## 通过可选链式调用访问属性 + +正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。 + +使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性: + +```swift +let john = Person() +if let roomCount = john.residence?.numberOfRooms { + print("John's residence has \(roomCount) room(s).") +} else { + print("Unable to retrieve the number of rooms.") +} +// 打印 “Unable to retrieve the number of rooms.” +``` + +因为 `john.residence` 为 `nil`,所以这个可选链式调用依旧会像先前一样失败。 + +还可以通过可选链式调用来设置属性值: + +```swift +let someAddress = Address() +someAddress.buildingNumber = "29" +someAddress.street = "Acacia Road" +john.residence?.address = someAddress +``` + +在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`。 + +上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印 “Function was called”,这使你能验证等号右侧的代码是否被执行。 + +```swift +func createAddress() -> Address { + print("Function was called.") + + let someAddress = Address() + someAddress.buildingNumber = "29" + someAddress.street = "Acacia Road" + + return someAddress +} +john.residence?.address = createAddress() +``` + +没有任何打印消息,可以看出 `createAddress()` 函数并未被执行。 + + +## 通过可选链式调用来调用方法 + +可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。 + +`Residence` 类中的 `printNumberOfRooms()` 方法打印当前的 `numberOfRooms` 值,如下所示: + +```swift +func printNumberOfRooms() { + print("The number of rooms is \(numberOfRooms)") +} +``` + +这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。 + +如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 `Void?`,而不是 `Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 `if` 语句来判断能否成功调用 `printNumberOfRooms()` 方法,即使方法本身没有定义返回值。通过判断返回值是否为 `nil` 可以判断调用是否成功: + +```swift +if john.residence?.printNumberOfRooms() != nil { + print("It was possible to print the number of rooms.") +} else { + print("It was not possible to print the number of rooms.") +} +// 打印 “It was not possible to print the number of rooms.” +``` + +同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence` 为 `nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功: + +```swift +if (john.residence?.address = someAddress) != nil { + print("It was possible to set the address.") +} else { + print("It was not possible to set the address.") +} +// 打印 “It was not possible to set the address.” +``` + + +## 通过可选链式调用访问下标 + +通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。 + +> 注意 +> +> 通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。 + +下面这个例子用下标访问 `john.residence` 属性存储的 `Residence` 实例的 `rooms` 数组中的第一个房间的名称,因为 `john.residence` 为 `nil`,所以下标调用失败了: + +```swift +if let firstRoomName = john.residence?[0].name { + print("The first room name is \(firstRoomName).") +} else { + print("Unable to retrieve the first room name.") +} +// 打印 “Unable to retrieve the first room name.” +``` + +在这个例子中,问号直接放在 `john.residence` 的后面,并且在方括号的前面,因为 `john.residence` 是可选值。 + +类似的,可以通过下标,用可选链式调用来赋值: + +```swift +john.residence?[0] = Room(name: "Bathroom") +``` + +这次赋值同样会失败,因为 `residence` 目前是 `nil`。 + +如果你创建一个 `Residence` 实例,并为其 `rooms` 数组添加一些 `Room` 实例,然后将 `Residence` 实例赋值给 `john.residence`,那就可以通过可选链和下标来访问数组中的元素: + +```swift +let johnsHouse = Residence() +johnsHouse.rooms.append(Room(name: "Living Room")) +johnsHouse.rooms.append(Room(name: "Kitchen")) +john.residence = johnsHouse + +if let firstRoomName = john.residence?[0].name { + print("The first room name is \(firstRoomName).") +} else { + print("Unable to retrieve the first room name.") +} +// 打印 “The first room name is Living Room.” +``` + + +### 访问可选类型的下标 + +如果下标返回可选类型值,比如 Swift 中 `Dictionary` 类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用: + +```swift +var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] +testScores["Dave"]?[0] = 91 +testScores["Bev"]?[0] += 1 +testScores["Brian"]?[0] = 72 +// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81] +``` + +上面的例子中定义了一个 `testScores` 数组,包含了两个键值对,分别把 `String` 类型的键映射到一个 `Int` 值的数组。这个例子用可选链式调用把 `"Dave"` 数组中第一个元素设为 `91`,把 `"Bev"` 数组的第一个元素 `+1`,然后尝试把 `"Brian"` 数组中的第一个元素设为 `72`。前两个调用成功,因为 `testScores` 字典中包含 `"Dave"` 和 `"Bev"` 这两个键。但是 `testScores` 字典中没有 `"Brian"` 这个键,所以第三个调用失败。 + + +## 连接多层可选链式调用 + +可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。 + +也就是说: + ++ 如果你访问的值不是可选的,可选链式调用将会返回可选值。 ++ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。 + +因此: + ++ 通过可选链式调用访问一个 `Int` 值,将会返回 `Int?`,无论使用了多少层可选链式调用。 ++ 类似的,通过可选链式调用访问 `Int?` 值,依旧会返回 `Int?` 值,并不会返回 `Int??`。 + +下面的例子尝试访问 `john` 中的 `residence` 属性中的 `address` 属性中的 `street` 属性。这里使用了两层可选链式调用,`residence` 以及 `address` 都是可选值: + +```swift +if let johnsStreet = john.residence?.address?.street { + print("John's street name is \(johnsStreet).") +} else { + print("Unable to retrieve the address.") +} +// 打印 “Unable to retrieve the address.” +``` + +`john.residence` 现在包含一个有效的 `Residence` 实例。然而,`john.residence.address` 的值当前为 `nil`。因此,调用 `john.residence?.address?.street` 会失败。 + +需要注意的是,上面的例子中,`street` 的属性为 `String?`。`john.residence?.address?.street` 的返回值也依然是 `String?`,即使已经使用了两层可选链式调用。 + +如果为 `john.residence.address` 赋值一个 `Address` 实例,并且为 `address` 中的 `street` 属性设置一个有效值,我们就能过通过可选链式调用来访问 `street` 属性: + +```swift +let johnsAddress = Address() +johnsAddress.buildingName = "The Larches" +johnsAddress.street = "Laurel Street" +john.residence?.address = johnsAddress + +if let johnsStreet = john.residence?.address?.street { + print("John's street name is \(johnsStreet).") +} else { + print("Unable to retrieve the address.") +} +// 打印 “John's street name is Laurel Street.” +``` + +在上面的例子中,因为 `john.residence` 包含一个有效的 `Address` 实例,所以对 `john.residence` 的 `address` 属性赋值将会成功。 + + +## 在方法的可选返回值上进行可选链式调用 + +上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。 + +在下面的例子中,通过可选链式调用来调用 `Address` 的 `buildingIdentifier()` 方法。这个方法返回 `String?` 类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是 `String?` 类型: + +```swift +if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { + print("John's building identifier is \(buildingIdentifier).") +} +// 打印 “John's building identifier is The Larches.” +``` + +如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可: + +```swift +if let beginsWithThe = + + john.residence?.address?.buildingIdentifier()?.hasPrefix("The") { + if beginsWithThe { + print("John's building identifier begins with \"The\".") + } else { + print("John's building identifier does not begin with \"The\".") + } +} +// 打印 “John's building identifier begins with "The".” +``` + +> 注意 +> +> 在上面的例子中,在方法的圆括号后面加上问号是因为你要在 `buildingIdentifier()` 方法的可选返回值上进行可选链式调用,而不是 `buildingIdentifier()` 方法本身。 diff --git a/source/chapter2/22_Generics.md b/source/chapter2/22_Generics.md index 526ec35c..a0133abf 100644 --- a/source/chapter2/22_Generics.md +++ b/source/chapter2/22_Generics.md @@ -1,11 +1,51 @@ # 泛型 +------------------- -*泛型代码*让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。 +> 1.0 +> 翻译:[takalard](https://github.com/takalard) +> 校对:[lifedim](https://github.com/lifedim) -泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言手册,只是你可能没有发现而已。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。 +> 2.0 +> 翻译+校对: [SergioChan](https://github.com/SergioChan) + +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-11-01 + +> 2.2:翻译+校对:[Lanford](https://github.com/LanfordCai),2016-04-08 [SketchK](https://github.com/SketchK) 2016-05-16 + +> 3.0:翻译+校对:[chenmingjia](https://github.com/chenmingjia),2016-09-12 +> 3.0.1,shanks,2016-11-13 + +> 3.1:翻译:[qhd](https://github.com/qhd),2017-04-10 + +> 4.0 +> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21 + +> 4.1 +> 翻译+校对:[mylittleswift](https://github.com/mylittleswift) + + +本页包含内容: + +- [泛型解决的问题](#the_problem_that_generics_solve) +- [泛型函数](#generic_functions) +- [类型参数](#type_parameters) +- [命名类型参数](#naming_type_parameters) +- [泛型类型](#generic_types) +- [泛型扩展](#extending_a_generic_type) +- [类型约束](#type_constraints) +- [关联类型](#associated_types) +- [泛型 Where 语句](#where_clauses) +- [具有泛型 where 子句的扩展](#extensions_with_a_generic_where_clause) +- [具有泛型 Where 子句的关联类型](#associated_types_with_a_generic_where_clause) +- [泛型下标](#generic_subscripts) + +*泛型代码*让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,用一种清晰抽象的方式来表达代码的意图。 + +泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在*语言指南*中使用泛型。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 类型数组,也可创建一个 `String` 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。 -## 泛型所解决的问题 +## 泛型解决的问题 下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值: @@ -16,20 +56,19 @@ func swapTwoInts(_ a: inout Int, _ b: inout Int) { b = temporaryA } ``` +这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,具体请参考[输入输出参数](./06_Functions.html#in_out_parameters) -这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。 - -`swapTwoInts(_:_:)` 函数交换 `b` 的原始值到 `a`,并交换 `a` 的原始值到 `b`。你可以调用这个函数交换两个 `Int` 变量的值: +`swapTwoInts(_:_:)` 函数将 `b` 的原始值换成了 `a`,将 `a` 的原始值换成了 `b`,你可以调用这个函数来交换两个 `Int` 类型变量: ```swift var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") -// 打印 “someInt is now 107, and anotherInt is now 3” -``` +// 打印 "someInt is now 107, and anotherInt is now 3" -诚然,`swapTwoInts(_:_:)` 函数挺有用,但是它只能交换 `Int` 值,如果你想要交换两个 `String` 值或者 `Double` 值,就不得不写更多的函数,例如 `swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)`,如下所示: +``` +`swapTwoInts(_:_:)` 函数很实用,但它只能作用于 `Int` 类型。如果你想交换两个 `String` 类型值,或者 `Double` 类型值,你必须编写对应的函数,类似下面 `swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 函数: ```swift func swapTwoStrings(_ a: inout String, _ b: inout String) { @@ -44,19 +83,17 @@ func swapTwoDoubles(_ a: inout Double, _ b: inout Double) { b = temporaryA } ``` - -你可能注意到 `swapTwoInts(_:_:)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int`、`String` 和 `Double`。 +你可能注意到了,`swapTwoInts(_:_:‘)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 函数体是一样的,唯一的区别是它们接受的参数类型(`Int`、`String` 和 `Double`)。 在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。) > 注意 -> > 在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。 ## 泛型函数 -泛型函数可以适用于任何类型,下面的 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本: +泛型函数可适用于任意类型,下面是函数 `swapTwoInts(_:_:)` 的泛型版本,命名为 `swapTwoValues(_:_:)`: ```swift func swapTwoValues(_ a: inout T, _ b: inout T) { @@ -65,21 +102,24 @@ func swapTwoValues(_ a: inout T, _ b: inout T) { b = temporaryA } ``` - -`swapTwoValues(_:_:)` 的函数主体和 `swapTwoInts(_:_:)` 函数是一样的,它们只在第一行有点不同,如下所示: +`swapTwoValues(_:_:)` 和 `swapTwoInts(_:_:)` 函数体内容相同,它们只在第一行不同,如下所示: ```swift func swapTwoInts(_ a: inout Int, _ b: inout Int) func swapTwoValues(_ a: inout T, _ b: inout T) ``` -这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int`、`String` 或 `Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a` 和 `b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才会根据所传入的实际类型决定 `T` 所代表的类型。 +----- -泛型函数和非泛型函数的另外一个不同之处,在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(``)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。 -`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来。 +泛型版本的函数使用`占位符`类型名(这里叫做 `T` ),而不是 *实际*类型名(例如 `Int`、`String` 或 `Double`),`占位符`类型名并不关心 `T` 具体的类型,但它要求 `a` 和` b` 必须是相同的类型,`T` 的实际类型由每次调用 `swapTwoValues(_:_:)` 来决定。 -在下面的两个例子中,`T` 分别代表 `Int` 和 `String`: + +泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(``)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T `的实际类型。 + +`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T ` 所代表的类型都会由传入的值的类型推断出来。 + +在下面的两个例子中,`T` 分别代表 ` Int` 和 `String`: ```swift var someInt = 3 @@ -91,52 +131,53 @@ var someString = "hello" var anotherString = "world" swapTwoValues(&someString, &anotherString) // someString 现在 "world", and anotherString 现在 "hello" + ``` > 注意 -> > 上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。 + ## 类型参数 -在上面的 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 ``)。 +上面 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是一个类型参数的例子,类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 ``)。 一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a` 和 `b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T` 被 `Int` 替换,第二次调用时,被 `String` 替换。) 你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。 + ## 命名类型参数 -在大多数情况下,类型参数具有一个描述性名字,例如 `Dictionary` 中的 `Key` 和 `Value`,以及 `Array` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 `T`、`U`、`V`,正如上面演示的 `swapTwoValues(_:_:)` 函数中的 `T` 一样。 +大多情况下,类型参数具有描述下的名称,例如字典 `Dictionary` 中的 `Key` 和 `Value` 及数组 `Array` 中的 `Element`,这能告诉阅读代码的人这些参数类型与泛型类型或函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字符来表示,例如 `T`、`U`、`V`,例如上面演示函数 `swapTwoValues(_:_:)` 中的 `T`。 -> 注意 -> +> 注意: > 请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。 + ## 泛型类型 -除了泛型函数,Swift 还允许你定义*泛型*类型。这些自定义类、结构体和枚举可以适用于*任何*类型,类似于 `Array` 和 `Dictionary`。 +除了泛型函数,Swift 还允许自定义*泛型类型*。这些自定义类、结构体和枚举可以适用于*任意类型*,类似于 `Array` 和 `Dictionary`。 -这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为*入*栈)。类似的,栈也只能从末端移除元素(称之为*出*栈)。 +本节将向你展示如何编写一个名为 `Stack`(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。 > 注意 -> -> 栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。 +> 栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的”后进先出”方式来管理集合,栈都是最实用的模型。 -下图展示了一个栈的入栈(push)和出栈(pop)的行为: +下图展示了入栈(push)和出栈(pop)的行为: -![此处输入图片的描述](https://docs.swift.org/swift-book/_images/stackPushPop_2x.png) +![](https://docs.swift.org/swift-book/_images/stackPushPop_2x.png) 1. 现在有三个值在栈中。 2. 第四个值被压入到栈的顶部。 -3. 现在有四个值在栈中,最近入栈的那个值在顶部。 +3. 现在栈中有四个值,最近入栈的那个值在顶部。 4. 栈中最顶部的那个值被移除出栈。 5. 一个值移除出栈后,现在栈又只有三个值了。 -下面展示了如何编写一个非泛型版本的栈,以 `Int` 型的栈为例: +下面展示如何编写一个非泛型版本的栈,以 `Int` 型的栈为例: ```swift struct IntStack { @@ -150,16 +191,16 @@ struct IntStack { } ``` -这个结构体在栈中使用一个名为 `items` 的 `Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)` 和 `pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。 +这个结构体在栈中使用一个名为 `items` 的数组属性来存储值。栈提供了两个方法:`push(_:)` 和 `pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。 -上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理*任意*类型的值。 +上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。 下面是相同代码的泛型版本: ```swift struct Stack { var items = [Element]() -    mutating func push(_ item: Element) { + mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { @@ -167,16 +208,15 @@ struct Stack { } } ``` - -注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(``)。 +注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(<`Element`>)。 `Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符: -- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。 -- 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。 -- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。 ++ 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。 ++ 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。 ++ 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。 -由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 `Array` 和 `Dictionary` 那样。 +由于 `Stack` 是泛型类型,因此可以用来创建适用于 Swift 中任意有效类型的栈,就像 `Array` 和 `Dictionary` 那样。 你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack()`: @@ -189,27 +229,27 @@ stackOfStrings.push("cuatro") // 栈中现在有 4 个字符串 ``` -下图展示了 `stackOfStrings` 如何将这四个值入栈: +下图展示了 `stackOfStrings` 如何将这四个值压栈: -![此处输入图片的描述](https://docs.swift.org/swift-book/_images/stackPushedFourStrings_2x.png) +![](https://docs.swift.org/swift-book/_images/stackPushedFourStrings_2x.png) -移除并返回栈顶部的值 `"cuatro"`,即将其出栈: +移除并返回栈顶部的值 "cuatro",即出栈: ```swift let fromTheTop = stackOfStrings.pop() // fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串 ``` +下图展示了如何将顶部的值出栈: -下图展示了 `stackOfStrings` 如何将顶部的值出栈: - -![此处输入图片的描述](https://docs.swift.org/swift-book/_images/stackPoppedOneString_2x.png) +![](https://docs.swift.org/swift-book/_images/stackPoppedOneString_2x.png) -## 扩展一个泛型类型 -当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。*原始*类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。 +## 泛型扩展 -下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除: +当对泛型类型进行扩展时,你并不需要提供类型参数列表作为定义的一部分。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。 + +下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶元素且不会将其从栈中移除: ```swift extension Stack { @@ -218,10 +258,9 @@ extension Stack { } } ``` +`topItem` 属性会返回 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。 -`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。 - -注意,这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。 +注意:这个扩展并没有定义类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。 计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它: @@ -233,33 +272,32 @@ if let topItem = stackOfStrings.topItem { ``` + ## 类型约束 -`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者遵循一个特定的协议或协议组合。 +`swapTwoValues(_:_:)` 函数和 `Stack` 适用于任意类型。不过,如果能对泛型函数或泛型类型中添加特定的*类型约束*,这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。 -例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。 +例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在 [字典的描述](./04_Collection_Types.html#dictionaries) 中,字典键的类型必须是可哈希(hashable)的。也就是说,必须有一种方法能够唯一地表示它。字典键之所以要是可哈希的,是为了便于检查字典中是否已经包含某个特定键的值。若没有这个要求,字典将无法判断是否可以插入或替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。 -为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须遵循 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。 +这个要求通过 `Dictionary` 键类型上的类型约束实现,它指明了键必须遵循 Swift 标准库中定义的 `Hashable` 协议。所有 Swift 的基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。 -当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。 +当自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。像 `可哈希(hashable)` 这种抽象概念根据它们的概念特征来描述类型,而不是它们的具体类型。 - ### 类型约束语法 -你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同): +在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束。下面将展示泛型函数约束的基本语法(与泛型类型的语法相同): ```swift func someFunction(someT: T, someU: U) { // 这里是泛型函数的函数体部分 } ``` - -上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须遵循 `SomeProtocol` 协议的类型约束。 +上面这个函数有两个类型参数。第一个类型参数 `T` 必须是 `SomeClass` 子类;第二个类型参数 `U` 必须符合 `SomeProtocol` 协议。 ### 类型约束实践 -这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,` findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`: +这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`: ```swift func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { @@ -271,8 +309,7 @@ func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { return nil } ``` - -`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串: +`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串值: ```swift let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] @@ -281,10 +318,9 @@ if let foundIndex = findIndex(ofString: "llama", in: strings) { } // 打印 “The index of llama is 2” ``` - 如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`。 -下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(ofString:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明: +下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(of:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因将在后面说明: ```swift func findIndex(of valueToFind: T, in array:[T]) -> Int? { @@ -297,11 +333,11 @@ func findIndex(of valueToFind: T, in array:[T]) -> Int? { } ``` -上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。比如说,如果你创建一个自定义的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。 +上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。例如,如果你自定义类或结构体来描述复杂的数据模型,对于这个类或结构体而言,Swift 无法明确知道“相等”意味着什么。正因如此,这部分代码无法保证适用于任意类型 `T`,当你试图编译这部分代码时就会出现相应的错误。 不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。 -任何 `Equatable` 类型都可以安全地使用在 `findIndex(of:in:)` 函数中,因为其保证支持等式操作符。为了说明这个事实,当你定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分: +遵循 `Equatable` 协议的类型都可以安全地用于 `findIndex(of:in:)` 函数,因为其保证支持等式操作符。为了说明这个事情,当定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分: ```swift func findIndex(of valueToFind: T, in array:[T]) -> Int? { @@ -313,10 +349,9 @@ func findIndex(of valueToFind: T, in array:[T]) -> Int? { return nil } ``` +`findIndex(of:in:)` 类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T`”。 -`findIndex(of:in:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何遵循 `Equatable` 协议的类型 `T`”。 - -`findIndex(of:in:)` 函数现在可以成功编译了,并且可以作用于任何遵循 `Equatable` 协议的类型,如 `Double` 或 `String`: +`findIndex(of:in:)` 函数现在可以成功编译了,并且适用于任何符合 `Equatable` 的类型,如 `Double` 或 `String`: ```swift let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25]) @@ -324,13 +359,12 @@ let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25]) let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"]) // stringIndex 类型为 Int?,其值为 2 ``` - + ## 关联类型 -定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被遵循时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。 +定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。关联类型通过 `associatedtype` 关键字来指定。 - ### 关联类型实践 下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `Item`: @@ -343,22 +377,21 @@ protocol Container { subscript(i: Int) -> Item { get } } ``` +`Container` 协议定义了三个任何遵循该协议的类型(即容器)必须提供的功能: -`Container` 协议定义了三个任何遵循了该协议的类型(即容器)必须提供的功能: ++ 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。 ++ 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 Int 值。 ++ 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。 -- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。 -- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `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 struct IntStack: Container { @@ -388,7 +421,7 @@ struct IntStack: Container { 此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item` 为 `Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。 -由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `Item` 为 `Int`。因为 `IntStack` 遵循 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。 +由于 Swift 的类型推断,实际上在 `IntStack` 的定义中不需要声明 `Item` 为 `Int`。因为 `IntStack` 符合 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切也可正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。 你也可以让泛型 `Stack` 结构体遵循 `Container` 协议: @@ -414,26 +447,22 @@ struct Stack: Container { } } ``` - 这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `Item` 的类型。 - -### 通过扩展一个存在的类型来指定关联类型 +### 扩展现有类型来指定关联类型 -[通过扩展添加协议遵循](./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` 遵循`Container` 协议,就可以扩展 Array,使其遵从 Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述: ```swift extension Array: Container {} ``` +`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 具体类型。定义了这个扩展后,你可以将任意 `Array` 当作 Container 来使用。 -如同上面的泛型 `Stack` 结构体一样,`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。 - - ### 给关联类型添加约束 -你可以给协议里的关联类型添加类型注释,让遵循协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` 的 `Container` 类型: +你可以在协议里给关联类型添加约束来要求遵循的类型满足约束。例如,下面的代码定义了 `Container` 协议, 要求关联类型 `Item` 必须遵循 `Equatable` 协议: ```swift protocol Container { @@ -443,13 +472,12 @@ protocol Container { subscript(i: Int) -> Item { get } } ``` - -为了遵循 `Container` 协议,Item 类型也必须遵循 `Equatable` 协议。 +要遵守 `Container` 协议,`Item` 类型也必须遵守 `Equatable` 协议。 ### 在关联类型约束里使用协议 -协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个 `suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,把它们存储在一个 `Suffix` 类型的实例里。 +协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个` suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,并把它们存储在一个 `Suffix` 类型的实例里。 ```swift protocol SuffixableContainer: Container { @@ -457,10 +485,9 @@ protocol SuffixableContainer: Container { func suffix(_ size: Int) -> Suffix } ``` +在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container` 的 `Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `where` 分句,它在下面带有泛型 `Where` 分句的扩展中有讨论。 -在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container` 的 `Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `wher`e 分句,它在下面[带有泛型 Where 分句的扩展](#extensions_with_a_generic_where_clause)中有讨论。 - -这里有一个来自[闭包的循环强引用](./23_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)的 Stack 类型的扩展,它添加了对 `SuffixableContainer` 协议的遵循: +这是上面 [强引用循环闭包](./23_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures) 中 `Stack` 类型的扩展,它遵循了 SuffixableContainer 协议: ```swift extension Stack: SuffixableContainer { @@ -480,8 +507,7 @@ stackOfInts.append(30) let suffix = stackOfInts.suffix(2) // suffix contains 20 and 30 ``` - -在上面的例子中,`Suffix` 是 `Stack` 的关联类型,也就是 `Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它添加了 `SuffixableContainer` 遵循,使用 `Stack` 作为它的后缀类型而不是 `IntStack`: +在上面的例子中,`Suffix` 是 `Stack` 的关联类型,也是 `Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它遵循了 `SuffixableContainer` 协议,使用 `Stack` 作为它的后缀类型而不是 `IntStack`: ```swift extension IntStack: SuffixableContainer { @@ -496,13 +522,13 @@ extension IntStack: SuffixableContainer { } ``` - + ## 泛型 Where 语句 -[类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。 +[类型约束](#type_constraints)让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。 -为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵循某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。 +对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 `where` 子句来实现。通过泛型 `where` 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 `where` 子句。 下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。 @@ -512,53 +538,52 @@ extension IntStack: SuffixableContainer { func allItemsMatch (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { - + // 检查两个容器含有相同数量的元素 if someContainer.count != anotherContainer.count { return false } - + // 检查每一对元素是否相等 for i in 0..() @@ -575,10 +600,11 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) { } // 打印 “All items match.” ``` +上面的例子创建 `Stack` 实例来存储 `String` 值,然后将三个字符串压栈。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。 -上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵循 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。 + ## 具有泛型 Where 子句的扩展 你也可以使用泛型 `where` 子句作为扩展的一部分。基于以前的例子,下面的示例扩展了泛型 `Stack` 结构体,添加一个 `isTop(_:)` 方法。 @@ -594,7 +620,7 @@ extension Stack where Element: Equatable { } ``` -这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是遵循 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素遵循 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。 +这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是符合 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。 以下是 `isTop(_:)` 方法的调用方式: @@ -607,7 +633,7 @@ if stackOfStrings.isTop("tres") { // 打印 "Top element is tres." ``` -如果尝试在其元素未遵循 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。 +如果尝试在其元素不符合 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。 ```swift struct NotEquatable { } @@ -616,7 +642,6 @@ let notEquatableValue = NotEquatable() notEquatableStack.push(notEquatableValue) notEquatableStack.isTop(notEquatableValue) // 报错 ``` - 你可以使用泛型 `where` 子句去扩展一个协议。基于以前的示例,下面的示例扩展了 `Container` 协议,添加一个 `startsWith(_:)` 方法。 ```swift @@ -627,7 +652,7 @@ extension Container where Item: Equatable { } ``` -这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何遵循 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是遵循 `Equatable` 协议的。 +这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 `Equatable` 协议的。 ```swift if [9, 9, 9].startsWith(42) { @@ -653,15 +678,15 @@ extension Container where Item == Double { print([1260.0, 1200.0, 98.6, 37.0].average()) // 打印 "648.9" ``` - 此示例将一个 `average()` 方法添加到 `Item` 类型为 `Double` 的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 `Int` 转换为 `Double` 确保能够进行浮点除法。 就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。 + ## 具有泛型 Where 子句的关联类型 -你可以在关联类型后面加上具有泛型 `where` 的字句。例如,建立一个包含迭代器(Iterator)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写: +你可以在关联类型后面加上具有泛型 `where` 的字句。例如,建立一个包含迭代器(`Iterator`)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写: ```swift protocol Container { @@ -669,24 +694,23 @@ protocol Container { mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } - + associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator } ``` - -迭代器(Iterator)的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。 +迭代器(`Iterator`)的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。 一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 `where` 子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 `ComparableContainer` 协议,它要求所有的 `Item` 必须是 `Comparable` 的。 ```swift protocol ComparableContainer: Container where Item: Comparable { } ``` - -##泛型下标 -下标能够是泛型的,他们能够包含泛型 `where` 子句。你可以把占位符类型的名称写在 `subscript` 后面的尖括号里,在下标代码体开始的标志的花括号之前写下泛型 `where` 子句。例如: +## 泛型下标 + +下标可以是泛型,它们能够包含泛型 `where` 子句。你可以在 `subscript` 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 `where` 子句。例如: ```swift extension Container { @@ -703,11 +727,8 @@ extension Container { 这个 `Container` 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下: -这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下: - -- 在尖括号中的泛型参数 `Indices`,必须是遵循标准库中的 `Sequence` 协议的类型。 -- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。 -- 泛型 `where` 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。 - -综合一下,这些约束意味着,传入到 `indices` 下标,是一个整型的序列。 ++ 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。 ++ 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。 ++ 泛型 `where` 子句要求 `Sequence(Indices)`的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(`Sequence`)中的索引和容器(`Container`)里面的索引类型是一致的。 +综合一下,这些约束意味着,传入到 `indices` 下标,是一个整型的序列。 \ No newline at end of file