From 4e4e29195667701ea61cff5a93de5a5147fc5db5 Mon Sep 17 00:00:00 2001 From: DanziChen Date: Thu, 27 Jun 2019 00:05:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=20(#952)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 术语表更新 * 更新文档内术语 * 术语表更新 * update Control Flow * update Version Compatibility * update Methods * update Types * 术语表更新 * update Types * Update 03_Types.md --- README.md | 2 + source/chapter1/02_version_compatibility.md | 9 +- source/chapter2/05_Control_Flow.md | 2 +- source/chapter2/11_Methods.md | 510 +++++----- source/chapter3/03_Types.md | 994 +++++++++++--------- 5 files changed, 789 insertions(+), 728 deletions(-) diff --git a/README.md b/README.md index f8344735..bb6a3fc6 100755 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ diff 操作如下: | assertion | 断言 | | conditional compilation | 条件编译 | | opaque type | 不透明类型 | +| function | 函数 | +| runtime | 运行时 | # 贡献者 diff --git a/source/chapter1/02_version_compatibility.md b/source/chapter1/02_version_compatibility.md index 482780af..4a501b68 100755 --- a/source/chapter1/02_version_compatibility.md +++ b/source/chapter1/02_version_compatibility.md @@ -1,10 +1,11 @@ # 版本兼容性 - -本书描述的是在 Xcode 10.2 中的默认 Swift 版本 Swift 5。你可以使用 Xcode10.2 来构建 Swift 5、Swift 4.2 或 Swift 4 写的项目 -当您使用 Xcode 10.2 构建 Swift 4 和 Swift 4.2 代码时,除了下面的功能仅支持 Swift 5,其他大多数功能都依然可用。 +本书描述的是在 Xcode 11 中的默认 Swift 版本 Swift 5.1。你可以使用 Xcode11 来构建 Swift 5.1、Swift 4.2 或 Swift 4 写的项目。 +当您使用 Xcode 11 构建 Swift 4 和 Swift 4.2 代码时,除了下面的功能仅支持 Swift 5.1,其他大多数功能都依然可用。 + +* 返回值是不透明类型的函数依赖 Swift 5.1 运行时。 * **try?** 表达式不会为已返回可选类型的代码引入额外的可选类型层级。 * 大数字的整型字面量初始化代码的类型将会被正确推导,例如 **UInt64(0xffff_ffff_ffff_ffff)** 将会被推导为整型类型而非溢出。 -用 Swift 5 写的项目可以依赖用 Swift 4.2 或 Swift 4 写的项目,反之亦然。这意味着,如果你将一个大的项目分解成多个框架(framework),你可以每次一个框架地迁移 Swift 4 代码到 Swift 5。 +用 Swift 5.1 写的项目可以依赖用 Swift 4.2 或 Swift 4 写的项目,反之亦然。这意味着,如果你将一个大的项目分解成多个框架(framework),你可以每次一个框架地迁移 Swift 4 代码到 Swift 5.1。 diff --git a/source/chapter2/05_Control_Flow.md b/source/chapter2/05_Control_Flow.md index 1371697c..0164c46d 100755 --- a/source/chapter2/05_Control_Flow.md +++ b/source/chapter2/05_Control_Flow.md @@ -30,9 +30,9 @@ let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] for (animalName, legCount) in numberOfLegs { print("\(animalName)s have \(legCount) legs") } +// cats have 4 legs // ants have 6 legs // spiders have 8 legs -// cats have 4 legs ``` 字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见[集合类型](./04_Collection_Types.md)。 diff --git a/source/chapter2/11_Methods.md b/source/chapter2/11_Methods.md index 4e27a3d3..9c061788 100755 --- a/source/chapter2/11_Methods.md +++ b/source/chapter2/11_Methods.md @@ -1,255 +1,255 @@ -# 方法 - -*方法*是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。 - -结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。 - -## 实例方法(Instance Methods) {#instance-methods} - -*实例方法*是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见[函数](./06_Functions.md)。 - -实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。 - -下面的例子,定义一个很简单的 `Counter` 类,`Counter` 能被用来对一个动作发生的次数进行计数: - -```swift -class Counter { - var count = 0 - func increment() { - count += 1 - } - func increment(by amount: Int) { - count += amount - } - func reset() { - count = 0 - } -} -``` - -`Counter` 类定义了三个实例方法: -- `increment` 让计数器按一递增; -- `increment(by: Int)` 让计数器按一个指定的整数值递增; -- `reset` 将计数器重置为0。 - -`Counter` 这个类还声明了一个可变属性 `count`,用它来保持对当前计数器值的追踪。 - -和调用属性一样,用点语法(dot syntax)调用实例方法: - -```swift -let counter = Counter() -// 初始计数值是0 -counter.increment() -// 计数值现在是1 -counter.increment(by: 5) -// 计数值现在是6 -counter.reset() -// 计数值现在是0 -``` - -函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.md#specifying_external_parameter_names)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。 - -### self 属性 {#the-self-property} - -类型的每一个实例都有一个隐含属性叫做 `self`,`self` 完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的 `self` 属性来引用当前实例。 - -上面例子中的 `increment` 方法还可以这样写: - -```swift -func increment() { - self.count += 1 -} -``` - -实际上,你不必在你的代码里面经常写 `self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写 `self`,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的 `Counter` 中已经示范了:`Counter` 中的三个实例方法中都使用的是 `count`(而不是 `self.count`)。 - -使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用 `self` 属性来区分参数名称和属性名称。 - -下面的例子中,`self` 消除方法参数 `x` 和实例属性 `x` 之间的歧义: - -```swift -struct Point { - var x = 0.0, y = 0.0 - func isToTheRightOf(x: Double) -> Bool { - return self.x > x - } -} -let somePoint = Point(x: 4.0, y: 5.0) -if somePoint.isToTheRightOf(x: 1.0) { - print("This point is to the right of the line where x == 1.0") -} -// 打印“This point is to the right of the line where x == 1.0” -``` - -如果不使用 `self` 前缀,Swift会认为 `x` 的两个用法都引用了名为 `x` 的方法参数。 - -### 在实例方法中修改值类型 {#modifying-value-types-from-within-instance-methods} - -结构体和枚举是*值类型*。默认情况下,值类型的属性不能在它的实例方法中被修改。 - -但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择 `可变(mutating)`行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的 `self` 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。 - -要使用 `可变`方法,将关键字 `mutating` 放到方法的 `func` 关键字之前就可以了: - -```swift -struct Point { - var x = 0.0, y = 0.0 - mutating func moveBy(x deltaX: Double, y deltaY: Double) { - x += deltaX - y += deltaY - } -} -var somePoint = Point(x: 1.0, y: 1.0) -somePoint.moveBy(x: 2.0, y: 3.0) -print("The point is now at (\(somePoint.x), \(somePoint.y))") -// 打印“The point is now at (3.0, 4.0)” -``` - -上面的 `Point` 结构体定义了一个可变方法 `moveBy(x:y :)` 来移动 `Point` 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 `mutating` 关键字,从而允许修改属性。 - -注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见[常量结构体的存储属性](./10_Properties.md#stored_properties_of_constant_structure_instances): - -```swift -let fixedPoint = Point(x: 3.0, y: 3.0) -fixedPoint.moveBy(x: 2.0, y: 3.0) -// 这里将会报告一个错误 -``` - -### 在可变方法中给 self 赋值 {#assigning-to-self-within-a-mutating-method} - -可变方法能够赋给隐含属性 `self` 一个全新的实例。上面 `Point` 的例子可以用下面的方式改写: - -```swift -struct Point { - var x = 0.0, y = 0.0 - mutating func moveBy(x deltaX: Double, y deltaY: Double) { - self = Point(x: x + deltaX, y: y + deltaY) - } -} -``` - -新版的可变方法 `moveBy(x:y:)` 创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。 - -枚举的可变方法可以把 `self` 设置为同一枚举类型中不同的成员: - -```swift -enum TriStateSwitch { - case off, low, high - mutating func next() { - switch self { - case .off: - self = .low - case .low: - self = .high - case .high: - self = .off - } - } -} -var ovenLight = TriStateSwitch.low -ovenLight.next() -// ovenLight 现在等于 .high -ovenLight.next() -// ovenLight 现在等于 .off -``` - -上面的例子中定义了一个三态切换的枚举。每次调用 `next()` 方法时,开关在不同的电源状态(`off`, `low`, `high`)之间循环切换。 - -## 类型方法 {#type-methods} - -实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做*类型方法*。在方法的 `func` 关键字之前加上关键字 `static`,来指定类型方法。类还可以用关键字 `class` 来允许子类重写父类的方法实现。 - -> 注意 -> -> 在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。 - -类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在 `SomeClass` 类上调用类型方法的例子: - -```swift -class SomeClass { - class func someTypeMethod() { - // 在这里实现类型方法 - } -} -SomeClass.someTypeMethod() -``` - -在类型方法的方法体(body)中,`self` 属性指向这个类型本身,而不是类型的某个实例。这意味着你可以用 `self` 来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。 - -一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。 - -下面的例子定义了一个名为 `LevelTracker` 结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息。 - -游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker` 结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。 - -```swift -struct LevelTracker { - static var highestUnlockedLevel = 1 - var currentLevel = 1 - - static func unlock(_ level: Int) { - if level > highestUnlockedLevel { highestUnlockedLevel = level } - } - - static func isUnlocked(_ level: Int) -> Bool { - return level <= highestUnlockedLevel - } - - @discardableResult - mutating func advance(to level: Int) -> Bool { - if LevelTracker.isUnlocked(level) { - currentLevel = level - return true - } else { - return false - } - } -} -``` - -`LevelTracker` 监测玩家已解锁的最高等级。这个值被存储在类型属性 `highestUnlockedLevel` 中。 - -`LevelTracker` 还定义了两个类型方法与 `highestUnlockedLevel` 配合工作。第一个类型方法是 `unlock(_:)`,一旦新等级被解锁,它会更新 `highestUnlockedLevel` 的值。第二个类型方法是 `isUnlocked(_:)`,如果某个给定的等级已经被解锁,它将返回 `true`。(注意,尽管我们没有使用类似 `LevelTracker.highestUnlockedLevel` 的写法,这个类型方法还是能够访问类型属性 `highestUnlockedLevel`) - -除了类型属性和类型方法,`LevelTracker` 还监测每个玩家的进度。它用实例属性 `currentLevel` 来监测每个玩家当前的等级。 - -为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@discardableResult` 属性,更多关于属性信息,请参考[特性](../chapter3/07_Attributes.html)章节。 - -下面,`Player` 类使用 `LevelTracker` 来监测和更新每个玩家的发展进度: - -```swift -class Player { - var tracker = LevelTracker() - let playerName: String - func complete(level: Int) { - LevelTracker.unlock(level + 1) - tracker.advance(to: level + 1) - } - init(name: String) { - playerName = name - } -} -``` - -`Player` 类创建一个新的 `LevelTracker` 实例来监测这个用户的进度。它提供了 `complete(level:)` 方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了 `advance(to:)` 返回的布尔值,因为之前调用 `LevelTracker.unlock(_:)` 时就知道了这个等级已经被解锁了)。 - -你还可以为一个新的玩家创建一个 `Player` 的实例,然后看这个玩家完成等级一时发生了什么: - -```swift -var player = Player(name: "Argyrios") -player.complete(level: 1) -print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") -// 打印“highest unlocked level is now 2” -``` - -如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败: - -```swift -player = Player(name: "Beto") -if player.tracker.advance(to: 6) { - print("player is now on level 6") -} else { - print("level 6 has not yet been unlocked") -} -// 打印“level 6 has not yet been unlocked” -``` +# 方法 + +*方法*是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。 + +结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。 + +## 实例方法(Instance Methods) {#instance-methods} + +*实例方法*是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见[函数](./06_Functions.md)。 + +实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。 + +下面的例子,定义一个很简单的 `Counter` 类,`Counter` 能被用来对一个动作发生的次数进行计数: + +```swift +class Counter { + var count = 0 + func increment() { + count += 1 + } + func increment(by amount: Int) { + count += amount + } + func reset() { + count = 0 + } +} +``` + +`Counter` 类定义了三个实例方法: +- `increment` 让计数器按一递增; +- `increment(by: Int)` 让计数器按一个指定的整数值递增; +- `reset` 将计数器重置为0。 + +`Counter` 这个类还声明了一个可变属性 `count`,用它来保持对当前计数器值的追踪。 + +和调用属性一样,用点语法(dot syntax)调用实例方法: + +```swift +let counter = Counter() +// 初始计数值是0 +counter.increment() +// 计数值现在是1 +counter.increment(by: 5) +// 计数值现在是6 +counter.reset() +// 计数值现在是0 +``` + +函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.md#specifying_external_parameter_names)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。 + +### self 属性 {#the-self-property} + +类型的每一个实例都有一个隐含属性叫做 `self`,`self` 完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的 `self` 属性来引用当前实例。 + +上面例子中的 `increment` 方法还可以这样写: + +```swift +func increment() { + self.count += 1 +} +``` + +实际上,你不必在你的代码里面经常写 `self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写 `self`,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的 `Counter` 中已经示范了:`Counter` 中的三个实例方法中都使用的是 `count`(而不是 `self.count`)。 + +使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用 `self` 属性来区分参数名称和属性名称。 + +下面的例子中,`self` 消除方法参数 `x` 和实例属性 `x` 之间的歧义: + +```swift +struct Point { + var x = 0.0, y = 0.0 + func isToTheRightOf(x: Double) -> Bool { + return self.x > x + } +} +let somePoint = Point(x: 4.0, y: 5.0) +if somePoint.isToTheRightOf(x: 1.0) { + print("This point is to the right of the line where x == 1.0") +} +// 打印“This point is to the right of the line where x == 1.0” +``` + +如果不使用 `self` 前缀,Swift会认为 `x` 的两个用法都引用了名为 `x` 的方法参数。 + +### 在实例方法中修改值类型 {#modifying-value-types-from-within-instance-methods} + +结构体和枚举是*值类型*。默认情况下,值类型的属性不能在它的实例方法中被修改。 + +但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择 `可变(mutating)`行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的 `self` 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。 + +要使用 `可变`方法,将关键字 `mutating` 放到方法的 `func` 关键字之前就可以了: + +```swift +struct Point { + var x = 0.0, y = 0.0 + mutating func moveBy(x deltaX: Double, y deltaY: Double) { + x += deltaX + y += deltaY + } +} +var somePoint = Point(x: 1.0, y: 1.0) +somePoint.moveBy(x: 2.0, y: 3.0) +print("The point is now at (\(somePoint.x), \(somePoint.y))") +// 打印“The point is now at (3.0, 4.0)” +``` + +上面的 `Point` 结构体定义了一个可变方法 `moveBy(x:y :)` 来移动 `Point` 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 `mutating` 关键字,从而允许修改属性。 + +注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见[常量结构体的存储属性](./10_Properties.md#stored_properties_of_constant_structure_instances): + +```swift +let fixedPoint = Point(x: 3.0, y: 3.0) +fixedPoint.moveBy(x: 2.0, y: 3.0) +// 这里将会报告一个错误 +``` + +### 在可变方法中给 self 赋值 {#assigning-to-self-within-a-mutating-method} + +可变方法能够赋给隐含属性 `self` 一个全新的实例。上面 `Point` 的例子可以用下面的方式改写: + +```swift +struct Point { + var x = 0.0, y = 0.0 + mutating func moveBy(x deltaX: Double, y deltaY: Double) { + self = Point(x: x + deltaX, y: y + deltaY) + } +} +``` + +新版的可变方法 `moveBy(x:y:)` 创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。 + +枚举的可变方法可以把 `self` 设置为同一枚举类型中不同的成员: + +```swift +enum TriStateSwitch { + case off, low, high + mutating func next() { + switch self { + case .off: + self = .low + case .low: + self = .high + case .high: + self = .off + } + } +} +var ovenLight = TriStateSwitch.low +ovenLight.next() +// ovenLight 现在等于 .high +ovenLight.next() +// ovenLight 现在等于 .off +``` + +上面的例子中定义了一个三态切换的枚举。每次调用 `next()` 方法时,开关在不同的电源状态(`off`, `low`, `high`)之间循环切换。 + +## 类型方法 {#type-methods} + +实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做*类型方法*。在方法的 `func` 关键字之前加上关键字 `static`,来指定类型方法。类还可以用关键字 `class` 来指定,从而允许子类重写父类该方法的实现。 + +> 注意 +> +> 在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。 + +类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在 `SomeClass` 类上调用类型方法的例子: + +```swift +class SomeClass { + class func someTypeMethod() { + // 在这里实现类型方法 + } +} +SomeClass.someTypeMethod() +``` + +在类型方法的方法体(body)中,`self` 属性指向这个类型本身,而不是类型的某个实例。这意味着你可以用 `self` 来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。 + +一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。 + +下面的例子定义了一个名为 `LevelTracker` 结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息。 + +游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker` 结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。 + +```swift +struct LevelTracker { + static var highestUnlockedLevel = 1 + var currentLevel = 1 + + static func unlock(_ level: Int) { + if level > highestUnlockedLevel { highestUnlockedLevel = level } + } + + static func isUnlocked(_ level: Int) -> Bool { + return level <= highestUnlockedLevel + } + + @discardableResult + mutating func advance(to level: Int) -> Bool { + if LevelTracker.isUnlocked(level) { + currentLevel = level + return true + } else { + return false + } + } +} +``` + +`LevelTracker` 监测玩家已解锁的最高等级。这个值被存储在类型属性 `highestUnlockedLevel` 中。 + +`LevelTracker` 还定义了两个类型方法与 `highestUnlockedLevel` 配合工作。第一个类型方法是 `unlock(_:)`,一旦新等级被解锁,它会更新 `highestUnlockedLevel` 的值。第二个类型方法是 `isUnlocked(_:)`,如果某个给定的等级已经被解锁,它将返回 `true`。(注意,尽管我们没有使用类似 `LevelTracker.highestUnlockedLevel` 的写法,这个类型方法还是能够访问类型属性 `highestUnlockedLevel`) + +除了类型属性和类型方法,`LevelTracker` 还监测每个玩家的进度。它用实例属性 `currentLevel` 来监测每个玩家当前的等级。 + +为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@discardableResult` 属性,更多关于属性信息,请参考[特性](../chapter3/07_Attributes.html)章节。 + +下面,`Player` 类使用 `LevelTracker` 来监测和更新每个玩家的发展进度: + +```swift +class Player { + var tracker = LevelTracker() + let playerName: String + func complete(level: Int) { + LevelTracker.unlock(level + 1) + tracker.advance(to: level + 1) + } + init(name: String) { + playerName = name + } +} +``` + +`Player` 类创建一个新的 `LevelTracker` 实例来监测这个用户的进度。它提供了 `complete(level:)` 方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了 `advance(to:)` 返回的布尔值,因为之前调用 `LevelTracker.unlock(_:)` 时就知道了这个等级已经被解锁了)。 + +你还可以为一个新的玩家创建一个 `Player` 的实例,然后看这个玩家完成等级一时发生了什么: + +```swift +var player = Player(name: "Argyrios") +player.complete(level: 1) +print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") +// 打印“highest unlocked level is now 2” +``` + +如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败: + +```swift +player = Player(name: "Beto") +if player.tracker.advance(to: 6) { + print("player is now on level 6") +} else { + print("level 6 has not yet been unlocked") +} +// 打印“level 6 has not yet been unlocked” +``` diff --git a/source/chapter3/03_Types.md b/source/chapter3/03_Types.md index d4c0a8fe..d2dd6ba2 100644 --- a/source/chapter3/03_Types.md +++ b/source/chapter3/03_Types.md @@ -1,468 +1,526 @@ -# 类型(Types) - -Swift 语言存在两种类型:命名型类型和复合型类型。*命名型类型*是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义类 `MyClass` 的实例拥有类型 `MyClass`。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。 - -那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/20_Extensions.md) 和 [扩展声明](./06_Declarations.md#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。 - -*复合型类型*是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。 - -你可以在命名型类型和复合型类型使用小括号。但是在类型旁加小括号没有任何作用。举个例子,`(Int)` 等同于 `Int`。 - -本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。 - -#### type {#type} - -> 类型语法 -> -> *类型* → [*数组类型*](#array-type) -> -> *类型* → [*字典类型*](#dictionary-type) -> -> *类型* → [*函数类型*](#function-type) -> -> *类型* → [*类型标识*](#type-identifier) -> -> *类型* → [*元组类型*](#tuple-type) -> -> *类型* → [*可选类型*](#optional-type) -> -> *类型* → [*隐式解析可选类型*](#implicitly-unwrapped-optional-type) -> -> *类型* → [*协议合成类型*](#protocol-composition-type) -> -> *类型* → [*元型类型*](#metatype-type) -> -> *类型* → **任意类型** -> -> *类型* → **自身类型** -> -> *类型* → [*(类型)*](#type) -> - -## 类型注解 {#type-annotation} -*类型注解*显式地指定一个变量或表达式的类型。类型注解始于冒号 `:` 终于类型,比如下面两个例子: - -```swift -let someTuple: (Double, Double) = (3.14159, 2.71828) -func someFunction(a: Int) { /* ... */ } -``` - -在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的参数 `a` 的类型被指定为 `Int`。 - -类型注解可以在类型之前包含一个类型特性的可选列表。 - -> 类型注解语法 -> - -#### type-annotation {#type-annotation} -> *类型注解* → **:** [*特性列表*](./07_Attributes.md#attributes)可选 **输入输出参数**可选 [*类型*](#type) -> - -## 类型标识符 {#type-identifier} -类型标识符引用命名型类型,还可引用命名型或复合型类型的别名。 - -大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary` 引用命名型类型 `Dictionary`。 - -在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`: - -```swift -typealias Point = (Int, Int) -let origin: Point = (0, 0) -``` - -情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `ExampleModule` 模块中声明的命名型类型 `MyType`: - -```swift -var someValue: ExampleModule.MyType -``` - -> 类型标识符语法 -> - -#### type-identifier {#type-identifier} -> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)可选 | [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)可选 **.** [*类型标识符*](#type-identifier) -> - -#### type-name {#type-name} -> *类型名称* → [*标识符*](./02_Lexical_Structure.md#identifier) -> - -## 元组类型 {#tuple-type} -元组类型是使用括号括起来的零个或多个类型,类型间用逗号隔开。 - -你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.md#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。 - -当一个元组类型的元素有名字的时候,这个名字就是类型的一部分。 - -```swift -var someTuple = (top: 10, bottom: 12) // someTuple 的类型为 (top: Int, bottom: Int) -someTuple = (top: 4, bottom: 42) // 正确:命名类型匹配 -someTuple = (9, 99) // 正确:命名类型被自动推断 -someTuple = (left: 5, right: 5) // 错误:命名类型不匹配 -``` - -所有的元组类型都包含两个及以上元素, 除了 `Void`。`Void` 是空元组类型 `()` 的别名。 - -> 元组类型语法 -> - -#### tuple-type {#tuple-type} -> *元组类型* → **(** **)** | **(** [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) **)** -> - -#### tuple-type-element-list {#tuple-type-element-list} -> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) -> - -#### tuple-type-element {#tuple-type-element} -> *元组类型元素* → [*元素名*](#element-name) [*类型注解*](#type-annotation) | [*类型*](#type) -> - -#### element-name {#element-name} -> *元素名* → [*标识符*](./02_Lexical_Structure.md#identifier) -> - -## 函数类型 {#function-type} -函数类型表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开: - -> (`参数类型`)->(`返回值类型`) - -*参数类型*是由逗号间隔的类型列表。由于*返回值类型*可以是元组类型,所以函数类型支持多返回值的函数与方法。 - -你可以对参数类型为 `() -> T`(其中 T 是任何类型)的函数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.md#autoclosures)。 - -函数类型可以拥有一个可变长参数作为*参数类型*中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.md#variadic_parameters)。 - -为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.md#in_out_parameters)。 - -如果一个函数类型只有一个形式参数而且形式参数的类型是元组类型,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说,`((Int, Int)) -> Void` 是接收一个元组 `(Int, Int)` 作为形式参数并且不返回任何值的函数类型。与此相对,不加括号的 `(Int, Int) -> Void` 是一个接收两个 `Int` 作为形式参数并且不返回任何值的函数类型。相似地,因为 `Void` 是空元组类型 `()` 的别名,函数类型 `(Void)-> Void` 与 `(()) -> ()` 是一样的 - 一个将空元组作为唯一参数的函数。但这些类型和无变量的函数类型 `() -> ()` 是不一样的。 - -函数和方法中的变量名并不是函数类型的一部分。例如: - -```swift -func someFunction(left: Int, right: Int) {} -func anotherFunction(left: Int, right: Int) {} -func functionWithDifferentLabels(top: Int, bottom: Int) {} - -var f = someFunction // 函数 f 的类型为 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void. - -f = anotherFunction // 正确 -f = functionWithDifferentLabels // 正确 - -func functionWithDifferentArgumentTypes(left: Int, right: String) {} -f = functionWithDifferentArgumentTypes // 错误 - -func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {} -f = functionWithDifferentNumberOfArguments // 错误 -``` - -由于变量标签不是函数类型的一部分,你可以在写函数类型的时候省略它们。 - -```swift -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` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](./06_Declarations.md#throwing_functions_and_methods) 和 [重抛函数与方法](./06_Declarations.md#rethrowing_functions_and_methods)。 - -### 对非逃逸闭包的限制 {#Restrictions for Nonescaping Closures} -当非逃逸闭包函数是参数时,不能存储在属性、变量或任何 `Any` 类型的常量中,因为这可能导致值的逃逸。 - -当非逃逸闭包函数是参数时,不能作为参数传递到另一个非逃逸闭包函数中。这样的限制可以让 Swift 在编译时就完成更多的内存访问冲突检查,而不是在运行时。举个例子: - -```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`, 因此它们都是非逃逸的。 - -上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为参数 `first` 和 `second` 是非逃逸函数,它们不能够作为参数被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不会产生编译错误。这些函数调用不会违反限制,因为 `external` 不是 `takesTwoFunctions(first:second:)` 的参数之一。 - -如果你需要避免这个限制,标记其中之一的参数为逃逸,或者使用 `withoutActuallyEscaping(_:do:)` 函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅[内存安全](../chapter2/24_Memory_Safety.md)。 - -> 函数类型语法 -> - -#### function-type {#function-type} -> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **throws**可选 **->** [*类型*](#type) -> -> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **rethrows­** **->** [*类型*](#type) -> - -#### function-type-argument-clause {#function-type-argument-clause} -> *函数类型子句* → **(**­ **)**­ -> *函数类型子句* → **(** [*函数类型参数列表*](#function-type-argument-list) *...*­ 可选 **)** -> - -#### function-type-argument-list {#function-type-argument-list} -> *函数类型参数列表* → [*函数类型参数*](function-type-argument) | [*函数类型参数*](function-type-argument), [*函数类型参数列表*](#function-type-argument-list) -> - -#### function-type-argument {#function-type-argument} -> *函数类型参数* → [*特性列表*](./07_Attributes.md#attributes)可选 **输入输出参数**可选 [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation) -> - -#### argument-label {#argument-label} -> *参数标签* → [*标识符*](./02_Lexical_Structure.md#identifier) -> - -## 数组类型 {#array-type} -Swift 语言为标准库中定义的 `Array` 类型提供了如下语法糖: - -> [`类型`] -> - -换句话说,下面两个声明是等价的: - -```swift -let someArray: Array = ["Alex", "Brian", "Dave"] -let someArray: [String] = ["Alex", "Brian", "Dave"] -``` - -上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`。 - -你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组: - -```swift -var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] -``` - -访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]` 是 `[[1, 2], [3, 4]]`,`array3D[0][1]` 是 `[3, 4]`,`array3D[0][1][1]` 则是 `4`。 - -关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.md#arrays)。 - -> 数组类型语法 -> - -#### array-type {#array-type} -> *数组类型* → **[** [*类型*](#type) **]** -> - -## 字典类型 {#dictionary-type} -Swift 语言为标准库中定义的 `Dictionary` 类型提供了如下语法糖: - -> [`键类型` : `值类型`] -> - -换句话说,下面两个声明是等价的: - -```swift -let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39] -let someDictionary: Dictionary = ["Alex": 31, "Paul": 39] -``` - -上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。 - -字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。通过下标访问会获取对应值的可选类型。如果键在字典中不存在的话,则这个下标返回 `nil`。 - -字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。 - -关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.md#dictionaries)。 - -> 字典类型语法 -> - -#### dictionary-type {#dictionary-type} -> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]** -> - -## 可选类型 {#optional-type} -Swift 定义后缀 `?` 来作为标准库中定义的命名型类型 `Optional` 的语法糖。换句话说,下面两个声明是等价的: - -```swift -var optionalInteger: Int? -var optionalInteger: Optional -``` - -在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。 - -类型 `Optional` 是一个枚举,有两个成员,`none` 和 `some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`。 - -如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的: - -```swift -optionalInteger = 42 -optionalInteger! // 42 -``` - -使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。 - -你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。 - -更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.md#optionals)。 - -> 可选类型语法 -> - -#### optional-type {#optional-type} -> *可选类型* → [*类型*](#type) **?** -> - -## 隐式解析可选类型 {#implicitly-unwrapped-optional-type} -当可以被访问时,Swift 语言定义后缀 `!` 作为标准库中命名类型 `Optional` 的语法糖,来实现自动解包的功能。如果尝试对一个值为 `nil` 的可选类型进行隐式解包,将会产生运行时错误。因为隐式解包,下面两个声明等价: - -```swift -var implicitlyUnwrappedString: String! -var explicitlyUnwrappedString: Optional -``` - -注意类型与 `!` 之间没有空格。 - -由于隐式解包会更改包含该类型的声明语义,嵌套在元组类型或泛型中可选类型(比如字典元素类型或数组元素类型),不能被标记为隐式解包。例如: - -```swift -let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 错误 -let implicitlyUnwrappedTuple: (Int, Int)! // 正确 - -let arrayOfImplicitlyUnwrappedElements: [Int!] // 错误 -let implicitlyUnwrappedArray: [Int]! // 正确 -``` - -由于隐式解析可选类型和可选类型有同样的类型 `Optional`,你可以在所有使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。 - -正如可选类型一样,如果你在声明隐式解析可选类型的变量或属性的时候没有指定初始值,它的值则会自动赋为默认值 `nil`。 - -可以使用可选链式调用对隐式解析可选表达式选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。 - -关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.md#implicityly_unwrapped_optionals)。 - -> 隐式解析可选类型语法 -> - -#### implicitly-unwrapped-optional-type {#implicitly-unwrapped-optional-type} -> *隐式解析可选类型* → [*类型*](#type) **!** -> - -## 协议合成类型 {#protocol-composition-type} -协议合成类型定义了一种遵循协议列表中每个指定协议的类型,或者一个现有类型的子类并遵循协议列表中每个指定协议。协议合成类型只能用在类型注解、泛型参数子句和泛型 `where` 子句中指定类型。 - -协议合成类型的形式如下: - -> `Protocol 1` & `Procotol 2` -> - -协议合成类型允许你指定一个值,其类型遵循多个协议的要求而不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `Protocol A & Protocol B & Protocol C` 等效于一个从 `Protocol A`,`Protocol B`,`Protocol C` 继承而来的新协议。同样的,你可以使用 `SuperClass & ProtocolA` 来取代申明一个新的协议作为 `SuperClass` 的子类并遵循 `ProtocolA`。 - -协议合成列表中的每一项都必须是下面所列情况之一,列表中最多只能包含一个类: - -- 类名 -- 协议名 -- 一个类型别名,它的潜在类型是一个协议合成类型、一个协议或者一个类 - -当协议合成类型包含类型别名时,同一个协议可能多次出现在定义中 — 重复被忽略。例如,下面代码中定义的 `PQR` 等同于 `P & Q & R`。 - -```swift -typealias PQ = P & Q -typealias PQR = PQ & Q & R -``` - -> 协议合成类型语法 -> - -#### protocol-composition-type {#protocol-composition-type} -> *协议合成类型* → [*协议标识符*](#protocol-identifier) & [*协议合成延续*](#protocol-composition-continuation) -> - -#### protocol-composition-continuation {#protocol-composition-continuation} -> *协议合成延续* → [*协议标识符*](#protocol-identifier) | [*协议合成类型*](#protocol-composition-type) -> - -## 元类型 {#metatype-type} -元类型是指任意类型的类型,包括类类型、结构体类型、枚举类型和协议类型。 - -类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时遵循该协议的具体类型——是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`。 - -你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时遵循 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `type(of:)` 表达式来获取该实例动态的、在运行阶段的类型,如下所示: - -```swift -class SomeBaseClass { - class func printClassName() { - println("SomeBaseClass") - } -} -class SomeSubClass: SomeBaseClass { - override class func printClassName() { - println("SomeSubClass") - } -} -let someInstance: SomeBaseClass = SomeSubClass() -// someInstance 在编译期是 SomeBaseClass 类型, -// 但是在运行期则是 SomeSubClass 类型 -type(of: someInstance).printClassName() -// 打印“SomeSubClass” -``` - -更多信息可以查看 Swift 标准库里的 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type)。 - -可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。 - -```swift -class AnotherSubClass: SomeBaseClass { - let string: String - required init(string: String) { - self.string = string - } - override class func printClassName() { - print("AnotherSubClass") - } -} -let metatype: AnotherSubClass.Type = AnotherSubClass.self -let anotherInstance = metatype.init(string: "some string") -``` - -> 元类型语法 -> - -#### metatype-type {#metatype-type} -> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol** -> - -## 类型继承子句 {#type-inheritance-clause} -类型继承子句被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句开始于冒号 `:`,其后是类型标识符列表。 - -类可以继承自单个超类,并遵循任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要遵循的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.md)。 - -其它命名型类型只能继承自或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。 - -枚举定义中的类型继承子句可以是一系列协议,或者是指定单一的命名类型,此时枚举为其用例分配原始值。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.md#raw_values)。 - -> 类型继承子句语法 -> - -#### type_inheritance_clause {#type-inheritance-clause} -> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list) -> - -#### type-inheritance-list {#type-inheritance-list} -> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list) -> - -#### class-requirement {#class-requirement} - - -## 类型推断 {#type-inference} -Swift 广泛使用类型推断,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary`。 - -在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0` 中 `x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。 - -在 Swift 中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量 `eFloat` 上的显式类型注解(`: Float`)将导致数字字面量 `2.71828` 的类型是 `Float` 而非 `Double`。 - -```swift -let e = 2.71828 // e 的类型会被推断为 Double -let eFloat: Float = 2.71828 // eFloat 的类型为 Float -``` - -Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。 \ No newline at end of file +# 类型(Types) + +Swift 语言存在两种类型:命名型类型和复合型类型。*命名型类型*是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义类 `MyClass` 的实例拥有类型 `MyClass`。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。 + +那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/20_Extensions.md) 和 [扩展声明](./06_Declarations.md#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。 + +*复合型类型*是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。 + +你可以在命名型类型和复合型类型使用小括号。但是在类型旁加小括号没有任何作用。举个例子,`(Int)` 等同于 `Int`。 + +本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。 + +#### type {#type} + +> 类型语法 +> +> *类型* → [函数类型](#function-type) +> +> *类型* → [数组类型](#array-type) +> +> *类型* → [字典类型](#dictionary-type) +> +> *类型* → [类型标识](#type-identifier) +> +> *类型* → [元组类型](#tuple-type) +> +> *类型* → [可选类型](#optional-type) +> +> *类型* → [隐式解析可选类型](#implicitly-unwrapped-optional-type) +> +> *类型* → [协议合成类型](#protocol-composition-type) +> +> *类型* →[不透明类型](#opaque-type) +> +> *类型* → [元型类型](#metatype-type) +> +> *类型* → [自身类型](#self-type) +> +> *类型* → **Any** +> +> *类型* → **(** [类型](#type) **)** + +## 类型注解 +*类型注解*显式地指定一个变量或表达式的类型。类型注解始于冒号 `:` 终于类型,比如下面两个例子: + +```swift +let someTuple: (Double, Double) = (3.14159, 2.71828) +func someFunction(a: Int) { /* ... */ } +``` + +在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的参数 `a` 的类型被指定为 `Int`。 + +类型注解可以在类型之前包含一个类型特性的可选列表。 + +> 类型注解语法 +> + +#### type-annotation {#type-annotation} +> *类型注解* → **:** [*特性列表*](./07_Attributes.md#attributes)可选 **输入输出参数**可选 [*类型*](#type) + +## 类型标识符 +*类型标识符*引用命名型类型,还可引用命名型或复合型类型的别名。 + +大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary` 引用命名型类型 `Dictionary`。 + +在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`: + +```swift +typealias Point = (Int, Int) +let origin: Point = (0, 0) +``` + +情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `ExampleModule` 模块中声明的命名型类型 `MyType`: + +```swift +var someValue: ExampleModule.MyType +``` + +> 类型标识符语法 +> + +#### type-identifier {#type-identifier} +> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)可选 | [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)可选 **.** [*类型标识符*](#type-identifier) +> + +#### type-name {#type-name} +> *类型名称* → [*标识符*](./02_Lexical_Structure.md#identifier) + +## 元组类型 +*元组类型*是使用括号括起来的零个或多个类型,类型间用逗号隔开。 + +你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.md#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。 + +当一个元组类型的元素有名字的时候,这个名字就是类型的一部分。 + +```swift +var someTuple = (top: 10, bottom: 12) // someTuple 的类型为 (top: Int, bottom: Int) +someTuple = (top: 4, bottom: 42) // 正确:命名类型匹配 +someTuple = (9, 99) // 正确:命名类型被自动推断 +someTuple = (left: 5, right: 5) // 错误:命名类型不匹配 +``` + +所有的元组类型都包含两个及以上元素, 除了 `Void`。`Void` 是空元组类型 `()` 的别名。 + +> 元组类型语法 +> + +#### tuple-type {#tuple-type} +> *元组类型* → **(** **)** | **(** [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) **)** +> + +#### tuple-type-element-list {#tuple-type-element-list} +> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) +> + +#### tuple-type-element {#tuple-type-element} +> *元组类型元素* → [*元素名*](#element-name) [*类型注解*](#type-annotation) | [*类型*](#type) +> + +#### element-name {#element-name} +> *元素名* → [*标识符*](./02_Lexical_Structure.md#identifier) +> + +## 函数类型 +*函数类型*表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开: + +> (`参数类型`)->(`返回值类型`) + +*参数类型*是由逗号间隔的类型列表。由于*返回值类型*可以是元组类型,所以函数类型支持多返回值的函数与方法。 + +你可以对参数类型为 `() -> T`(其中 T 是任何类型)的函数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.md#autoclosures)。 + +函数类型可以拥有一个可变长参数作为*参数类型*中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.md#variadic_parameters)。 + +为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.md#in_out_parameters)。 + +如果一个函数类型只有一个形式参数而且形式参数的类型是元组类型,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说,`((Int, Int)) -> Void` 是接收一个元组 `(Int, Int)` 作为形式参数并且不返回任何值的函数类型。与此相对,不加括号的 `(Int, Int) -> Void` 是一个接收两个 `Int` 作为形式参数并且不返回任何值的函数类型。相似地,因为 `Void` 是空元组类型 `()` 的别名,函数类型 `(Void)-> Void` 与 `(()) -> ()` 是一样的 - 一个将空元组作为唯一参数的函数。但这些类型和无变量的函数类型 `() -> ()` 是不一样的。 + +函数和方法中的变量名并不是函数类型的一部分。例如: + +```swift +func someFunction(left: Int, right: Int) {} +func anotherFunction(left: Int, right: Int) {} +func functionWithDifferentLabels(top: Int, bottom: Int) {} + +var f = someFunction // 函数 f 的类型为 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void. + +f = anotherFunction // 正确 +f = functionWithDifferentLabels // 正确 + +func functionWithDifferentArgumentTypes(left: Int, right: String) {} +f = functionWithDifferentArgumentTypes // 错误 + +func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {} +f = functionWithDifferentNumberOfArguments // 错误 +``` + +由于变量标签不是函数类型的一部分,你可以在写函数类型的时候省略它们。 + +```swift +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` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](./06_Declarations.md#throwing_functions_and_methods) 和 [重抛函数与方法](./06_Declarations.md#rethrowing_functions_and_methods)。 + +### 对非逃逸闭包的限制 {#Restrictions for Nonescaping Closures} +当非逃逸闭包函数是参数时,不能存储在属性、变量或任何 `Any` 类型的常量中,因为这可能导致值的逃逸。 + +当非逃逸闭包函数是参数时,不能作为参数传递到另一个非逃逸闭包函数中。这样的限制可以让 Swift 在编译时就完成更多的内存访问冲突检查,而不是在运行时。举个例子: + +```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`, 因此它们都是非逃逸的。 + +上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为参数 `first` 和 `second` 是非逃逸函数,它们不能够作为参数被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不会产生编译错误。这些函数调用不会违反限制,因为 `external` 不是 `takesTwoFunctions(first:second:)` 的参数之一。 + +如果你需要避免这个限制,标记其中之一的参数为逃逸,或者使用 `withoutActuallyEscaping(_:do:)` 函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅[内存安全](../chapter2/24_Memory_Safety.md)。 + +> 函数类型语法 +> + +#### function-type {#function-type} +> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **throws**可选 **->** [*类型*](#type) +> +> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)可选 [*函数类型子句*](#function-type-argument-clause) **rethrows­** **->** [*类型*](#type) +> + +#### function-type-argument-clause {#function-type-argument-clause} +> *函数类型子句* → **(**­ **)**­ +> *函数类型子句* → **(** [*函数类型参数列表*](#function-type-argument-list) *...*­ 可选 **)** +> + +#### function-type-argument-list {#function-type-argument-list} +> *函数类型参数列表* → [*函数类型参数*](function-type-argument) | [*函数类型参数*](function-type-argument), [*函数类型参数列表*](#function-type-argument-list) +> + +#### function-type-argument {#function-type-argument} +> *函数类型参数* → [*特性列表*](./07_Attributes.md#attributes)可选 **输入输出参数**可选 [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation) +> + +#### argument-label {#argument-label} +> *参数标签* → [*标识符*](./02_Lexical_Structure.md#identifier) +> + +## 数组类型 +Swift 语言为标准库中定义的 `Array` 类型提供了如下语法糖: + +> [`类型`] +> + +换句话说,下面两个声明是等价的: + +```swift +let someArray: Array = ["Alex", "Brian", "Dave"] +let someArray: [String] = ["Alex", "Brian", "Dave"] +``` + +上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`。 + +你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组: + +```swift +var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +``` + +访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]` 是 `[[1, 2], [3, 4]]`,`array3D[0][1]` 是 `[3, 4]`,`array3D[0][1][1]` 则是 `4`。 + +关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.md#arrays)。 + +> 数组类型语法 +> + +#### array-type {#array-type} +> *数组类型* → **[** [*类型*](#type) **]** +> + +## 字典类型 +Swift 语言为标准库中定义的 `Dictionary` 类型提供了如下语法糖: + +> [`键类型` : `值类型`] +> + +换句话说,下面两个声明是等价的: + +```swift +let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39] +let someDictionary: Dictionary = ["Alex": 31, "Paul": 39] +``` + +上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。 + +字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。通过下标访问会获取对应值的可选类型。如果键在字典中不存在的话,则这个下标返回 `nil`。 + +字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。 + +关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.md#dictionaries)。 + +> 字典类型语法 +> + +#### dictionary-type {#dictionary-type} +> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]** +> + +## 可选类型 +Swift 定义后缀 `?` 来作为标准库中定义的命名型类型 `Optional` 的语法糖。换句话说,下面两个声明是等价的: + +```swift +var optionalInteger: Int? +var optionalInteger: Optional +``` + +在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。 + +类型 `Optional` 是一个枚举,有两个成员,`none` 和 `some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`。 + +如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的: + +```swift +optionalInteger = 42 +optionalInteger! // 42 +``` + +使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。 + +你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。 + +更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.md#optionals)。 + +> 可选类型语法 +> + +#### optional-type {#optional-type} +> *可选类型* → [*类型*](#type) **?** +> + +## 隐式解析可选类型 {#implicitly-unwrapped-optional-type} +当可以被访问时,Swift 语言定义后缀 `!` 作为标准库中命名类型 `Optional` 的语法糖,来实现自动解包的功能。如果尝试对一个值为 `nil` 的可选类型进行隐式解包,将会产生运行时错误。因为隐式解包,下面两个声明等价: + +```swift +var implicitlyUnwrappedString: String! +var explicitlyUnwrappedString: Optional +``` + +注意类型与 `!` 之间没有空格。 + +由于隐式解包会更改包含该类型的声明语义,嵌套在元组类型或泛型中可选类型(比如字典元素类型或数组元素类型),不能被标记为隐式解包。例如: + +```swift +let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 错误 +let implicitlyUnwrappedTuple: (Int, Int)! // 正确 + +let arrayOfImplicitlyUnwrappedElements: [Int!] // 错误 +let implicitlyUnwrappedArray: [Int]! // 正确 +``` + +由于隐式解析可选类型和可选类型有同样的类型 `Optional`,你可以在所有使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。 + +正如可选类型一样,如果你在声明隐式解析可选类型的变量或属性的时候没有指定初始值,它的值则会自动赋为默认值 `nil`。 + +可以使用可选链式调用对隐式解析可选表达式选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。 + +关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.md#implicityly_unwrapped_optionals)。 + +> 隐式解析可选类型语法 +> + +#### implicitly-unwrapped-optional-type {#implicitly-unwrapped-optional-type} +> *隐式解析可选类型* → [*类型*](#type) **!** +> + +## 协议合成类型 +*协议合成类型*定义了一种遵循协议列表中每个指定协议的类型,或者一个现有类型的子类并遵循协议列表中每个指定协议。协议合成类型只能用在类型注解、泛型参数子句和泛型 `where` 子句中指定类型。 + +协议合成类型的形式如下: + +> `Protocol 1` & `Procotol 2` + +协议合成类型允许你指定一个值,其类型遵循多个协议的要求而不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `Protocol A & Protocol B & Protocol C` 等效于一个从 `Protocol A`,`Protocol B`,`Protocol C` 继承而来的新协议。同样的,你可以使用 `SuperClass & ProtocolA` 来取代申明一个新的协议作为 `SuperClass` 的子类并遵循 `ProtocolA`。 + +协议合成列表中的每一项都必须是下面所列情况之一,列表中最多只能包含一个类: + +- 类名 +- 协议名 +- 一个类型别名,它的潜在类型是一个协议合成类型、一个协议或者一个类 + +当协议合成类型包含类型别名时,同一个协议可能多次出现在定义中 — 重复被忽略。例如,下面代码中定义的 `PQR` 等同于 `P & Q & R`。 + +```swift +typealias PQ = P & Q +typealias PQR = PQ & Q & R +``` + +> 协议合成类型语法 +> + +#### protocol-composition-type {#protocol-composition-type} +> *协议合成类型* → [*协议标识符*](#protocol-identifier) & [*协议合成延续*](#protocol-composition-continuation) +> + +#### protocol-composition-continuation {#protocol-composition-continuation} +> *协议合成延续* → [*协议标识符*](#protocol-identifier) | [*协议合成类型*](#protocol-composition-type) + +## 不透明类型 + +*不透明类型*定义了遵循某个协议或者合成协议的类型,但不需要指明底层的具体类型。 + +不透明类型可以作为函数或下标的返回值,亦或是属性的类型使用。 + +不透明类型不能作为元组类型的一部分或范型类型使用,比如数组元素类型或者可选值的包装类型。 + +不透明类型的形式如下: + +> some `constraint` + +*constraint* 可以是类类型,协议类型,协议组合类型或者 `Any`。值只有当它遵循该协议或者组合协议,或者从该类继承的时候,才能作为这个不透明类型的实例使用。和不透明值交互的代码只能使用该值定义在 *constraint* 上的接口。 + +协议声明里不能包括不透明类型。类不能使用不透明类型作为非 final 方法的返回值。 + +使用不透明类型作为返回值的函数必须返回单一公用底层类型。返回的类型可以包含函数范型类型参数的一部分。举个例子,函数 `someFunction()` 可以返回类型 `T` 或者 `Dictionary` 的值。 + +> 不透明类型语法 + +#### opaque-type {#opaque-type} + +> *不透明类型* → **some** [type](#type) + +## 元类型 + +*元类型*是指任意类型的类型,包括类类型、结构体类型、枚举类型和协议类型。 + +类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时遵循该协议的具体类型——是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`。 + +你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时遵循 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `type(of:)` 表达式来获取该实例动态的、在运行阶段的类型,如下所示: + +```swift +class SomeBaseClass { + class func printClassName() { + println("SomeBaseClass") + } +} +class SomeSubClass: SomeBaseClass { + override class func printClassName() { + println("SomeSubClass") + } +} +let someInstance: SomeBaseClass = SomeSubClass() +// someInstance 在编译期是 SomeBaseClass 类型, +// 但是在运行期则是 SomeSubClass 类型 +type(of: someInstance).printClassName() +// 打印“SomeSubClass” +``` + +更多信息可以查看 Swift 标准库里的 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type)。 + +可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。 + +```swift +class AnotherSubClass: SomeBaseClass { + let string: String + required init(string: String) { + self.string = string + } + override class func printClassName() { + print("AnotherSubClass") + } +} +let metatype: AnotherSubClass.Type = AnotherSubClass.self +let anotherInstance = metatype.init(string: "some string") +``` + +> 元类型语法 +> + +#### metatype-type {#metatype-type} +> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol** + +## 自身类型 + +`Self` 类型不是具体的类型,而是让你更方便的引用当前类型,不需要重复或者知道该类的名字。 + +在协议声明或者协议成员声明时,`Self` 类型引用的是最终遵循该协议的类型。 + +在结构体,类或者枚举值声明时,`Self` 类型引用的是声明的类型。在某个类型成员声明时,`Self` 类型引用的是该类型。在类成员声明时,`Self` 可以在方法的返回值和方法体中使用,但不能在其他上下文中使用。举个例子,下面的代码演示了返回值是 `Self` 的实例方法 `f` 。 + +```swift +class Superclass { + func f() -> Self { return self } +} +let x = Superclass() +print(type(of: x.f())) +// 打印 "Superclass" + +class Subclass: Superclass { } +let y = Subclass() +print(type(of: y.f())) +// 打印 "Subclass" + +let z: Superclass = Subclass() +print(type(of: z.f())) +// 打印 "Subclass" +``` + +上面例子的最后一部分表明 `Self` 引用的是值 `z` 的运行时类型 `Subclass` ,而不是变量本身的编译时类型 `Superclass` 。 + +在嵌套类型声明时,`Self` 类型引用的是最内层声明的类型。 + +`Self` 类型引用的类型和 Swift 标准库中 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type) 函数的结果一样。使用 `Self.someStaticMember` 访问当前类型中的成员和使用 `type(of: self).someStaticMember` 是一样的。 + +> 自身类型语法 + +#### self-type{#self-type} + +> *自身类型* → **Self** + +## 类型继承子句 + +*类型继承子句*被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句开始于冒号 `:`,其后是类型标识符列表。 + +类可以继承自单个超类,并遵循任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要遵循的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.md)。 + +其它命名型类型只能继承自或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。 + +枚举定义中的类型继承子句可以是一系列协议,或者是指定单一的命名类型,此时枚举为其用例分配原始值。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.md#raw_values)。 + +> 类型继承子句语法 +> + +#### type_inheritance_clause {#type-inheritance-clause} +> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list) +> + +#### type-inheritance-list {#type-inheritance-list} +> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list) +> + + +## 类型推断 +Swift 广泛使用*类型推断*,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary`。 + +在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0` 中 `x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。 + +在 Swift 中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量 `eFloat` 上的显式类型注解(`: Float`)将导致数字字面量 `2.71828` 的类型是 `Float` 而非 `Double`。 + +```swift +let e = 2.71828 // e 的类型会被推断为 Double +let eFloat: Float = 2.71828 // eFloat 的类型为 Float +``` + +Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。