From d6862fa4dc459c11aab5932028d4d5c26fe5b6f0 Mon Sep 17 00:00:00 2001 From: Nemocdz Date: Fri, 28 Jun 2019 00:54:02 +0800 Subject: [PATCH] update Links --- source/chapter2/01_The_Basics.md | 1542 +++++----- source/chapter2/02_Basic_Operators.md | 958 +++---- source/chapter2/03_Strings_and_Characters.md | 4 +- source/chapter2/04_Collection_Types.md | 1292 ++++----- source/chapter2/05_Control_Flow.md | 14 +- source/chapter2/06_Functions.md | 2 +- source/chapter2/07_Closures.md | 16 +- source/chapter2/08_Enumerations.md | 12 +- source/chapter2/10_Properties.md | 10 +- source/chapter2/11_Methods.md | 8 +- source/chapter2/13_Inheritance.md | 442 +-- source/chapter2/14_Initialization.md | 24 +- source/chapter2/15_Deinitialization.md | 2 +- source/chapter2/16_Optional_Chaining.md | 6 +- source/chapter2/18_Type_Casting.md | 2 +- source/chapter2/19_Nested_Types.md | 170 +- source/chapter2/21_Protocols.md | 8 +- source/chapter2/22_Generics.md | 6 +- .../23_Automatic_Reference_Counting.md | 1120 ++++---- source/chapter2/25_Access_Control.md | 8 +- source/chapter2/26_Advanced_Operators.md | 12 +- source/chapter3/03_Types.md | 2 +- source/chapter3/04_Expressions.md | 2496 ++++++++--------- source/chapter3/05_Statements.md | 1816 ++++++------ source/chapter3/06_Declarations.md | 10 +- .../09_Generic_Parameters_and_Arguments.md | 272 +- 26 files changed, 5127 insertions(+), 5127 deletions(-) diff --git a/source/chapter2/01_The_Basics.md b/source/chapter2/01_The_Basics.md index 41c72d16..ed981656 100755 --- a/source/chapter2/01_The_Basics.md +++ b/source/chapter2/01_The_Basics.md @@ -1,771 +1,771 @@ -# 基础部分 - -Swift 是一门开发 iOS, macOS, watchOS 和 tvOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。 - -Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int` 表示整型值; `Double` 和 `Float` 表示浮点型值; `Bool` 是布尔型值;`String` 是文本型数据。 Swift 还提供了三个基本的集合类型,`Array`、`Set` 和 `Dictionary` ,详见[集合类型](./04_Collection_Types.md)。 - -就像 C 语言一样,Swift 使用变量来进行存储并通过变量名来关联值。在 Swift 中,广泛的使用着值不可变的变量,它们就是常量,而且比 C 语言的常量更强大。在 Swift 中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。 - -除了我们熟悉的类型,Swift 还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。 - -Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示 “那儿有一个值,并且它等于 *x* ” 或者 “那儿没有值” 。可选有点像在 Objective-C 中使用 `nil` ,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 `nil` 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。 - -Swift 是一门*类型安全*的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码需要一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码需要一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。类型安全可以帮助你在开发阶段尽早发现并修正错误。 - -## 常量和变量 {#constants-and-variables} - -常量和变量把一个名字(比如 `maximumNumberOfLoginAttempts` 或者 `welcomeMessage` )和一个指定类型的值(比如数字 `10` 或者字符串 `"Hello"` )关联起来。*常量*的值一旦设定就不能改变,而*变量*的值可以随意更改。 - -### 声明常量和变量 {#declaring} - -常量和变量必须在使用前声明,用 `let` 来声明常量,用 `var` 来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数: - -```swift -let maximumNumberOfLoginAttempts = 10 -var currentLoginAttempt = 0 -``` - -这两行代码可以被理解为: - -“声明一个名字是 `maximumNumberOfLoginAttempts` 的新常量,并给它一个值 `10` 。然后,声明一个名字是 `currentLoginAttempt` 的变量并将它的值初始化为 `0` 。” - -在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。 - -你可以在一行中声明多个常量或者多个变量,用逗号隔开: - -```swift -var x = 0.0, y = 0.0, z = 0.0 -``` - -> 注意 -> -> 如果你的代码中有不需要改变的值,请使用 `let` 关键字将它声明为常量。只将需要改变的值声明为变量。 - -### 类型注解 {#type-annotations} - -当你声明常量或者变量的时候可以加上*类型注解(type annotation)*,说明常量或者变量中要存储的值的类型。如果要添加类型注解,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。 - -这个例子给 `welcomeMessage` 变量添加了类型注解,表示这个变量可以存储 `String` 类型的值: - -```swift -var welcomeMessage: String -``` - -声明中的冒号代表着*“是...类型”*,所以这行代码可以被理解为: - -“声明一个类型为 `String` ,名字为 `welcomeMessage` 的变量。” - -“类型为 `String` ”的意思是“可以存储任意 `String` 类型的值。” - -`welcomeMessage` 变量现在可以被设置成任意字符串: - -```swift -welcomeMessage = "Hello" -``` - -你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型注解: - -```swift -var red, green, blue: Double -``` - -> 注意 -> -> 一般来说你很少需要写类型注解。如果你在声明常量或者变量的时候赋了一个初始值,Swift 可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type-safety-and-type-inference)。在上面的例子中,没有给 `welcomeMessage` 赋初始值,所以变量 `welcomeMessage` 的类型是通过一个类型注解指定的,而不是通过初始值推断的。 - -### 常量和变量的命名 {#naming} - -常量和变量名可以包含任何字符,包括 Unicode 字符: - -```swift -let π = 3.14159 -let 你好 = "你好世界" -let 🐶🐮 = "dogcow" -``` - -常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。 - -一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。 - -> 注意 -> -> 如果你需要使用与 Swift 保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。 - -你可以更改现有的变量值为其他同类型的值,在下面的例子中,`friendlyWelcome` 的值从 `"Hello!"` 改为了 `"Bonjour!"`: - -```swift -var friendlyWelcome = "Hello!" -friendlyWelcome = "Bonjour!" -// friendlyWelcome 现在是 "Bonjour!" -``` - -与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错: - -```swift -let languageName = "Swift" -languageName = "Swift++" -// 这会报编译时错误 - languageName 不可改变 -``` - -### 输出常量和变量 {#printing} - -你可以用 `print(_:separator:terminator:)` 函数来输出当前常量或变量的值: - -```swift -print(friendlyWelcome) -// 输出“Bonjour!” -``` - -`print(_:separator:terminator:)` 是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)` 将会输出内容到“console”面板上。`separator` 和 `terminator` 参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 `terminator` 参数--例如,`print(someValue, terminator:"")` 。关于参数默认值的更多信息,请参考[默认参数值](./06_Functions.md#default_parameter_values)。 - -Swift 用*字符串插值(string interpolation)*的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义: - -```swift -print("The current value of friendlyWelcome is \(friendlyWelcome)") -// 输出“The current value of friendlyWelcome is Bonjour!” -``` - -> 注意 -> -> 字符串插值所有可用的选项,请参考[字符串插值](./03_Strings_and_Characters.md#string_interpolation)。 - -## 注释 {#comments} - -请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。 - -Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜杠(`//`)作为起始标记: - -```swift -// 这是一个注释 -``` - -你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号(`/*`),终止标记为一个星号后跟随单个正斜杠(`*/`): - -```swift -/* 这也是一个注释, -但是是多行的 */ -``` - -与 C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记: - -```swift -/* 这是第一个多行注释的开头 -/* 这是第二个被嵌套的多行注释 */ -这是第一个多行注释的结尾 */ -``` - -通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。 - -## 分号 {#semicolons} - -与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号(`;`),当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句: - -```swift -let cat = "🐱"; print(cat) -// 输出“🐱” -``` - -## 整数 {#integers} - -整数就是没有小数部分的数字,比如 `42` 和 `-23` 。整数可以是 `有符号`(正、负、零)或者 `无符号`(正、零)。 - -Swift 提供了8、16、32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是 `UInt8`,32位有符号整数类型是 `Int32` 。就像 Swift 的其他类型一样,整数类型采用大写命名法。 - -### 整数范围 {#integer-bounds} - -你可以访问不同整数类型的 `min` 和 `max` 属性来获取对应类型的最小值和最大值: - -```swift -let minValue = UInt8.min // minValue 为 0,是 UInt8 类型 -let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型 -``` - -`min` 和 `max` 所传回值的类型,正是其所对的整数类型(如上例 UInt8, 所传回的类型是 UInt8),可用在表达式中相同类型值旁。 - -### Int {#Int} - -一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型 `Int`,长度与当前平台的原生字长相同: - -* 在32位平台上,`Int` 和 `Int32` 长度相同。 -* 在64位平台上,`Int` 和 `Int64` 长度相同。 - -除非你需要特定长度的整数,一般来说使用 `Int` 就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int` 可以存储的整数范围也可以达到 `-2,147,483,648` ~ `2,147,483,647`,大多数时候这已经足够大了。 - -### UInt {#UInt} - -Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台的原生字长相同: - -* 在32位平台上,`UInt` 和 `UInt32` 长度相同。 -* 在64位平台上,`UInt` 和 `UInt64` 长度相同。 - -> 注意 -> -> 尽量不要使用 `UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用 `Int`,即使你要存储的值已知是非负的。统一使用 `Int` 可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考[类型安全和类型推断](#type_safety_and_type_inference)。 - -## 浮点数 {#floating-point-numbers} - -浮点数是有小数部分的数字,比如 `3.14159`、`0.1` 和 `-273.15`。 - -浮点类型比整数类型表示的范围更大,可以存储比 `Int` 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型: - -* `Double` 表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。 -* `Float` 表示32位浮点数。精度要求不高的话可以使用此类型。 - -> 注意 -> -> `Double` 精确度很高,至少有15位数字,而 `Float` 只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 `Double`。 - -## 类型安全和类型推断 {#type-safety-and-type-inference} - -Swift 是一个*类型安全(type safe)*的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 `String`,你绝对不可能不小心传进去一个 `Int`。 - -由于 Swift 是类型安全的,所以它会在编译你的代码时进行*类型检查(type checks)*,并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。 - -当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用*类型推断(type inference)*来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。 - -因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。 - -当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 `42` 和 `3.14159` 。) - -例如,如果你给一个新常量赋值 `42` 并且没有标明类型,Swift 可以推断出常量类型是 `Int` ,因为你给它赋的初始值看起来像一个整数: - -```swift -let meaningOfLife = 42 -// meaningOfLife 会被推测为 Int 类型 -``` - -同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 `Double`: - -```swift -let pi = 3.14159 -// pi 会被推测为 Double 类型 -``` - -当推断浮点数的类型时,Swift 总是会选择 `Double` 而不是 `Float`。 - -如果表达式中同时出现了整数和浮点数,会被推断为 `Double` 类型: - -```swift -let anotherPi = 3 + 0.14159 -// anotherPi 会被推测为 Double 类型 -``` - -原始值 `3` 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 `Double` 类型。 - -## 数值型字面量 {#numeric-literals} - -整数字面量可以被写作: - -* 一个*十进制*数,没有前缀 -* 一个*二进制*数,前缀是 `0b` -* 一个*八进制*数,前缀是 `0o` -* 一个*十六进制*数,前缀是 `0x` - -下面的所有整数字面量的十进制值都是 `17`: - -```swift -let decimalInteger = 17 -let binaryInteger = 0b10001 // 二进制的17 -let octalInteger = 0o21 // 八进制的17 -let hexadecimalInteger = 0x11 // 十六进制的17 -``` - -浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 `0x` )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 `e` 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 `p` 来指定。 - -如果一个十进制数的指数为 `exp`,那这个数相当于基数和10^exp 的乘积: - -* `1.25e2` 表示 1.25 × 10^2,等于 `125.0`。 -* `1.25e-2` 表示 1.25 × 10^-2,等于 `0.0125`。 - -如果一个十六进制数的指数为 `exp`,那这个数相当于基数和2^exp 的乘积: - -* `0xFp2` 表示 15 × 2^2,等于 `60.0`。 -* `0xFp-2` 表示 15 × 2^-2,等于 `3.75`。 - -下面的这些浮点字面量都等于十进制的 `12.1875`: - -```swift -let decimalDouble = 12.1875 -let exponentDouble = 1.21875e1 -let hexadecimalDouble = 0xC.3p0 -``` - -数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量: - -```swift -let paddedDouble = 000123.456 -let oneMillion = 1_000_000 -let justOverOneMillion = 1_000_000.000_000_1 -``` - -## 数值型类型转换 {#numeric-type-conversion} - -通常来讲,即使代码中的整数常量和变量已知非负,也请使用 `Int` 类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。 - -只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。 - -### 整数转换 {#integer-conversion} - -不同整数类型的变量和常量可以存储不同范围的数字。`Int8` 类型的常量或者变量可以存储的数字范围是 `-128`~`127`,而 `UInt8` 类型的常量或者变量能存储的数字范围是 `0`~`255`。如果数字超出了常量或者变量可存储的范围,编译的时候会报错: - -```swift -let cannotBeNegative: UInt8 = -1 -// UInt8 类型不能存储负数,所以会报错 -let tooBig: Int8 = Int8.max + 1 -// Int8 类型不能存储超过最大值的数,所以会报错 -``` - -由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。 - -要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量 `twoThousand` 是 `UInt16` 类型,然而常量 `one` 是 `UInt8` 类型。它们不能直接相加,因为它们类型不同。所以要调用 `UInt16(one)` 来创建一个新的 `UInt16` 数字并用 `one` 的值来初始化,然后使用这个新数字来计算: - -```swift -let twoThousand: UInt16 = 2_000 -let one: UInt8 = 1 -let twoThousandAndOne = twoThousand + UInt16(one) -``` - -现在两个数字的类型都是 `UInt16`,可以进行相加。目标常量 `twoThousandAndOne` 的类型被推断为 `UInt16`,因为它是两个 `UInt16` 值的和。 - -`SomeType(ofInitialValue)` 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16` 有一个构造器,可以接受一个 `UInt8` 类型的值,所以这个构造器可以用现有的 `UInt8` 来创建一个新的 `UInt16`。注意,你并不能传入任意类型的值,只能传入 `UInt16` 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考[扩展](./20_Extensions.md)。 - -### 整数和浮点数转换 {#integer-and-floating-point-conversion} - -整数和浮点数的转换必须显式指定类型: - -```swift -let three = 3 -let pointOneFourOneFiveNine = 0.14159 -let pi = Double(three) + pointOneFourOneFiveNine -// pi 等于 3.14159,所以被推测为 Double 类型 -``` - -这个例子中,常量 `three` 的值被用来创建一个 `Double` 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。 - -浮点数到整数的反向转换同样行,整数类型可以用 `Double` 或者 `Float` 类型来初始化: - -```swift -let integerPi = Int(pi) -// integerPi 等于 3,所以被推测为 Int 类型 -``` - -当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 `4.75` 会变成 `4`,`-3.9` 会变成 `-3`。 - -> 注意 -> -> 结合数字类常量和变量不同于结合数字类字面量。字面量 `3` 可以直接和字面量 `0.14159` 相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。 - -## 类型别名 {#type-aliases} - -*类型别名(type aliases)*就是给现有类型定义另一个名字。你可以使用 `typealias` 关键字来定义类型别名。 - -当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据: - -```swift -typealias AudioSample = UInt16 -``` - -定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名: - -```swift -var maxAmplitudeFound = AudioSample.min -// maxAmplitudeFound 现在是 0 -``` - -本例中,`AudioSample` 被定义为 `UInt16` 的一个别名。因为它是别名,`AudioSample.min` 实际上是 `UInt16.min`,所以会给 `maxAmplitudeFound` 赋一个初值 `0`。 - -## 布尔值 {#booleans} - -Swift 有一个基本的*布尔(Boolean)类型*,叫做 `Bool`。布尔值指*逻辑*上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true` 和 `false`: - -```swift -let orangesAreOrange = true -let turnipsAreDelicious = false -``` - -`orangesAreOrange` 和 `turnipsAreDelicious` 的类型会被推断为 `Bool`,因为它们的初值是布尔字面量。就像之前提到的 `Int` 和 `Double` 一样,如果你创建变量的时候给它们赋值 `true` 或者 `false`,那你不需要将常量或者变量声明为 `Bool` 类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。 - -当你编写条件语句比如 `if` 语句的时候,布尔值非常有用: - -```swift -if turnipsAreDelicious { - print("Mmm, tasty turnips!") -} else { - print("Eww, turnips are horrible.") -} -// 输出“Eww, turnips are horrible.” -``` - -条件语句,例如 `if`,请参考[控制流](./05_Control_Flow.md)。 - -如果你在需要使用 `Bool` 类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误: - -```swift -let i = 1 -if i { - // 这个例子不会通过编译,会报错 -} -``` - -然而,下面的例子是合法的: - -```swift -let i = 1 -if i == 1 { - // 这个例子会编译成功 -} -``` - -`i == 1` 的比较结果是 `Bool` 类型,所以第二个例子可以通过类型检查。类似 `i == 1` 这样的比较,请参考[基本操作符](./05_Control_Flow.md)。 - -和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。 - -## 元组 {#tuples} - -*元组(tuples)*把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。 - -下面这个例子中,`(404, "Not Found")` 是一个描述 *HTTP 状态码(HTTP status code)*的元组。HTTP 状态码是当你请求网页的时候 web 服务器返回的一个特殊值。如果你请求的网页不存在就会返回一个 `404 Not Found` 状态码。 - -```swift -let http404Error = (404, "Not Found") -// http404Error 的类型是 (Int, String),值是 (404, "Not Found") -``` - -`(404, "Not Found")` 元组把一个 `Int` 值和一个 `String` 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 `(Int, String)` 的元组”。 - -你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为 `(Int, Int, Int)` 或者 `(String, Bool)` 或者其他任何你想要的组合的元组。 - -你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了: - -```swift -let (statusCode, statusMessage) = http404Error -print("The status code is \(statusCode)") -// 输出“The status code is 404” -print("The status message is \(statusMessage)") -// 输出“The status message is Not Found” -``` - -如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(`_`)标记: - -```swift -let (justTheStatusCode, _) = http404Error -print("The status code is \(justTheStatusCode)") -// 输出“The status code is 404” -``` - -此外,你还可以通过下标来访问元组中的单个元素,下标从零开始: - -```swift -print("The status code is \(http404Error.0)") -// 输出“The status code is 404” -print("The status message is \(http404Error.1)") -// 输出“The status message is Not Found” -``` - -你可以在定义元组的时候给单个元素命名: - -```swift -let http200Status = (statusCode: 200, description: "OK") -``` - -给元组中的元素命名后,你可以通过名字来获取这些元素的值: - -```swift -print("The status code is \(http200Status.statusCode)") -// 输出“The status code is 200” -print("The status message is \(http200Status.description)") -// 输出“The status message is OK” -``` - -作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 `(Int, String)` 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.md#Function_Parameters_and_Return_Values)。 - -> 注意 -> -> 元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考[类和结构体](./09_Classes_and_Structures.md)。 - -## 可选类型 {#optionals} - -使用*可选类型(optionals)*来处理值可能缺失的情况。可选类型表示两种可能: -或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。 - -> 注意 -> -> C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 `nil`,`nil` 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 `NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示*任意类型*的值缺失,并不需要一个特殊值。 - -来看一个例子。Swift 的 `Int` 类型有一种构造器,作用是将一个 `String` 值转换成一个 `Int` 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 `"123"` 可以被转换成数字 `123` ,但是字符串 `"hello, world"` 不行。 - -下面的例子使用这种构造器来尝试将一个 `String` 转换成 `Int`: - -```swift -let possibleNumber = "123" -let convertedNumber = Int(possibleNumber) -// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int" -``` - -因为该构造器可能会失败,所以它返回一个*可选类型*(optional)`Int`,而不是一个 `Int`。一个可选的 `Int` 被写作 `Int?` 而不是 `Int`。问号暗示包含的值是可选类型,也就是说可能包含 `Int` 值也可能*不包含值*。(不能包含其他任何值比如 `Bool` 值或者 `String` 值。只能是 `Int` 或者什么都没有。) - -### nil {#nil} - -你可以给可选变量赋值为 `nil` 来表示它没有值: - -```swift -var serverResponseCode: Int? = 404 -// serverResponseCode 包含一个可选的 Int 值 404 -serverResponseCode = nil -// serverResponseCode 现在不包含值 -``` - -> 注意 -> -> `nil` 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。 - -如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 `nil`: - -```swift -var surveyAnswer: String? -// surveyAnswer 被自动设置为 nil -``` - -> 注意 -> -> Swift 的 `nil` 和 Objective-C 中的 `nil` 并不一样。在 Objective-C 中,`nil` 是一个指向不存在对象的指针。在 Swift 中,`nil` 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 `nil`,不只是对象类型。 - -### if 语句以及强制解析 {#if} - -你可以使用 `if` 语句和 `nil` 比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。 - -如果可选类型有值,它将不等于 `nil`: - -```swift -if convertedNumber != nil { - print("convertedNumber contains some integer value.") -} -// 输出“convertedNumber contains some integer value.” -``` - -当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的*强制解析(forced unwrapping)*: - -```swift -if convertedNumber != nil { - print("convertedNumber has an integer value of \(convertedNumber!).") -} -// 输出“convertedNumber has an integer value of 123.” -``` - -更多关于 `if` 语句的内容,请参考[控制流](./05_Control_Flow.md)。 - -> 注意 -> -> 使用 `!` 来获取一个不存在的可选值会导致运行时错误。使用 `!` 来强制解析值之前,一定要确定可选包含一个非 `nil` 的值。 - -### 可选绑定 {#optional-binding} - -使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 `if` 和 `while` 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if` 和 `while` 语句,请参考[控制流](./05_Control_Flow.md)。 - -像下面这样在 `if` 语句中写一个可选绑定: - -```swift -if let constantName = someOptional { - statements -} -``` - -你可以像上面这样使用可选绑定来重写 在[可选类型](./01_The_Basics.md#optionals)举出的 `possibleNumber` 例子: - -```swift -if let actualNumber = Int(possibleNumber) { - print("\'\(possibleNumber)\' has an integer value of \(actualNumber)") -} else { - print("\'\(possibleNumber)\' could not be converted to an integer") -} -// 输出“'123' has an integer value of 123” -``` - -这段代码可以被理解为: - -“如果 `Int(possibleNumber)` 返回的可选 `Int` 包含一个值,创建一个叫做 `actualNumber` 的新常量并将可选包含的值赋给它。” - -如果转换成功,`actualNumber` 常量可以在 `if` 语句的第一个分支中使用。它已经被可选类型 *包含的* 值初始化过,所以不需要再使用 `!` 后缀来获取它的值。在这个例子中,`actualNumber` 只被用来输出转换结果。 - -你可以在可选绑定中使用常量和变量。如果你想在 `if` 语句的第一个分支中操作 `actualNumber` 的值,你可以改成 `if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。 - -你可以包含多个可选绑定或多个布尔条件在一个 `if` 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 `nil`,或者任意一个布尔条件为 `false`,则整个 `if` 条件判断为 `false`,这时你就需要使用嵌套 `if` 条件语句来处理,如下所示: - -```swift -if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 { - print("\(firstNumber) < \(secondNumber) < 100") -} -// 输出“4 < 42 < 100” - -if let firstNumber = Int("4") { - if let secondNumber = Int("42") { - if firstNumber < secondNumber && secondNumber < 100 { - print("\(firstNumber) < \(secondNumber) < 100") - } - } -} -// 输出“4 < 42 < 100” -``` - -> 注意 -> -> 在 `if` 条件语句中使用常量和变量来创建一个可选绑定,仅在 `if` 语句的句中(`body`)中才能获取到值。相反,在 `guard` 语句中使用常量和变量来创建一个可选绑定,仅在 `guard` 语句外且在语句后才能获取到值,请参考[提前退出](./05_Control_Flow.md#early_exit)。 - -### 隐式解析可选类型 {#implicityly-unwrapped-optionals} - -如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 `if` 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。 - -有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型_总会_有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。 - -这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(`String?`)改成感叹号(`String!`)来声明一个隐式解析可选类型。 - -当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考[无主引用以及隐式解析可选属性](./23_Automatic_Reference_Counting.md#unowned_references_and_implicitly_unwrapped_optional_properties)。 - -一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 `String` 和隐式解析可选类型 `String` 之间的区别: - -```swift -let possibleString: String? = "An optional string." -let forcedString: String = possibleString! // 需要感叹号来获取值 - -let assumedString: String! = "An implicitly unwrapped optional string." -let implicitString: String = assumedString // 不需要感叹号 -``` - -你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。 - -> 注意 -> -> 如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。 - -你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值: - -```swift -if assumedString != nil { - print(assumedString!) -} -// 输出“An implicitly unwrapped optional string.” -``` - -你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值: - -```swift -if let definiteString = assumedString { - print(definiteString) -} -// 输出“An implicitly unwrapped optional string.” -``` - -> 注意 -> -> 如果一个变量之后可能变成 `nil` 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 `nil` 的话,请使用普通可选类型。 - -## 错误处理 {#error-handling} - -你可以使用 *错误处理(error handling)* 来应对程序执行中可能会遇到的错误条件。 - -相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。 - -当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。 - -```swift -func canThrowAnError() throws { - // 这个函数有可能抛出错误 -} -``` - -一个函数可以通过在声明中添加 `throws` 关键词来抛出错误消息。当你的函数能抛出错误消息时,你应该在表达式中前置 `try` 关键词。 - -```swift -do { - try canThrowAnError() - // 没有错误消息抛出 -} catch { - // 有一个错误消息抛出 -} -``` - -一个 `do` 语句创建了一个新的包含作用域,使得错误能被传播到一个或多个 `catch` 从句。 - -这里有一个错误处理如何用来应对不同错误条件的例子。 - -```swift -func makeASandwich() throws { - // ... -} - -do { - try makeASandwich() - eatASandwich() -} catch SandwichError.outOfCleanDishes { - washDishes() -} catch SandwichError.missingIngredients(let ingredients) { - buyGroceries(ingredients) -} -``` - -在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 `makeASandwich()` 抛出错误,函数调用被包裹在 `try` 表达式中。将函数包裹在一个 `do` 语句中,任何被抛出的错误会被传播到提供的 `catch` 从句中。 - -如果没有错误被抛出,`eatASandwich()` 函数会被调用。如果一个匹配 `SandwichError.outOfCleanDishes` 的错误被抛出,`washDishes()` 函数会被调用。如果一个匹配 `SandwichError.missingIngredients` 的错误被抛出,`buyGroceries(_:)` 函数会被调用,并且使用 `catch` 所捕捉到的关联值 `[String]` 作为参数。 - -抛出,捕捉,以及传播错误会在[错误处理](./17_Error_Handling.md)章节详细说明。 - -## 断言和先决条件 {#assertions-and-preconditions} - -断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true(真),则代码像往常一样继续执行。如果布尔条件评估结果为 false(假),程序的当前状态是无效的,则代码执行结束,应用程序中止。 - -你使用断言和先决条件来表达你所做的假设和你在编码时候的期望。你可以将这些包含在你的代码中。断言帮助你在开发阶段找到错误和不正确的假设,先决条件帮助你在生产环境中探测到存在的问题。 - -除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的[错误处理](./17_Error_Handling.md)不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。 - -使用断言和先决条件不是一个能够避免出现程序出现无效状态的编码方法。然而,如果一个无效状态程序产生了,断言和先决条件可以强制检查你的数据和程序状态,使得你的程序可预测的中止(译者:不是系统强制的,被动的中止),并帮助使这个问题更容易调试。一旦探测到无效的状态,执行则被中止,防止无效的状态导致的进一步对于系统的伤害。 - -断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。 - -### 使用断言进行调试 {#debugging-with-assertions} - -你可以调用 Swift 标准库的 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: - -```swift -let age = -3 -assert(age >= 0, "A person's age cannot be less than zero") -// 因为 age < 0,所以断言会触发 -``` - -在这个例子中,只有 `age >= 0` 为 `true` 时,即 `age` 的值非负的时候,代码才会继续执行。如果 `age` 的值是负数,就像代码中那样,`age >= 0` 为 `false`,断言被触发,终止应用。 - -如果不需要断言信息,可以就像这样忽略掉: - -```swift -assert(age >= 0) -``` - -如果代码已经检查了条件,你可以使用 `assertionFailure(_:file:line:)` 函数来表明断言失败了,例如: - -```swift -if age > 10 { - print("You can ride the roller-coaster or the ferris wheel.") -} else if age > 0 { - print("You can ride the ferris wheel.") -} else { - assertionFailure("A person's age can't be less than zero.") -} -``` - -### 强制执行先决条件 {#enforcing-preconditions} - -当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。 - -你可以使用全局 `precondition(_:_:file:line:)` 函数来写一个先决条件。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: - -```swift -// 在一个下标的实现里... -precondition(index > 0, "Index must be greater than zero.") -``` - -你可以调用 `preconditionFailure(_:file:line:)` 方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。 - -> 注意 -> -> 如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。然而,`fatalError(_:file:line:)` 函数总是中断执行,无论你怎么进行优化设定。 -> -> 你能使用 `fatalError(_:file:line:)` 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError("Unimplemented")作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。 +# 基础部分 + +Swift 是一门开发 iOS, macOS, watchOS 和 tvOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。 + +Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int` 表示整型值; `Double` 和 `Float` 表示浮点型值; `Bool` 是布尔型值;`String` 是文本型数据。 Swift 还提供了三个基本的集合类型,`Array`、`Set` 和 `Dictionary` ,详见 [集合类型](./04_Collection_Types.md)。 + +就像 C 语言一样,Swift 使用变量来进行存储并通过变量名来关联值。在 Swift 中,广泛的使用着值不可变的变量,它们就是常量,而且比 C 语言的常量更强大。在 Swift 中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。 + +除了我们熟悉的类型,Swift 还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。 + +Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示 “那儿有一个值,并且它等于 *x* ” 或者 “那儿没有值” 。可选有点像在 Objective-C 中使用 `nil` ,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 `nil` 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。 + +Swift 是一门*类型安全*的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码需要一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码需要一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。类型安全可以帮助你在开发阶段尽早发现并修正错误。 + +## 常量和变量 {#constants-and-variables} + +常量和变量把一个名字(比如 `maximumNumberOfLoginAttempts` 或者 `welcomeMessage` )和一个指定类型的值(比如数字 `10` 或者字符串 `"Hello"` )关联起来。*常量*的值一旦设定就不能改变,而*变量*的值可以随意更改。 + +### 声明常量和变量 {#declaring} + +常量和变量必须在使用前声明,用 `let` 来声明常量,用 `var` 来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数: + +```swift +let maximumNumberOfLoginAttempts = 10 +var currentLoginAttempt = 0 +``` + +这两行代码可以被理解为: + +“声明一个名字是 `maximumNumberOfLoginAttempts` 的新常量,并给它一个值 `10` 。然后,声明一个名字是 `currentLoginAttempt` 的变量并将它的值初始化为 `0` 。” + +在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。 + +你可以在一行中声明多个常量或者多个变量,用逗号隔开: + +```swift +var x = 0.0, y = 0.0, z = 0.0 +``` + +> 注意 +> +> 如果你的代码中有不需要改变的值,请使用 `let` 关键字将它声明为常量。只将需要改变的值声明为变量。 + +### 类型注解 {#type-annotations} + +当你声明常量或者变量的时候可以加上*类型注解(type annotation)*,说明常量或者变量中要存储的值的类型。如果要添加类型注解,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。 + +这个例子给 `welcomeMessage` 变量添加了类型注解,表示这个变量可以存储 `String` 类型的值: + +```swift +var welcomeMessage: String +``` + +声明中的冒号代表着*“是...类型”*,所以这行代码可以被理解为: + +“声明一个类型为 `String` ,名字为 `welcomeMessage` 的变量。” + +“类型为 `String` ”的意思是“可以存储任意 `String` 类型的值。” + +`welcomeMessage` 变量现在可以被设置成任意字符串: + +```swift +welcomeMessage = "Hello" +``` + +你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型注解: + +```swift +var red, green, blue: Double +``` + +> 注意 +> +> 一般来说你很少需要写类型注解。如果你在声明常量或者变量的时候赋了一个初始值,Swift 可以推断出这个常量或者变量的类型,请参考 [类型安全和类型推断](#type-safety-and-type-inference)。在上面的例子中,没有给 `welcomeMessage` 赋初始值,所以变量 `welcomeMessage` 的类型是通过一个类型注解指定的,而不是通过初始值推断的。 + +### 常量和变量的命名 {#naming} + +常量和变量名可以包含任何字符,包括 Unicode 字符: + +```swift +let π = 3.14159 +let 你好 = "你好世界" +let 🐶🐮 = "dogcow" +``` + +常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。 + +一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。 + +> 注意 +> +> 如果你需要使用与 Swift 保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。 + +你可以更改现有的变量值为其他同类型的值,在下面的例子中,`friendlyWelcome` 的值从 `"Hello!"` 改为了 `"Bonjour!"`: + +```swift +var friendlyWelcome = "Hello!" +friendlyWelcome = "Bonjour!" +// friendlyWelcome 现在是 "Bonjour!" +``` + +与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错: + +```swift +let languageName = "Swift" +languageName = "Swift++" +// 这会报编译时错误 - languageName 不可改变 +``` + +### 输出常量和变量 {#printing} + +你可以用 `print(_:separator:terminator:)` 函数来输出当前常量或变量的值: + +```swift +print(friendlyWelcome) +// 输出“Bonjour!” +``` + +`print(_:separator:terminator:)` 是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)` 将会输出内容到“console”面板上。`separator` 和 `terminator` 参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 `terminator` 参数--例如,`print(someValue, terminator:"")` 。关于参数默认值的更多信息,请参考 [默认参数值](./06_Functions.md#default_parameter_values)。 + +Swift 用*字符串插值(string interpolation)*的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义: + +```swift +print("The current value of friendlyWelcome is \(friendlyWelcome)") +// 输出“The current value of friendlyWelcome is Bonjour!” +``` + +> 注意 +> +> 字符串插值所有可用的选项,请参考 [字符串插值](./03_Strings_and_Characters.md#string_interpolation)。 + +## 注释 {#comments} + +请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。 + +Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜杠(`//`)作为起始标记: + +```swift +// 这是一个注释 +``` + +你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号(`/*`),终止标记为一个星号后跟随单个正斜杠(`*/`): + +```swift +/* 这也是一个注释, +但是是多行的 */ +``` + +与 C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记: + +```swift +/* 这是第一个多行注释的开头 +/* 这是第二个被嵌套的多行注释 */ +这是第一个多行注释的结尾 */ +``` + +通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。 + +## 分号 {#semicolons} + +与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号(`;`),当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句: + +```swift +let cat = "🐱"; print(cat) +// 输出“🐱” +``` + +## 整数 {#integers} + +整数就是没有小数部分的数字,比如 `42` 和 `-23` 。整数可以是 `有符号`(正、负、零)或者 `无符号`(正、零)。 + +Swift 提供了8、16、32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是 `UInt8`,32位有符号整数类型是 `Int32` 。就像 Swift 的其他类型一样,整数类型采用大写命名法。 + +### 整数范围 {#integer-bounds} + +你可以访问不同整数类型的 `min` 和 `max` 属性来获取对应类型的最小值和最大值: + +```swift +let minValue = UInt8.min // minValue 为 0,是 UInt8 类型 +let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型 +``` + +`min` 和 `max` 所传回值的类型,正是其所对的整数类型(如上例 UInt8, 所传回的类型是 UInt8),可用在表达式中相同类型值旁。 + +### Int {#Int} + +一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型 `Int`,长度与当前平台的原生字长相同: + +* 在32位平台上,`Int` 和 `Int32` 长度相同。 +* 在64位平台上,`Int` 和 `Int64` 长度相同。 + +除非你需要特定长度的整数,一般来说使用 `Int` 就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int` 可以存储的整数范围也可以达到 `-2,147,483,648` ~ `2,147,483,647`,大多数时候这已经足够大了。 + +### UInt {#UInt} + +Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台的原生字长相同: + +* 在32位平台上,`UInt` 和 `UInt32` 长度相同。 +* 在64位平台上,`UInt` 和 `UInt64` 长度相同。 + +> 注意 +> +> 尽量不要使用 `UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用 `Int`,即使你要存储的值已知是非负的。统一使用 `Int` 可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考 [类型安全和类型推断](#type_safety_and_type_inference)。 + +## 浮点数 {#floating-point-numbers} + +浮点数是有小数部分的数字,比如 `3.14159`、`0.1` 和 `-273.15`。 + +浮点类型比整数类型表示的范围更大,可以存储比 `Int` 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型: + +* `Double` 表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。 +* `Float` 表示32位浮点数。精度要求不高的话可以使用此类型。 + +> 注意 +> +> `Double` 精确度很高,至少有15位数字,而 `Float` 只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 `Double`。 + +## 类型安全和类型推断 {#type-safety-and-type-inference} + +Swift 是一个*类型安全(type safe)*的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 `String`,你绝对不可能不小心传进去一个 `Int`。 + +由于 Swift 是类型安全的,所以它会在编译你的代码时进行*类型检查(type checks)*,并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。 + +当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用*类型推断(type inference)*来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。 + +因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。 + +当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 `42` 和 `3.14159` 。) + +例如,如果你给一个新常量赋值 `42` 并且没有标明类型,Swift 可以推断出常量类型是 `Int` ,因为你给它赋的初始值看起来像一个整数: + +```swift +let meaningOfLife = 42 +// meaningOfLife 会被推测为 Int 类型 +``` + +同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 `Double`: + +```swift +let pi = 3.14159 +// pi 会被推测为 Double 类型 +``` + +当推断浮点数的类型时,Swift 总是会选择 `Double` 而不是 `Float`。 + +如果表达式中同时出现了整数和浮点数,会被推断为 `Double` 类型: + +```swift +let anotherPi = 3 + 0.14159 +// anotherPi 会被推测为 Double 类型 +``` + +原始值 `3` 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 `Double` 类型。 + +## 数值型字面量 {#numeric-literals} + +整数字面量可以被写作: + +* 一个*十进制*数,没有前缀 +* 一个*二进制*数,前缀是 `0b` +* 一个*八进制*数,前缀是 `0o` +* 一个*十六进制*数,前缀是 `0x` + +下面的所有整数字面量的十进制值都是 `17`: + +```swift +let decimalInteger = 17 +let binaryInteger = 0b10001 // 二进制的17 +let octalInteger = 0o21 // 八进制的17 +let hexadecimalInteger = 0x11 // 十六进制的17 +``` + +浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 `0x` )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 `e` 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 `p` 来指定。 + +如果一个十进制数的指数为 `exp`,那这个数相当于基数和10^exp 的乘积: + +* `1.25e2` 表示 1.25 × 10^2,等于 `125.0`。 +* `1.25e-2` 表示 1.25 × 10^-2,等于 `0.0125`。 + +如果一个十六进制数的指数为 `exp`,那这个数相当于基数和2^exp 的乘积: + +* `0xFp2` 表示 15 × 2^2,等于 `60.0`。 +* `0xFp-2` 表示 15 × 2^-2,等于 `3.75`。 + +下面的这些浮点字面量都等于十进制的 `12.1875`: + +```swift +let decimalDouble = 12.1875 +let exponentDouble = 1.21875e1 +let hexadecimalDouble = 0xC.3p0 +``` + +数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量: + +```swift +let paddedDouble = 000123.456 +let oneMillion = 1_000_000 +let justOverOneMillion = 1_000_000.000_000_1 +``` + +## 数值型类型转换 {#numeric-type-conversion} + +通常来讲,即使代码中的整数常量和变量已知非负,也请使用 `Int` 类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。 + +只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。 + +### 整数转换 {#integer-conversion} + +不同整数类型的变量和常量可以存储不同范围的数字。`Int8` 类型的常量或者变量可以存储的数字范围是 `-128`~`127`,而 `UInt8` 类型的常量或者变量能存储的数字范围是 `0`~`255`。如果数字超出了常量或者变量可存储的范围,编译的时候会报错: + +```swift +let cannotBeNegative: UInt8 = -1 +// UInt8 类型不能存储负数,所以会报错 +let tooBig: Int8 = Int8.max + 1 +// Int8 类型不能存储超过最大值的数,所以会报错 +``` + +由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。 + +要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量 `twoThousand` 是 `UInt16` 类型,然而常量 `one` 是 `UInt8` 类型。它们不能直接相加,因为它们类型不同。所以要调用 `UInt16(one)` 来创建一个新的 `UInt16` 数字并用 `one` 的值来初始化,然后使用这个新数字来计算: + +```swift +let twoThousand: UInt16 = 2_000 +let one: UInt8 = 1 +let twoThousandAndOne = twoThousand + UInt16(one) +``` + +现在两个数字的类型都是 `UInt16`,可以进行相加。目标常量 `twoThousandAndOne` 的类型被推断为 `UInt16`,因为它是两个 `UInt16` 值的和。 + +`SomeType(ofInitialValue)` 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16` 有一个构造器,可以接受一个 `UInt8` 类型的值,所以这个构造器可以用现有的 `UInt8` 来创建一个新的 `UInt16`。注意,你并不能传入任意类型的值,只能传入 `UInt16` 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考 [扩展](./20_Extensions.md)。 + +### 整数和浮点数转换 {#integer-and-floating-point-conversion} + +整数和浮点数的转换必须显式指定类型: + +```swift +let three = 3 +let pointOneFourOneFiveNine = 0.14159 +let pi = Double(three) + pointOneFourOneFiveNine +// pi 等于 3.14159,所以被推测为 Double 类型 +``` + +这个例子中,常量 `three` 的值被用来创建一个 `Double` 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。 + +浮点数到整数的反向转换同样行,整数类型可以用 `Double` 或者 `Float` 类型来初始化: + +```swift +let integerPi = Int(pi) +// integerPi 等于 3,所以被推测为 Int 类型 +``` + +当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 `4.75` 会变成 `4`,`-3.9` 会变成 `-3`。 + +> 注意 +> +> 结合数字类常量和变量不同于结合数字类字面量。字面量 `3` 可以直接和字面量 `0.14159` 相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。 + +## 类型别名 {#type-aliases} + +*类型别名(type aliases)*就是给现有类型定义另一个名字。你可以使用 `typealias` 关键字来定义类型别名。 + +当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据: + +```swift +typealias AudioSample = UInt16 +``` + +定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名: + +```swift +var maxAmplitudeFound = AudioSample.min +// maxAmplitudeFound 现在是 0 +``` + +本例中,`AudioSample` 被定义为 `UInt16` 的一个别名。因为它是别名,`AudioSample.min` 实际上是 `UInt16.min`,所以会给 `maxAmplitudeFound` 赋一个初值 `0`。 + +## 布尔值 {#booleans} + +Swift 有一个基本的*布尔(Boolean)类型*,叫做 `Bool`。布尔值指*逻辑*上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true` 和 `false`: + +```swift +let orangesAreOrange = true +let turnipsAreDelicious = false +``` + +`orangesAreOrange` 和 `turnipsAreDelicious` 的类型会被推断为 `Bool`,因为它们的初值是布尔字面量。就像之前提到的 `Int` 和 `Double` 一样,如果你创建变量的时候给它们赋值 `true` 或者 `false`,那你不需要将常量或者变量声明为 `Bool` 类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。 + +当你编写条件语句比如 `if` 语句的时候,布尔值非常有用: + +```swift +if turnipsAreDelicious { + print("Mmm, tasty turnips!") +} else { + print("Eww, turnips are horrible.") +} +// 输出“Eww, turnips are horrible.” +``` + +条件语句,例如 `if`,请参考 [控制流](./05_Control_Flow.md)。 + +如果你在需要使用 `Bool` 类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误: + +```swift +let i = 1 +if i { + // 这个例子不会通过编译,会报错 +} +``` + +然而,下面的例子是合法的: + +```swift +let i = 1 +if i == 1 { + // 这个例子会编译成功 +} +``` + +`i == 1` 的比较结果是 `Bool` 类型,所以第二个例子可以通过类型检查。类似 `i == 1` 这样的比较,请参考 [基本操作符](./05_Control_Flow.md)。 + +和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。 + +## 元组 {#tuples} + +*元组(tuples)*把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。 + +下面这个例子中,`(404, "Not Found")` 是一个描述 *HTTP 状态码(HTTP status code)*的元组。HTTP 状态码是当你请求网页的时候 web 服务器返回的一个特殊值。如果你请求的网页不存在就会返回一个 `404 Not Found` 状态码。 + +```swift +let http404Error = (404, "Not Found") +// http404Error 的类型是 (Int, String),值是 (404, "Not Found") +``` + +`(404, "Not Found")` 元组把一个 `Int` 值和一个 `String` 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 `(Int, String)` 的元组”。 + +你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为 `(Int, Int, Int)` 或者 `(String, Bool)` 或者其他任何你想要的组合的元组。 + +你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了: + +```swift +let (statusCode, statusMessage) = http404Error +print("The status code is \(statusCode)") +// 输出“The status code is 404” +print("The status message is \(statusMessage)") +// 输出“The status message is Not Found” +``` + +如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(`_`)标记: + +```swift +let (justTheStatusCode, _) = http404Error +print("The status code is \(justTheStatusCode)") +// 输出“The status code is 404” +``` + +此外,你还可以通过下标来访问元组中的单个元素,下标从零开始: + +```swift +print("The status code is \(http404Error.0)") +// 输出“The status code is 404” +print("The status message is \(http404Error.1)") +// 输出“The status message is Not Found” +``` + +你可以在定义元组的时候给单个元素命名: + +```swift +let http200Status = (statusCode: 200, description: "OK") +``` + +给元组中的元素命名后,你可以通过名字来获取这些元素的值: + +```swift +print("The status code is \(http200Status.statusCode)") +// 输出“The status code is 200” +print("The status message is \(http200Status.description)") +// 输出“The status message is OK” +``` + +作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 `(Int, String)` 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考 [函数参数与返回值](./06_Functions.md#Function_Parameters_and_Return_Values)。 + +> 注意 +> +> 元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考 [类和结构体](./09_Classes_and_Structures.md)。 + +## 可选类型 {#optionals} + +使用*可选类型(optionals)*来处理值可能缺失的情况。可选类型表示两种可能: +或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。 + +> 注意 +> +> C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 `nil`,`nil` 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 `NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示*任意类型*的值缺失,并不需要一个特殊值。 + +来看一个例子。Swift 的 `Int` 类型有一种构造器,作用是将一个 `String` 值转换成一个 `Int` 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 `"123"` 可以被转换成数字 `123` ,但是字符串 `"hello, world"` 不行。 + +下面的例子使用这种构造器来尝试将一个 `String` 转换成 `Int`: + +```swift +let possibleNumber = "123" +let convertedNumber = Int(possibleNumber) +// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int" +``` + +因为该构造器可能会失败,所以它返回一个*可选类型*(optional)`Int`,而不是一个 `Int`。一个可选的 `Int` 被写作 `Int?` 而不是 `Int`。问号暗示包含的值是可选类型,也就是说可能包含 `Int` 值也可能*不包含值*。(不能包含其他任何值比如 `Bool` 值或者 `String` 值。只能是 `Int` 或者什么都没有。) + +### nil {#nil} + +你可以给可选变量赋值为 `nil` 来表示它没有值: + +```swift +var serverResponseCode: Int? = 404 +// serverResponseCode 包含一个可选的 Int 值 404 +serverResponseCode = nil +// serverResponseCode 现在不包含值 +``` + +> 注意 +> +> `nil` 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。 + +如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 `nil`: + +```swift +var surveyAnswer: String? +// surveyAnswer 被自动设置为 nil +``` + +> 注意 +> +> Swift 的 `nil` 和 Objective-C 中的 `nil` 并不一样。在 Objective-C 中,`nil` 是一个指向不存在对象的指针。在 Swift 中,`nil` 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 `nil`,不只是对象类型。 + +### if 语句以及强制解析 {#if} + +你可以使用 `if` 语句和 `nil` 比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。 + +如果可选类型有值,它将不等于 `nil`: + +```swift +if convertedNumber != nil { + print("convertedNumber contains some integer value.") +} +// 输出“convertedNumber contains some integer value.” +``` + +当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的*强制解析(forced unwrapping)*: + +```swift +if convertedNumber != nil { + print("convertedNumber has an integer value of \(convertedNumber!).") +} +// 输出“convertedNumber has an integer value of 123.” +``` + +更多关于 `if` 语句的内容,请参考 [控制流](./05_Control_Flow.md)。 + +> 注意 +> +> 使用 `!` 来获取一个不存在的可选值会导致运行时错误。使用 `!` 来强制解析值之前,一定要确定可选包含一个非 `nil` 的值。 + +### 可选绑定 {#optional-binding} + +使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 `if` 和 `while` 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if` 和 `while` 语句,请参考 [控制流](./05_Control_Flow.md)。 + +像下面这样在 `if` 语句中写一个可选绑定: + +```swift +if let constantName = someOptional { + statements +} +``` + +你可以像上面这样使用可选绑定来重写 在 [可选类型](./01_The_Basics.md#optionals) 举出的 `possibleNumber` 例子: + +```swift +if let actualNumber = Int(possibleNumber) { + print("\'\(possibleNumber)\' has an integer value of \(actualNumber)") +} else { + print("\'\(possibleNumber)\' could not be converted to an integer") +} +// 输出“'123' has an integer value of 123” +``` + +这段代码可以被理解为: + +“如果 `Int(possibleNumber)` 返回的可选 `Int` 包含一个值,创建一个叫做 `actualNumber` 的新常量并将可选包含的值赋给它。” + +如果转换成功,`actualNumber` 常量可以在 `if` 语句的第一个分支中使用。它已经被可选类型 *包含的* 值初始化过,所以不需要再使用 `!` 后缀来获取它的值。在这个例子中,`actualNumber` 只被用来输出转换结果。 + +你可以在可选绑定中使用常量和变量。如果你想在 `if` 语句的第一个分支中操作 `actualNumber` 的值,你可以改成 `if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。 + +你可以包含多个可选绑定或多个布尔条件在一个 `if` 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 `nil`,或者任意一个布尔条件为 `false`,则整个 `if` 条件判断为 `false`,这时你就需要使用嵌套 `if` 条件语句来处理,如下所示: + +```swift +if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 { + print("\(firstNumber) < \(secondNumber) < 100") +} +// 输出“4 < 42 < 100” + +if let firstNumber = Int("4") { + if let secondNumber = Int("42") { + if firstNumber < secondNumber && secondNumber < 100 { + print("\(firstNumber) < \(secondNumber) < 100") + } + } +} +// 输出“4 < 42 < 100” +``` + +> 注意 +> +> 在 `if` 条件语句中使用常量和变量来创建一个可选绑定,仅在 `if` 语句的句中(`body`)中才能获取到值。相反,在 `guard` 语句中使用常量和变量来创建一个可选绑定,仅在 `guard` 语句外且在语句后才能获取到值,请参考 [提前退出](./05_Control_Flow.md#early_exit)。 + +### 隐式解析可选类型 {#implicityly-unwrapped-optionals} + +如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 `if` 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。 + +有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型_总会_有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。 + +这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(`String?`)改成感叹号(`String!`)来声明一个隐式解析可选类型。 + +当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考 [无主引用以及隐式解析可选属性](./23_Automatic_Reference_Counting.md#unowned_references_and_implicitly_unwrapped_optional_properties)。 + +一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 `String` 和隐式解析可选类型 `String` 之间的区别: + +```swift +let possibleString: String? = "An optional string." +let forcedString: String = possibleString! // 需要感叹号来获取值 + +let assumedString: String! = "An implicitly unwrapped optional string." +let implicitString: String = assumedString // 不需要感叹号 +``` + +你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。 + +> 注意 +> +> 如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。 + +你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值: + +```swift +if assumedString != nil { + print(assumedString!) +} +// 输出“An implicitly unwrapped optional string.” +``` + +你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值: + +```swift +if let definiteString = assumedString { + print(definiteString) +} +// 输出“An implicitly unwrapped optional string.” +``` + +> 注意 +> +> 如果一个变量之后可能变成 `nil` 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 `nil` 的话,请使用普通可选类型。 + +## 错误处理 {#error-handling} + +你可以使用 *错误处理(error handling)* 来应对程序执行中可能会遇到的错误条件。 + +相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。 + +当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。 + +```swift +func canThrowAnError() throws { + // 这个函数有可能抛出错误 +} +``` + +一个函数可以通过在声明中添加 `throws` 关键词来抛出错误消息。当你的函数能抛出错误消息时,你应该在表达式中前置 `try` 关键词。 + +```swift +do { + try canThrowAnError() + // 没有错误消息抛出 +} catch { + // 有一个错误消息抛出 +} +``` + +一个 `do` 语句创建了一个新的包含作用域,使得错误能被传播到一个或多个 `catch` 从句。 + +这里有一个错误处理如何用来应对不同错误条件的例子。 + +```swift +func makeASandwich() throws { + // ... +} + +do { + try makeASandwich() + eatASandwich() +} catch SandwichError.outOfCleanDishes { + washDishes() +} catch SandwichError.missingIngredients(let ingredients) { + buyGroceries(ingredients) +} +``` + +在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 `makeASandwich()` 抛出错误,函数调用被包裹在 `try` 表达式中。将函数包裹在一个 `do` 语句中,任何被抛出的错误会被传播到提供的 `catch` 从句中。 + +如果没有错误被抛出,`eatASandwich()` 函数会被调用。如果一个匹配 `SandwichError.outOfCleanDishes` 的错误被抛出,`washDishes()` 函数会被调用。如果一个匹配 `SandwichError.missingIngredients` 的错误被抛出,`buyGroceries(_:)` 函数会被调用,并且使用 `catch` 所捕捉到的关联值 `[String]` 作为参数。 + +抛出,捕捉,以及传播错误会在 [错误处理](./17_Error_Handling.md) 章节详细说明。 + +## 断言和先决条件 {#assertions-and-preconditions} + +断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true(真),则代码像往常一样继续执行。如果布尔条件评估结果为 false(假),程序的当前状态是无效的,则代码执行结束,应用程序中止。 + +你使用断言和先决条件来表达你所做的假设和你在编码时候的期望。你可以将这些包含在你的代码中。断言帮助你在开发阶段找到错误和不正确的假设,先决条件帮助你在生产环境中探测到存在的问题。 + +除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的 [错误处理](./17_Error_Handling.md) 不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。 + +使用断言和先决条件不是一个能够避免出现程序出现无效状态的编码方法。然而,如果一个无效状态程序产生了,断言和先决条件可以强制检查你的数据和程序状态,使得你的程序可预测的中止(译者:不是系统强制的,被动的中止),并帮助使这个问题更容易调试。一旦探测到无效的状态,执行则被中止,防止无效的状态导致的进一步对于系统的伤害。 + +断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。 + +### 使用断言进行调试 {#debugging-with-assertions} + +你可以调用 Swift 标准库的 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: + +```swift +let age = -3 +assert(age >= 0, "A person's age cannot be less than zero") +// 因为 age < 0,所以断言会触发 +``` + +在这个例子中,只有 `age >= 0` 为 `true` 时,即 `age` 的值非负的时候,代码才会继续执行。如果 `age` 的值是负数,就像代码中那样,`age >= 0` 为 `false`,断言被触发,终止应用。 + +如果不需要断言信息,可以就像这样忽略掉: + +```swift +assert(age >= 0) +``` + +如果代码已经检查了条件,你可以使用 `assertionFailure(_:file:line:)` 函数来表明断言失败了,例如: + +```swift +if age > 10 { + print("You can ride the roller-coaster or the ferris wheel.") +} else if age > 0 { + print("You can ride the ferris wheel.") +} else { + assertionFailure("A person's age can't be less than zero.") +} +``` + +### 强制执行先决条件 {#enforcing-preconditions} + +当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。 + +你可以使用全局 `precondition(_:_:file:line:)` 函数来写一个先决条件。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: + +```swift +// 在一个下标的实现里... +precondition(index > 0, "Index must be greater than zero.") +``` + +你可以调用 `preconditionFailure(_:file:line:)` 方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。 + +> 注意 +> +> 如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。然而,`fatalError(_:file:line:)` 函数总是中断执行,无论你怎么进行优化设定。 +> +> 你能使用 `fatalError(_:file:line:)` 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError("Unimplemented")作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。 diff --git a/source/chapter2/02_Basic_Operators.md b/source/chapter2/02_Basic_Operators.md index c3a8494b..e6b561ff 100755 --- a/source/chapter2/02_Basic_Operators.md +++ b/source/chapter2/02_Basic_Operators.md @@ -1,479 +1,479 @@ -# 基本运算符 - -*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。 - -Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(`=`)不再有返回值,这样就消除了手误将判等运算符(`==`)写成赋值符导致代码错误的缺陷。算术运算符(`+`,`-`,`*`,`/`,`%` 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](./26_Advanced_Operators.md#overflow_operators)。 - -Swift 还提供了 C 语言没有的区间运算符,例如 `a.. 注意 -> -> 求余运算符(`%`)在其他语言也叫*取模运算符*。但是严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。 - -我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中: - -![Art/remainderInteger_2x.png](https://docs.swift.org/swift-book/_images/remainderInteger_2x.png "Art/remainderInteger_2x.png") - -你可以在 `9` 中放入两个 `4`,那余数是 1(用橙色标出)。 - -在 Swift 中可以表达为: - -```swift -9 % 4 // 等于 1 -``` - -为了得到 `a % b` 的结果,`%` 计算了以下等式,并输出 `余数`作为结果: - - a = (b × 倍数) + 余数 - -当 `倍数`取最大值的时候,就会刚好可以容入 `a` 中。 - -把 `9` 和 `4` 代入等式中,我们得 `1`: - - 9 = (4 × 2) + 1 - -同样的方法,我们来计算 `-9 % 4`: - -```swift --9 % 4 // 等于 -1 -``` - -把 `-9` 和 `4` 代入等式,`-2` 是取到的最大整数: - - -9 = (4 × -2) + -1 - -余数是 `-1`。 - -在对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b` 和 `a % -b` 的结果是相同的。 - -### 一元负号运算符 {#unary-minus-operator} - -数值的正负号可以使用前缀 `-`(即*一元负号符*)来切换: - -```swift -let three = 3 -let minusThree = -three // minusThree 等于 -3 -let plusThree = -minusThree // plusThree 等于 3, 或 "负负3" -``` - -一元负号符(`-`)写在操作数之前,中间没有空格。 - -### 一元正号运算符 {#unary-plus-operator} - -*一元正号符*(`+`)不做任何改变地返回操作数的值: - -```swift -let minusSix = -6 -let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6 -``` - -虽然一元正号符什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。 - -## 组合赋值运算符 {#compound-assignment-operators} - -如同 C 语言,Swift 也提供把其他运算符和赋值运算(`=`)组合的*组合赋值运算符*,组合加运算(`+=`)是其中一个例子: - -```swift -var a = 1 -a += 2 -// a 现在是 3 -``` - -表达式 `a += 2` 是 `a = a + 2` 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。 - -> 注意 -> -> 复合赋值运算没有返回值,`let b = a += 2` 这类代码是错误。这不同于上面提到的自增和自减运算符。 - -更多 Swift 标准库运算符的信息,请看[运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。 -‌ -## 比较运算符(Comparison Operators) {#comparison-operators} - -所有标准 C 语言中的*比较运算符*都可以在 Swift 中使用: - -- 等于(`a == b`) -- 不等于(`a != b`) -- 大于(`a > b`) -- 小于(`a < b`) -- 大于等于(`a >= b`) -- 小于等于(`a <= b`) - -> 注意 -> -> Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](./09_Classes_and_Structures.md)章节的 **Identity Operators** 部分。 - -每个比较运算都返回了一个标识表达式是否成立的布尔值: - -```swift -1 == 1 // true, 因为 1 等于 1 -2 != 1 // true, 因为 2 不等于 1 -2 > 1 // true, 因为 2 大于 1 -1 < 2 // true, 因为 1 小于2 -1 >= 1 // true, 因为 1 大于等于 1 -2 <= 1 // false, 因为 2 并不小于等于 1 -``` - -比较运算多用于条件语句,如 `if` 条件: - -```swift -let name = "world" -if name == "world" { - print("hello, world") -} else { - print("I'm sorry \(name), but I don't recognize you") -} -// 输出“hello, world", 因为 `name` 就是等于 "world” -``` - -关于 `if` 语句,请看[控制流](./05_Control_Flow.md)。 - -如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如: - -```swift -(1, "zebra") < (2, "apple") // true,因为 1 小于 2 -(3, "apple") < (3, "bird") // true,因为 3 等于 3,但是 apple 小于 bird -(4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog -``` - -在上面的例子中,你可以看到,在第一行中从左到右的比较行为。因为 `1` 小于 `2`,所以 `(1, "zebra")` 小于 `(2, "apple")`,不管元组剩下的值如何。所以 `"zebra"` 大于 `"apple"` 对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。不过,当元组的第一个元素相同时候,第二个元素将会用作比较-第二行和第三行代码就发生了这样的比较。 - -当元组中的元素都可以被比较时,你也可以使用这些运算符来比较它们的大小。例如,像下面展示的代码,你可以比较两个类型为 `(String, Int)` 的元组,因为 `Int` 和 `String` 类型的值可以比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。 - -```swift -("blue", -1) < ("purple", 1) // 正常,比较的结果为 true -("blue", false) < ("purple", true) // 错误,因为 < 不能比较布尔类型 -``` - -> 注意 -> -> Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。 - -## 三元运算符(Ternary Conditional Operator) {#ternary-conditional-operator} - -*三元运算符*的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果。 - -三元运算符是以下代码的缩写形式: - -```swift -if question { - answer1 -} else { - answer2 -} -``` - -这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点: - -```swift -let contentHeight = 40 -let hasHeader = true -let rowHeight = contentHeight + (hasHeader ? 50 : 20) -// rowHeight 现在是 90 -``` - -上面的写法比下面的代码更简洁: - -```swift -let contentHeight = 40 -let hasHeader = true -var rowHeight = contentHeight -if hasHeader { - rowHeight = rowHeight + 50 -} else { - rowHeight = rowHeight + 20 -} -// rowHeight 现在是 90 -``` - -第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。 - -三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。 - -## 空合运算符(Nil Coalescing Operator) {#nil-coalescing-operator} - -*空合运算符*(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解包,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。 - -空合运算符是对以下代码的简短表达方法: - -```swift -a != nil ? a! : b -``` - -上述代码使用了三元运算符。当可选类型 `a` 的值不为空时,进行强制解封(`a!`),访问 `a` 中的值;反之返回默认值 `b`。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。 - -> 注意 -> -> 如果 `a` 为非空值(`non-nil`),那么值 `b` 将不会被计算。这也就是所谓的*短路求值*。 - -下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择: - -```swift -let defaultColorName = "red" -var userDefinedColorName: String? //默认值为 nil - -var colorNameToUse = userDefinedColorName ?? defaultColorName -// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red" -``` - -`userDefinedColorName` 变量被定义为一个可选的 `String` 类型,默认值为 `nil`。由于 `userDefinedColorName` 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 `colorNameToUse` 的变量赋予一个字符串类型初始值。 -由于 `userDefinedColorName` 值为空,因此表达式 `userDefinedColorName ?? defaultColorName` 返回 `defaultColorName` 的值,即 `red`。 - -如果你分配一个非空值(`non-nil`)给 `userDefinedColorName`,再次执行空合运算,运算结果为封包在 `userDefaultColorName` 中的值,而非默认值。 - -```swift -userDefinedColorName = "green" -colorNameToUse = userDefinedColorName ?? defaultColorName -// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green" -``` - -## 区间运算符(Range Operators) {#range-operators} - -Swift 提供了几种方便表达一个区间的值的*区间运算符*。 - -### 闭区间运算符 {#closed-range-operator} - -*闭区间运算符*(`a...b`)定义一个包含从 `a` 到 `b`(包括 `a` 和 `b`)的所有值的区间。`a` 的值不能超过 `b`。 - -闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 `for-in` 循环中: - -```swift -for index in 1...5 { - print("\(index) * 5 = \(index * 5)") -} -// 1 * 5 = 5 -// 2 * 5 = 10 -// 3 * 5 = 15 -// 4 * 5 = 20 -// 5 * 5 = 25 -``` - -关于 `for-in` 循环,请看[控制流](./05_Control_Flow.md)。 - -### 半开区间运算符 {#half-open-range-operator} - -*半开区间运算符*(`a.. 注意 -> -> Swift 逻辑操作符 `&&` 和 `||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。 - -### 使用括号来明确优先级 {#explicit-parentheses} - -为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确: - -```swift -if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword { - print("Welcome!") -} else { - print("ACCESS DENIED") -} -// 输出“Welcome!” -``` - -这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧! +# 基本运算符 + +*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。 + +Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(`=`)不再有返回值,这样就消除了手误将判等运算符(`==`)写成赋值符导致代码错误的缺陷。算术运算符(`+`,`-`,`*`,`/`,`%` 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见 [溢出运算符](./26_Advanced_Operators.md#overflow_operators)。 + +Swift 还提供了 C 语言没有的区间运算符,例如 `a.. 注意 +> +> 求余运算符(`%`)在其他语言也叫*取模运算符*。但是严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。 + +我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中: + +![Art/remainderInteger_2x.png](https://docs.swift.org/swift-book/_images/remainderInteger_2x.png "Art/remainderInteger_2x.png") + +你可以在 `9` 中放入两个 `4`,那余数是 1(用橙色标出)。 + +在 Swift 中可以表达为: + +```swift +9 % 4 // 等于 1 +``` + +为了得到 `a % b` 的结果,`%` 计算了以下等式,并输出 `余数`作为结果: + + a = (b × 倍数) + 余数 + +当 `倍数`取最大值的时候,就会刚好可以容入 `a` 中。 + +把 `9` 和 `4` 代入等式中,我们得 `1`: + + 9 = (4 × 2) + 1 + +同样的方法,我们来计算 `-9 % 4`: + +```swift +-9 % 4 // 等于 -1 +``` + +把 `-9` 和 `4` 代入等式,`-2` 是取到的最大整数: + + -9 = (4 × -2) + -1 + +余数是 `-1`。 + +在对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b` 和 `a % -b` 的结果是相同的。 + +### 一元负号运算符 {#unary-minus-operator} + +数值的正负号可以使用前缀 `-`(即*一元负号符*)来切换: + +```swift +let three = 3 +let minusThree = -three // minusThree 等于 -3 +let plusThree = -minusThree // plusThree 等于 3, 或 "负负3" +``` + +一元负号符(`-`)写在操作数之前,中间没有空格。 + +### 一元正号运算符 {#unary-plus-operator} + +*一元正号符*(`+`)不做任何改变地返回操作数的值: + +```swift +let minusSix = -6 +let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6 +``` + +虽然一元正号符什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。 + +## 组合赋值运算符 {#compound-assignment-operators} + +如同 C 语言,Swift 也提供把其他运算符和赋值运算(`=`)组合的*组合赋值运算符*,组合加运算(`+=`)是其中一个例子: + +```swift +var a = 1 +a += 2 +// a 现在是 3 +``` + +表达式 `a += 2` 是 `a = a + 2` 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。 + +> 注意 +> +> 复合赋值运算没有返回值,`let b = a += 2` 这类代码是错误。这不同于上面提到的自增和自减运算符。 + +更多 Swift 标准库运算符的信息,请看 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。 +‌ +## 比较运算符(Comparison Operators) {#comparison-operators} + +所有标准 C 语言中的*比较运算符*都可以在 Swift 中使用: + +- 等于(`a == b`) +- 不等于(`a != b`) +- 大于(`a > b`) +- 小于(`a < b`) +- 大于等于(`a >= b`) +- 小于等于(`a <= b`) + +> 注意 +> +> Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在 [类与结构](./09_Classes_and_Structures.md) 章节的 **Identity Operators** 部分。 + +每个比较运算都返回了一个标识表达式是否成立的布尔值: + +```swift +1 == 1 // true, 因为 1 等于 1 +2 != 1 // true, 因为 2 不等于 1 +2 > 1 // true, 因为 2 大于 1 +1 < 2 // true, 因为 1 小于2 +1 >= 1 // true, 因为 1 大于等于 1 +2 <= 1 // false, 因为 2 并不小于等于 1 +``` + +比较运算多用于条件语句,如 `if` 条件: + +```swift +let name = "world" +if name == "world" { + print("hello, world") +} else { + print("I'm sorry \(name), but I don't recognize you") +} +// 输出“hello, world", 因为 `name` 就是等于 "world” +``` + +关于 `if` 语句,请看 [控制流](./05_Control_Flow.md)。 + +如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如: + +```swift +(1, "zebra") < (2, "apple") // true,因为 1 小于 2 +(3, "apple") < (3, "bird") // true,因为 3 等于 3,但是 apple 小于 bird +(4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog +``` + +在上面的例子中,你可以看到,在第一行中从左到右的比较行为。因为 `1` 小于 `2`,所以 `(1, "zebra")` 小于 `(2, "apple")`,不管元组剩下的值如何。所以 `"zebra"` 大于 `"apple"` 对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。不过,当元组的第一个元素相同时候,第二个元素将会用作比较-第二行和第三行代码就发生了这样的比较。 + +当元组中的元素都可以被比较时,你也可以使用这些运算符来比较它们的大小。例如,像下面展示的代码,你可以比较两个类型为 `(String, Int)` 的元组,因为 `Int` 和 `String` 类型的值可以比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。 + +```swift +("blue", -1) < ("purple", 1) // 正常,比较的结果为 true +("blue", false) < ("purple", true) // 错误,因为 < 不能比较布尔类型 +``` + +> 注意 +> +> Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。 + +## 三元运算符(Ternary Conditional Operator) {#ternary-conditional-operator} + +*三元运算符*的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果。 + +三元运算符是以下代码的缩写形式: + +```swift +if question { + answer1 +} else { + answer2 +} +``` + +这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点: + +```swift +let contentHeight = 40 +let hasHeader = true +let rowHeight = contentHeight + (hasHeader ? 50 : 20) +// rowHeight 现在是 90 +``` + +上面的写法比下面的代码更简洁: + +```swift +let contentHeight = 40 +let hasHeader = true +var rowHeight = contentHeight +if hasHeader { + rowHeight = rowHeight + 50 +} else { + rowHeight = rowHeight + 20 +} +// rowHeight 现在是 90 +``` + +第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。 + +三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。 + +## 空合运算符(Nil Coalescing Operator) {#nil-coalescing-operator} + +*空合运算符*(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解包,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。 + +空合运算符是对以下代码的简短表达方法: + +```swift +a != nil ? a! : b +``` + +上述代码使用了三元运算符。当可选类型 `a` 的值不为空时,进行强制解封(`a!`),访问 `a` 中的值;反之返回默认值 `b`。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。 + +> 注意 +> +> 如果 `a` 为非空值(`non-nil`),那么值 `b` 将不会被计算。这也就是所谓的*短路求值*。 + +下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择: + +```swift +let defaultColorName = "red" +var userDefinedColorName: String? //默认值为 nil + +var colorNameToUse = userDefinedColorName ?? defaultColorName +// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red" +``` + +`userDefinedColorName` 变量被定义为一个可选的 `String` 类型,默认值为 `nil`。由于 `userDefinedColorName` 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 `colorNameToUse` 的变量赋予一个字符串类型初始值。 +由于 `userDefinedColorName` 值为空,因此表达式 `userDefinedColorName ?? defaultColorName` 返回 `defaultColorName` 的值,即 `red`。 + +如果你分配一个非空值(`non-nil`)给 `userDefinedColorName`,再次执行空合运算,运算结果为封包在 `userDefaultColorName` 中的值,而非默认值。 + +```swift +userDefinedColorName = "green" +colorNameToUse = userDefinedColorName ?? defaultColorName +// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green" +``` + +## 区间运算符(Range Operators) {#range-operators} + +Swift 提供了几种方便表达一个区间的值的*区间运算符*。 + +### 闭区间运算符 {#closed-range-operator} + +*闭区间运算符*(`a...b`)定义一个包含从 `a` 到 `b`(包括 `a` 和 `b`)的所有值的区间。`a` 的值不能超过 `b`。 + +闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 `for-in` 循环中: + +```swift +for index in 1...5 { + print("\(index) * 5 = \(index * 5)") +} +// 1 * 5 = 5 +// 2 * 5 = 10 +// 3 * 5 = 15 +// 4 * 5 = 20 +// 5 * 5 = 25 +``` + +关于 `for-in` 循环,请看 [控制流](./05_Control_Flow.md)。 + +### 半开区间运算符 {#half-open-range-operator} + +*半开区间运算符*(`a.. 注意 +> +> Swift 逻辑操作符 `&&` 和 `||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。 + +### 使用括号来明确优先级 {#explicit-parentheses} + +为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确: + +```swift +if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword { + print("Welcome!") +} else { + print("ACCESS DENIED") +} +// 输出“Welcome!” +``` + +这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧! diff --git a/source/chapter2/03_Strings_and_Characters.md b/source/chapter2/03_Strings_and_Characters.md index 21d5eca3..22102fa4 100755 --- a/source/chapter2/03_Strings_and_Characters.md +++ b/source/chapter2/03_Strings_and_Characters.md @@ -458,7 +458,7 @@ Swift 提供了三种方式来比较文本值:字符串字符相等、前缀 ### 字符串/字符相等 {#string-and-character-equality} -字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在[比较运算符](./02_Basic_Operators.md#comparison_operators): +字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在 [比较运算符](./02_Basic_Operators.md#comparison_operators): ```swift let quotation = "We're a lot alike, you and I." @@ -556,7 +556,7 @@ print("\(mansionCount) mansion scenes; \(cellCount) cell scenes") > 注意 > -> `hasPrefix(_:)` 和 `hasSuffix(_:)` 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在[字符串/字符相等](#string_and_character_equality)。 +> `hasPrefix(_:)` 和 `hasSuffix(_:)` 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在 [字符串/字符相等](#string_and_character_equality)。 ## 字符串的 Unicode 表示形式 {#unicode-representations-of-strings} diff --git a/source/chapter2/04_Collection_Types.md b/source/chapter2/04_Collection_Types.md index 48ae2d64..04ca0349 100755 --- a/source/chapter2/04_Collection_Types.md +++ b/source/chapter2/04_Collection_Types.md @@ -1,646 +1,646 @@ -# 集合类型 - -Swift 语言提供 `Arrays`、`Sets` 和 `Dictionaries` 三种基本的*集合类型*用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。 - -![](https://docs.swift.org/swift-book/_images/CollectionTypes_intro_2x.png) - -Swift 语言中的 `Arrays`、`Sets` 和 `Dictionaries` 中存储的数据值类型必须明确。这意味着我们不能把错误的数据类型插入其中。同时这也说明你完全可以对取回值的类型非常放心。 - -> 注意 -> -> Swift 的 `Arrays`、`Sets` 和 `Dictionaries` 类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.md)章节。 - -## 集合的可变性 {#mutability-of-collections} - -如果创建一个 `Arrays`、`Sets` 或 `Dictionaries` 并且把它分配成一个变量,这个集合将会是*可变的*。这意味着你可以在创建之后添加更多或移除已存在的数据项,或者改变集合中的数据项。如果我们把 `Arrays`、`Sets` 或 `Dictionaries` 分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。 - -> 注意 -> -> 在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift 编译器可以优化我们创建的集合。 - -## 数组(Arrays) {#arrays} - -*数组*使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 - -> 注意 -> -> Swift 的 `Array` 类型被桥接到 `Foundation` 中的 `NSArray` 类。更多关于在 `Foundation` 和 `Cocoa` 中使用 `Array` 的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。 - -### 数组的简单语法 {#array-type-shorthand-syntax} - -写 Swift 数组应该遵循像 `Array` 这样的形式,其中 `Element` 是这个数组中唯一允许存在的数据类型。我们也可以使用像 `[Element]` 这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 - -### 创建一个空数组 {#creating-an-empty-array} - -我们可以使用构造语法来创建一个由特定数据类型构成的空数组: - -```swift -var someInts = [Int]() -print("someInts is of type [Int] with \(someInts.count) items.") -// 打印“someInts is of type [Int] with 0 items.” -``` - -注意,通过构造函数的类型,`someInts` 的值类型被推断为 `[Int]`。 - -或者,如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,我们可以使用空数组语句创建一个空数组,它的写法很简单:`[]`(一对空方括号): - -```swift -someInts.append(3) -// someInts 现在包含一个 Int 值 -someInts = [] -// someInts 现在是空数组,但是仍然是 [Int] 类型的。 -``` - -### 创建一个带有默认值的数组 {#creating-an-array-with-a-default-value} - -Swift 中的 `Array` 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeating`)传入数组构造函数: - -```swift -var threeDoubles = Array(repeating: 0.0, count: 3) -// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0] -``` - -### 通过两个数组相加创建一个数组 {#creating-an-array-by-adding-two-arrays-together} - -我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来: - -```swift -var anotherThreeDoubles = Array(repeating: 2.5, count: 3) -// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5] - -var sixDoubles = threeDoubles + anotherThreeDoubles -// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] -``` - -### 用数组字面量构造数组 {#creating-an-array-with-an-array-literals} - -我们可以使用*数组字面量*来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。数组字面量是一系列由逗号分割并由方括号包含的数值: - -`[value 1, value 2, value 3]`。 - -下面这个例子创建了一个叫做 `shoppingList` 并且存储 `String` 的数组: - -```swift -var shoppingList: [String] = ["Eggs", "Milk"] -// shoppingList 已经被构造并且拥有两个初始项。 -``` - -`shoppingList` 变量被声明为“字符串值类型的数组“,记作 `[String]`。 因为这个数组被规定只有 `String` 一种数据结构,所以只有 `String` 类型可以在其中被存取。 在这里,`shoppingList` 数组由两个 `String` 值(`"Eggs"` 和 `"Milk"`)构造,并且由数组字面量定义。 - -> 注意 -> -> `shoppingList` 数组被声明为变量(`var` 关键字创建)而不是常量(`let` 创建)是因为以后可能会有更多的数据项被插入其中。 - -在这个例子中,字面量仅仅包含两个 `String` 值。匹配了该数组的变量声明(只能包含 `String` 的数组),所以这个字面量的分配过程可以作为用两个初始项来构造 `shoppingList` 的一种方式。 - -由于 Swift 的类型推断机制,当我们用字面量构造只拥有相同类型值数组的时候,我们不必把数组的类型定义清楚。`shoppingList` 的构造也可以这样写: - -```swift -var shoppingList = ["Eggs", "Milk"] -``` - -因为所有数组字面量中的值都是相同的类型,Swift 可以推断出 `[String]` 是 `shoppingList` 中变量的正确类型。 - -### 访问和修改数组 {#accessing-and-modifying-an-array} - -我们可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。 - -可以使用数组的只读属性 `count` 来获取数组中的数据项数量: - -```swift -print("The shopping list contains \(shoppingList.count) items.") -// 输出“The shopping list contains 2 items.”(这个数组有2个项) -``` - -使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`: - -```swift -if shoppingList.isEmpty { - print("The shopping list is empty.") -} else { - print("The shopping list is not empty.") -} -// 打印“The shopping list is not empty.”(shoppinglist 不是空的) -``` - -也可以使用 `append(_:)` 方法在数组后面添加新的数据项: - -```swift -shoppingList.append("Flour") -// shoppingList 现在有3个数据项,有人在摊煎饼 -``` - -除此之外,使用加法赋值运算符(`+=`)也可以直接在数组后面添加一个或多个拥有相同类型的数据项: - -```swift -shoppingList += ["Baking Powder"] -// shoppingList 现在有四项了 -shoppingList += ["Chocolate Spread", "Cheese", "Butter"] -// shoppingList 现在有七项了 -``` - -可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中: - -```swift -var firstItem = shoppingList[0] -// 第一项是“Eggs” -``` - -> 注意 -> -> 第一项在数组中的索引值是 `0` 而不是 `1`。 Swift 中的数组索引总是从零开始。 - -我们也可以用下标来改变某个已有索引值对应的数据值: - -```swift -shoppingList[0] = "Six eggs" -// 其中的第一项现在是“Six eggs”而不是“Eggs” -``` - -还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把 `"Chocolate Spread"`、`"Cheese"` 和 `"Butter"` 替换为 `"Bananas"` 和 `"Apples"`: - -```swift -shoppingList[4...6] = ["Bananas", "Apples"] -// shoppingList 现在有6项 -``` - -> 注意 -> -> 不可以用下标访问的形式去在数组尾部添加新项。 - -调用数组的 `insert(_:at:)` 方法来在某个具体索引值之前添加数据项: - -```swift -shoppingList.insert("Maple Syrup", at: 0) -// shoppingList 现在有7项 -// 现在是这个列表中的第一项是“Maple Syrup” -``` - -这次 `insert(_:at:)` 方法调用把值为 `"Maple Syrup"` 的新数据项插入列表的最开始位置,并且使用 `0` 作为索引值。 - -类似的我们可以使用 `remove(at:)` 方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): - -```swift -let mapleSyrup = shoppingList.remove(at: 0) -// 索引值为0的数据项被移除 -// shoppingList 现在只有6项,而且不包括 Maple Syrup -// mapleSyrup 常量的值等于被移除数据项“Maple Syrup”的值 -``` - -> 注意 -> -> 如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的 `count` 属性进行比较来在使用某个索引之前先检验是否有效。除了当 `count` 等于 0 时(说明这是个空数组),最大索引值一直是 `count - 1`,因为数组都是零起索引。 - -数据项被移除后数组中的空出项会被自动填补,所以现在索引值为 `0` 的数据项的值再次等于 `"Six eggs"`: - -```swift -firstItem = shoppingList[0] -// firstItem 现在等于“Six eggs” -``` - -如果我们只想把数组中的最后一项移除,可以使用 `removeLast()` 方法而不是 `remove(at:)` 方法来避免我们需要获取数组的 `count` 属性。就像后者一样,前者也会返回被移除的数据项: - -```swift -let apples = shoppingList.removeLast() -// 数组的最后一项被移除了 -// shoppingList 现在只有5项,不包括 Apples -// apples 常量的值现在等于“Apples”字符串 -``` - -### 数组的遍历 {#iterating-over-an-array} - -我们可以使用 `for-in` 循环来遍历所有数组中的数据项: - -```swift -for item in shoppingList { - print(item) -} -// Six eggs -// Milk -// Flour -// Baking Powder -// Bananas -``` - -如果我们同时需要每个数据项的值和索引值,可以使用 `enumerated()` 方法来进行数组遍历。`enumerated()` 返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: - -```swift -for (index, value) in shoppingList.enumerated() { - print("Item \(String(index + 1)): \(value)") -} -// Item 1: Six eggs -// Item 2: Milk -// Item 3: Flour -// Item 4: Baking Powder -// Item 5: Bananas -``` - -更多关于 `for-in` 循环的介绍请参见[for 循环](05_Control_Flow.html#for_loops)。 - -## 集合(Sets) {#sets} - -*集合(Set)*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 - -> 注意 -> Swift 的 `Set` 类型被桥接到 `Foundation` 中的 `NSSet` 类。 -> -> 关于使用 `Foundation` 和 `Cocoa` 中 `Set` 的知识,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。 - -### 集合类型的哈希值 {#hash-values-for-set-types} - -一个类型为了存储在集合中,该类型必须是*可哈希化*的——也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a==b`,因此必须 `a.hashValue == b.hashValue`。 - -Swift 的所有基本类型(比如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.md)有讲述)默认也是可哈希化的。 - -> 注意 -> -> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型遵循 Swift 标准库中的 `Hashable` 协议。遵循 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 -> -> 因为 `Hashable` 协议遵循 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何遵循 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况: - -> * `a == a`(自反性) -> * `a == b` 意味着 `b == a`(对称性) -> * `a == b && b == c` 意味着 `a == c`(传递性) - -关于遵循协议的更多信息,请看[协议](./22_Protocols.md)。 - -### 集合类型语法 {#set-type-syntax} - -Swift 中的 `Set` 类型被写为 `Set`,这里的 `Element` 表示 `Set` 中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 - -### 创建和构造一个空的集合 {#creating-and-initalizing-an-empty-set} - -你可以通过构造器语法创建一个特定类型的空集合: - -```swift -var letters = Set() -print("letters is of type Set with \(letters.count) items.") -// 打印“letters is of type Set with 0 items.” -``` - -> 注意 -> -> 通过构造器,这里的 `letters` 变量的类型被推断为 `Set`。 - -此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的 `Set`: - -```swift -letters.insert("a") -// letters 现在含有1个 Character 类型的值 -letters = [] -// letters 现在是一个空的 Set,但是它依然是 Set 类型 -``` - -### 用数组字面量创建集合 {#creating-a-set-with-an-array-literal} - -你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。 - -下面的例子创建一个称之为 `favoriteGenres` 的集合来存储 `String` 类型的值: - -```swift -var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] -// favoriteGenres 被构造成含有三个初始值的集合 -``` - -这个 `favoriteGenres` 变量被声明为“一个 `String` 值的集合”,写为 `Set`。由于这个特定的集合含有指定 `String` 类型的值,所以它只允许存储 `String` 类型值。这里的 `favoriteGenres` 变量有三个 `String` 类型的初始值(`"Rock"`,`"Classical"` 和 `"Hip hop"`),并以数组字面量的方式出现。 - -> 注意 -> -> `favoriteGenres` 被声明为一个变量(拥有 `var` 标示符)而不是一个常量(拥有 `let` 标示符),因为它里面的元素将会在下面的例子中被增加或者移除。 - -一个 `Set` 类型不能从数组字面量中被单独推断出来,因此 `Set` 类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个 `Set` 并且该数组字面量中的所有元素类型相同,那么你无须写出 `Set` 的具体类型。`favoriteGenres` 的构造形式可以采用简化的方式代替: - -```swift -var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] -``` - -由于数组字面量中的所有元素类型相同,Swift 可以推断出 `Set` 作为 `favoriteGenres` 变量的正确类型。 - -### 访问和修改一个集合 {#accesing-and-modifying-a-set} - -你可以通过 `Set` 的属性和方法来访问和修改一个 `Set`。 - -为了找出一个 `Set` 中元素的数量,可以使用其只读属性 `count`: - -```swift -print("I have \(favoriteGenres.count) favorite music genres.") -// 打印“I have 3 favorite music genres.” -``` - -使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`: - -```swift -if favoriteGenres.isEmpty { - print("As far as music goes, I'm not picky.") -} else { - print("I have particular music preferences.") -} -// 打印“I have particular music preferences.” -``` - -你可以通过调用 `Set` 的 `insert(_:)` 方法来添加一个新元素: - -```swift -favoriteGenres.insert("Jazz") -// favoriteGenres 现在包含4个元素 -``` - -你可以通过调用 `Set` 的 `remove(_:)` 方法去删除一个元素,如果该值是该 `Set` 的一个元素则删除该元素并且返回被删除的元素值,否则如果该 `Set` 不包含该值,则返回 `nil`。另外,`Set` 中的所有元素可以通过它的 `removeAll()` 方法删除。 - -```swift -if let removedGenre = favoriteGenres.remove("Rock") { - print("\(removedGenre)? I'm over it.") -} else { - print("I never much cared for that.") -} -// 打印“Rock? I'm over it.” -``` - -使用 `contains(_:)` 方法去检查 `Set` 中是否包含一个特定的值: - -```swift -if favoriteGenres.contains("Funk") { - print("I get up on the good foot.") -} else { - print("It's too funky in here.") -} -// 打印“It's too funky in here.” -``` - -### 遍历一个集合 {#iterating-over-a-set} - -你可以在一个 `for-in` 循环中遍历一个 `Set` 中的所有值。 - -```swift -for genre in favoriteGenres { - print("\(genre)") -} -// Classical -// Jazz -// Hip hop -``` - -更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.md#for_loops)。 - -Swift 的 `Set` 类型没有确定的顺序,为了按照特定顺序来遍历一个 `Set` 中的值可以使用 `sorted()` 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符'<'对元素进行比较的结果来确定。 - -```swift -for genre in favoriteGenres.sorted() { - print("\(genre)") -} -// Classical -// Hip hop -// Jazz -``` - -## 集合操作 {#performing-set-operations} - -你可以高效地完成 `Set` 的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 - -### 基本集合操作 {#fundamental-set-operations} - -下面的插图描述了两个集合 `a` 和 `b`,以及通过阴影部分的区域显示集合各种操作的结果。 - -![](https://docs.swift.org/swift-book/_images/setVennDiagram_2x.png) - -* 使用 `intersection(_:)` 方法根据两个集合中都包含的值创建的一个新的集合。 -* 使用 `symmetricDifference(_:)` 方法根据在一个集合中但不在两个集合中的值创建一个新的集合。 -* 使用 `union(_:)` 方法根据两个集合的值创建一个新的集合。 -* 使用 `subtracting(_:)` 方法根据不在该集合中的值创建一个新的集合。 - -```swift -let oddDigits: Set = [1, 3, 5, 7, 9] -let evenDigits: Set = [0, 2, 4, 6, 8] -let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] - -oddDigits.union(evenDigits).sorted() -// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -oddDigits.intersection(evenDigits).sorted() -// [] -oddDigits.subtracting(singleDigitPrimeNumbers).sorted() -// [1, 9] -oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() -// [1, 2, 9] -``` - -### 集合成员关系和相等 {#set-membership-and-equality} - -下面的插图描述了三个集合 `a`、`b` 和 `c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的父集合,因为 `a` 包含了 `b` 中所有的元素,相反的,集合 `b` 是集合 `a` 的子集合,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 彼此不关联,因为它们之间没有共同的元素。 - -![](https://docs.swift.org/swift-book/_images/setEulerDiagram_2x.png) - -* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。 -* 使用 `isSubset(of:)` 方法来判断一个集合中的值是否也被包含在另外一个集合中。 -* 使用 `isSuperset(of:)` 方法来判断一个集合中包含另一个集合中所有的值。 -* 使用 `isStrictSubset(of:)` 或者 `isStrictSuperset(of:)` 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。 -* 使用 `isDisjoint(with:)` 方法来判断两个集合是否不含有相同的值(是否没有交集)。 - -```swift -let houseAnimals: Set = ["🐶", "🐱"] -let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] -let cityAnimals: Set = ["🐦", "🐭"] - -houseAnimals.isSubset(of: farmAnimals) -// true -farmAnimals.isSuperset(of: houseAnimals) -// true -farmAnimals.isDisjoint(with: cityAnimals) -// true -``` - -## 字典 {#dictionaries} - -*字典*是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 - -> 注意 -> -> Swift 的 `Dictionary` 类型被桥接到 `Foundation` 的 `NSDictionary` 类。 -> -> 更多关于在 `Foundation` 和 `Cocoa` 中使用 `Dictionary` 类型的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。 - -### 字典类型简化语法 {#dictionary-type-shorthand-syntax} - -Swift 的字典使用 `Dictionary` 定义,其中 `Key` 是字典中键的数据类型,`Value` 是字典中对应于这些键所存储值的数据类型。 - -> 注意 -> -> 一个字典的 `Key` 类型必须遵循 `Hashable` 协议,就像 `Set` 的值类型。 - -我们也可以用 `[Key: Value]` 这样简化的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。 - -### 创建一个空字典 {#creating-an-empty-dictionary} - -我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典: - -```swift -var namesOfIntegers = [Int: String]() -// namesOfIntegers 是一个空的 [Int: String] 字典 -``` - -这个例子创建了一个 `[Int: String]` 类型的空字典来储存整数的英语命名。它的键是 `Int` 型,值是 `String` 型。 - -如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作 `[:]`(中括号中放一个冒号): - -```swift -namesOfIntegers[16] = "sixteen" -// namesOfIntegers 现在包含一个键值对 -namesOfIntegers = [:] -// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典 -``` - -### 用字典字面量创建字典 {#creating-a-dictionary-with-a-dictionary-literal} - -我们可以使用*字典字面量*来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作 `Dictionary` 集合的快捷途径。 - -一个键值对是一个 `key` 和一个 `value` 的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割: - -```swift -[key 1: value 1, key 2: value 2, key 3: value 3] -``` - -下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: - -```swift -var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] -``` - -`airports` 字典被声明为一种 `[String: String]` 类型,这意味着这个字典的键和值都是 `String` 类型。 - -> 注意 -> -> `airports` 字典被声明为变量(用 `var` 关键字)而不是常量(`let` 关键字)因为后来更多的机场信息会被添加到这个示例字典中。 - -`airports` 字典使用字典字面量初始化,包含两个键值对。第一对的键是 `YYZ`,值是 `Toronto Pearson`。第二对的键是 `DUB`,值是 `Dublin`。 - -这个字典语句包含了两个 `String: String` 类型的键值对。它们对应 `airports` 变量声明的类型(一个只有 `String` 键和 `String` 值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的 `airport` 字典。 - -和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。 -`airports` 字典也可以用这种简短方式定义: - -```swift -var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] -``` - -因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出 `Dictionary` 是 `airports` 字典的正确类型。 - -### 访问和修改字典 {#accessing-and-modifying-a-dictionary} - -我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。 - -和数组一样,我们可以通过字典的只读属性 `count` 来获取某个字典的数据项数量: - -```swift -print("The dictionary of airports contains \(airports.count) items.") -// 打印“The dictionary of airports contains 2 items.”(这个字典有两个数据项) -``` - -使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`: - -```swift -if airports.isEmpty { - print("The airports dictionary is empty.") -} else { - print("The airports dictionary is not empty.") -} -// 打印“The airports dictionary is not empty.” -``` - -我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值: - -```swift -airports["LHR"] = "London" -// airports 字典现在有三个数据项 -``` - -我们也可以使用下标语法来改变特定键对应的值: - -```swift -airports["LHR"] = "London Heathrow" -// “LHR”对应的值被改为“London Heathrow” -``` - -作为另一种下标方法,字典的 `updateValue(_:forKey:)` 方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)` 方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)` 这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。 - -`updateValue(_:forKey:)` 方法会返回对应值的类型的可选值。举例来说:对于存储 `String` 值的字典,这个函数会返回一个 `String?` 或者“可选 `String`”类型的值。 - -如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是 `nil`。 - -```swift -if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") { - print("The old value for DUB was \(oldValue).") -} -// 输出“The old value for DUB was Dublin.” -``` - -我们也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值的类型的可选值。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选值,否则将返回 `nil`: - -```swift -if let airportName = airports["DUB"] { - print("The name of the airport is \(airportName).") -} else { - print("That airport is not in the airports dictionary.") -} -// 打印“The name of the airport is Dublin Airport.” -``` - -我们还可以使用下标语法来通过给某个键的对应值赋值为 `nil` 来从字典里移除一个键值对: - -```swift -airports["APL"] = "Apple Internation" -// “Apple Internation”不是真的 APL 机场,删除它 -airports["APL"] = nil -// APL 现在被移除了 -``` - -此外,`removeValue(forKey:)` 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回 `nil`: - -```swift -if let removedValue = airports.removeValue(forKey: "DUB") { - print("The removed airport's name is \(removedValue).") -} else { - print("The airports dictionary does not contain a value for DUB.") -} -// 打印“The removed airport's name is Dublin Airport.” -``` - -### 字典遍历 {#iterating-over-a-dictionary} - -我们可以使用 `for-in` 循环来遍历某个字典中的键值对。每一个字典中的数据项都以 `(key, value)` 元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: - -```swift -for (airportCode, airportName) in airports { - print("\(airportCode): \(airportName)") -} -// YYZ: Toronto Pearson -// LHR: London Heathrow -``` - -更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.md#for_loops)。 - -通过访问 `keys` 或者 `values` 属性,我们也可以遍历字典的键或者值: - -```swift -for airportCode in airports.keys { - print("Airport code: \(airportCode)") -} -// Airport code: YYZ -// Airport code: LHR - -for airportName in airports.values { - print("Airport name: \(airportName)") -} -// Airport name: Toronto Pearson -// Airport name: London Heathrow -``` - -如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受 `Array` 实例的 API 的参数,可以直接使用 `keys` 或者 `values` 属性构造一个新数组: - -```swift -let airportCodes = [String](airports.keys) -// airportCodes 是 ["YYZ", "LHR"] - -let airportNames = [String](airports.values) -// airportNames 是 ["Toronto Pearson", "London Heathrow"] -``` - -Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的 `keys` 或 `values` 属性使用 `sorted()` 方法。 +# 集合类型 + +Swift 语言提供 `Arrays`、`Sets` 和 `Dictionaries` 三种基本的*集合类型*用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。 + +![](https://docs.swift.org/swift-book/_images/CollectionTypes_intro_2x.png) + +Swift 语言中的 `Arrays`、`Sets` 和 `Dictionaries` 中存储的数据值类型必须明确。这意味着我们不能把错误的数据类型插入其中。同时这也说明你完全可以对取回值的类型非常放心。 + +> 注意 +> +> Swift 的 `Arrays`、`Sets` 和 `Dictionaries` 类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.md) 章节。 + +## 集合的可变性 {#mutability-of-collections} + +如果创建一个 `Arrays`、`Sets` 或 `Dictionaries` 并且把它分配成一个变量,这个集合将会是*可变的*。这意味着你可以在创建之后添加更多或移除已存在的数据项,或者改变集合中的数据项。如果我们把 `Arrays`、`Sets` 或 `Dictionaries` 分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。 + +> 注意 +> +> 在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift 编译器可以优化我们创建的集合。 + +## 数组(Arrays) {#arrays} + +*数组*使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 + +> 注意 +> +> Swift 的 `Array` 类型被桥接到 `Foundation` 中的 `NSArray` 类。更多关于在 `Foundation` 和 `Cocoa` 中使用 `Array` 的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中 [使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6) 部分。 + +### 数组的简单语法 {#array-type-shorthand-syntax} + +写 Swift 数组应该遵循像 `Array` 这样的形式,其中 `Element` 是这个数组中唯一允许存在的数据类型。我们也可以使用像 `[Element]` 这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 + +### 创建一个空数组 {#creating-an-empty-array} + +我们可以使用构造语法来创建一个由特定数据类型构成的空数组: + +```swift +var someInts = [Int]() +print("someInts is of type [Int] with \(someInts.count) items.") +// 打印“someInts is of type [Int] with 0 items.” +``` + +注意,通过构造函数的类型,`someInts` 的值类型被推断为 `[Int]`。 + +或者,如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,我们可以使用空数组语句创建一个空数组,它的写法很简单:`[]`(一对空方括号): + +```swift +someInts.append(3) +// someInts 现在包含一个 Int 值 +someInts = [] +// someInts 现在是空数组,但是仍然是 [Int] 类型的。 +``` + +### 创建一个带有默认值的数组 {#creating-an-array-with-a-default-value} + +Swift 中的 `Array` 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeating`)传入数组构造函数: + +```swift +var threeDoubles = Array(repeating: 0.0, count: 3) +// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0] +``` + +### 通过两个数组相加创建一个数组 {#creating-an-array-by-adding-two-arrays-together} + +我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来: + +```swift +var anotherThreeDoubles = Array(repeating: 2.5, count: 3) +// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5] + +var sixDoubles = threeDoubles + anotherThreeDoubles +// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] +``` + +### 用数组字面量构造数组 {#creating-an-array-with-an-array-literals} + +我们可以使用*数组字面量*来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。数组字面量是一系列由逗号分割并由方括号包含的数值: + +`[value 1, value 2, value 3]`。 + +下面这个例子创建了一个叫做 `shoppingList` 并且存储 `String` 的数组: + +```swift +var shoppingList: [String] = ["Eggs", "Milk"] +// shoppingList 已经被构造并且拥有两个初始项。 +``` + +`shoppingList` 变量被声明为“字符串值类型的数组“,记作 `[String]`。 因为这个数组被规定只有 `String` 一种数据结构,所以只有 `String` 类型可以在其中被存取。 在这里,`shoppingList` 数组由两个 `String` 值(`"Eggs"` 和 `"Milk"`)构造,并且由数组字面量定义。 + +> 注意 +> +> `shoppingList` 数组被声明为变量(`var` 关键字创建)而不是常量(`let` 创建)是因为以后可能会有更多的数据项被插入其中。 + +在这个例子中,字面量仅仅包含两个 `String` 值。匹配了该数组的变量声明(只能包含 `String` 的数组),所以这个字面量的分配过程可以作为用两个初始项来构造 `shoppingList` 的一种方式。 + +由于 Swift 的类型推断机制,当我们用字面量构造只拥有相同类型值数组的时候,我们不必把数组的类型定义清楚。`shoppingList` 的构造也可以这样写: + +```swift +var shoppingList = ["Eggs", "Milk"] +``` + +因为所有数组字面量中的值都是相同的类型,Swift 可以推断出 `[String]` 是 `shoppingList` 中变量的正确类型。 + +### 访问和修改数组 {#accessing-and-modifying-an-array} + +我们可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。 + +可以使用数组的只读属性 `count` 来获取数组中的数据项数量: + +```swift +print("The shopping list contains \(shoppingList.count) items.") +// 输出“The shopping list contains 2 items.”(这个数组有2个项) +``` + +使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`: + +```swift +if shoppingList.isEmpty { + print("The shopping list is empty.") +} else { + print("The shopping list is not empty.") +} +// 打印“The shopping list is not empty.”(shoppinglist 不是空的) +``` + +也可以使用 `append(_:)` 方法在数组后面添加新的数据项: + +```swift +shoppingList.append("Flour") +// shoppingList 现在有3个数据项,有人在摊煎饼 +``` + +除此之外,使用加法赋值运算符(`+=`)也可以直接在数组后面添加一个或多个拥有相同类型的数据项: + +```swift +shoppingList += ["Baking Powder"] +// shoppingList 现在有四项了 +shoppingList += ["Chocolate Spread", "Cheese", "Butter"] +// shoppingList 现在有七项了 +``` + +可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中: + +```swift +var firstItem = shoppingList[0] +// 第一项是“Eggs” +``` + +> 注意 +> +> 第一项在数组中的索引值是 `0` 而不是 `1`。 Swift 中的数组索引总是从零开始。 + +我们也可以用下标来改变某个已有索引值对应的数据值: + +```swift +shoppingList[0] = "Six eggs" +// 其中的第一项现在是“Six eggs”而不是“Eggs” +``` + +还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把 `"Chocolate Spread"`、`"Cheese"` 和 `"Butter"` 替换为 `"Bananas"` 和 `"Apples"`: + +```swift +shoppingList[4...6] = ["Bananas", "Apples"] +// shoppingList 现在有6项 +``` + +> 注意 +> +> 不可以用下标访问的形式去在数组尾部添加新项。 + +调用数组的 `insert(_:at:)` 方法来在某个具体索引值之前添加数据项: + +```swift +shoppingList.insert("Maple Syrup", at: 0) +// shoppingList 现在有7项 +// 现在是这个列表中的第一项是“Maple Syrup” +``` + +这次 `insert(_:at:)` 方法调用把值为 `"Maple Syrup"` 的新数据项插入列表的最开始位置,并且使用 `0` 作为索引值。 + +类似的我们可以使用 `remove(at:)` 方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): + +```swift +let mapleSyrup = shoppingList.remove(at: 0) +// 索引值为0的数据项被移除 +// shoppingList 现在只有6项,而且不包括 Maple Syrup +// mapleSyrup 常量的值等于被移除数据项“Maple Syrup”的值 +``` + +> 注意 +> +> 如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的 `count` 属性进行比较来在使用某个索引之前先检验是否有效。除了当 `count` 等于 0 时(说明这是个空数组),最大索引值一直是 `count - 1`,因为数组都是零起索引。 + +数据项被移除后数组中的空出项会被自动填补,所以现在索引值为 `0` 的数据项的值再次等于 `"Six eggs"`: + +```swift +firstItem = shoppingList[0] +// firstItem 现在等于“Six eggs” +``` + +如果我们只想把数组中的最后一项移除,可以使用 `removeLast()` 方法而不是 `remove(at:)` 方法来避免我们需要获取数组的 `count` 属性。就像后者一样,前者也会返回被移除的数据项: + +```swift +let apples = shoppingList.removeLast() +// 数组的最后一项被移除了 +// shoppingList 现在只有5项,不包括 Apples +// apples 常量的值现在等于“Apples”字符串 +``` + +### 数组的遍历 {#iterating-over-an-array} + +我们可以使用 `for-in` 循环来遍历所有数组中的数据项: + +```swift +for item in shoppingList { + print(item) +} +// Six eggs +// Milk +// Flour +// Baking Powder +// Bananas +``` + +如果我们同时需要每个数据项的值和索引值,可以使用 `enumerated()` 方法来进行数组遍历。`enumerated()` 返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: + +```swift +for (index, value) in shoppingList.enumerated() { + print("Item \(String(index + 1)): \(value)") +} +// Item 1: Six eggs +// Item 2: Milk +// Item 3: Flour +// Item 4: Baking Powder +// Item 5: Bananas +``` + +更多关于 `for-in` 循环的介绍请参见 [For 循环](05_Control_Flow.html#for_loops)。 + +## 集合(Sets) {#sets} + +*集合(Set)*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 + +> 注意 +> Swift 的 `Set` 类型被桥接到 `Foundation` 中的 `NSSet` 类。 +> +> 关于使用 `Foundation` 和 `Cocoa` 中 `Set` 的知识,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。 + +### 集合类型的哈希值 {#hash-values-for-set-types} + +一个类型为了存储在集合中,该类型必须是*可哈希化*的——也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a==b`,因此必须 `a.hashValue == b.hashValue`。 + +Swift 的所有基本类型(比如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在 [枚举](./08_Enumerations.md) 有讲述)默认也是可哈希化的。 + +> 注意 +> +> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型遵循 Swift 标准库中的 `Hashable` 协议。遵循 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 +> +> 因为 `Hashable` 协议遵循 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何遵循 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况: + +> * `a == a`(自反性) +> * `a == b` 意味着 `b == a`(对称性) +> * `a == b && b == c` 意味着 `a == c`(传递性) + +关于遵循协议的更多信息,请看 [协议](./22_Protocols.md)。 + +### 集合类型语法 {#set-type-syntax} + +Swift 中的 `Set` 类型被写为 `Set`,这里的 `Element` 表示 `Set` 中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 + +### 创建和构造一个空的集合 {#creating-and-initalizing-an-empty-set} + +你可以通过构造器语法创建一个特定类型的空集合: + +```swift +var letters = Set() +print("letters is of type Set with \(letters.count) items.") +// 打印“letters is of type Set with 0 items.” +``` + +> 注意 +> +> 通过构造器,这里的 `letters` 变量的类型被推断为 `Set`。 + +此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的 `Set`: + +```swift +letters.insert("a") +// letters 现在含有1个 Character 类型的值 +letters = [] +// letters 现在是一个空的 Set,但是它依然是 Set 类型 +``` + +### 用数组字面量创建集合 {#creating-a-set-with-an-array-literal} + +你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。 + +下面的例子创建一个称之为 `favoriteGenres` 的集合来存储 `String` 类型的值: + +```swift +var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] +// favoriteGenres 被构造成含有三个初始值的集合 +``` + +这个 `favoriteGenres` 变量被声明为“一个 `String` 值的集合”,写为 `Set`。由于这个特定的集合含有指定 `String` 类型的值,所以它只允许存储 `String` 类型值。这里的 `favoriteGenres` 变量有三个 `String` 类型的初始值(`"Rock"`,`"Classical"` 和 `"Hip hop"`),并以数组字面量的方式出现。 + +> 注意 +> +> `favoriteGenres` 被声明为一个变量(拥有 `var` 标示符)而不是一个常量(拥有 `let` 标示符),因为它里面的元素将会在下面的例子中被增加或者移除。 + +一个 `Set` 类型不能从数组字面量中被单独推断出来,因此 `Set` 类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个 `Set` 并且该数组字面量中的所有元素类型相同,那么你无须写出 `Set` 的具体类型。`favoriteGenres` 的构造形式可以采用简化的方式代替: + +```swift +var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] +``` + +由于数组字面量中的所有元素类型相同,Swift 可以推断出 `Set` 作为 `favoriteGenres` 变量的正确类型。 + +### 访问和修改一个集合 {#accesing-and-modifying-a-set} + +你可以通过 `Set` 的属性和方法来访问和修改一个 `Set`。 + +为了找出一个 `Set` 中元素的数量,可以使用其只读属性 `count`: + +```swift +print("I have \(favoriteGenres.count) favorite music genres.") +// 打印“I have 3 favorite music genres.” +``` + +使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`: + +```swift +if favoriteGenres.isEmpty { + print("As far as music goes, I'm not picky.") +} else { + print("I have particular music preferences.") +} +// 打印“I have particular music preferences.” +``` + +你可以通过调用 `Set` 的 `insert(_:)` 方法来添加一个新元素: + +```swift +favoriteGenres.insert("Jazz") +// favoriteGenres 现在包含4个元素 +``` + +你可以通过调用 `Set` 的 `remove(_:)` 方法去删除一个元素,如果该值是该 `Set` 的一个元素则删除该元素并且返回被删除的元素值,否则如果该 `Set` 不包含该值,则返回 `nil`。另外,`Set` 中的所有元素可以通过它的 `removeAll()` 方法删除。 + +```swift +if let removedGenre = favoriteGenres.remove("Rock") { + print("\(removedGenre)? I'm over it.") +} else { + print("I never much cared for that.") +} +// 打印“Rock? I'm over it.” +``` + +使用 `contains(_:)` 方法去检查 `Set` 中是否包含一个特定的值: + +```swift +if favoriteGenres.contains("Funk") { + print("I get up on the good foot.") +} else { + print("It's too funky in here.") +} +// 打印“It's too funky in here.” +``` + +### 遍历一个集合 {#iterating-over-a-set} + +你可以在一个 `for-in` 循环中遍历一个 `Set` 中的所有值。 + +```swift +for genre in favoriteGenres { + print("\(genre)") +} +// Classical +// Jazz +// Hip hop +``` + +更多关于 `for-in` 循环的信息,参见 [For 循环](./05_Control_Flow.md#for_loops)。 + +Swift 的 `Set` 类型没有确定的顺序,为了按照特定顺序来遍历一个 `Set` 中的值可以使用 `sorted()` 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符'<'对元素进行比较的结果来确定。 + +```swift +for genre in favoriteGenres.sorted() { + print("\(genre)") +} +// Classical +// Hip hop +// Jazz +``` + +## 集合操作 {#performing-set-operations} + +你可以高效地完成 `Set` 的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 + +### 基本集合操作 {#fundamental-set-operations} + +下面的插图描述了两个集合 `a` 和 `b`,以及通过阴影部分的区域显示集合各种操作的结果。 + +![](https://docs.swift.org/swift-book/_images/setVennDiagram_2x.png) + +* 使用 `intersection(_:)` 方法根据两个集合中都包含的值创建的一个新的集合。 +* 使用 `symmetricDifference(_:)` 方法根据在一个集合中但不在两个集合中的值创建一个新的集合。 +* 使用 `union(_:)` 方法根据两个集合的值创建一个新的集合。 +* 使用 `subtracting(_:)` 方法根据不在该集合中的值创建一个新的集合。 + +```swift +let oddDigits: Set = [1, 3, 5, 7, 9] +let evenDigits: Set = [0, 2, 4, 6, 8] +let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] + +oddDigits.union(evenDigits).sorted() +// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +oddDigits.intersection(evenDigits).sorted() +// [] +oddDigits.subtracting(singleDigitPrimeNumbers).sorted() +// [1, 9] +oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() +// [1, 2, 9] +``` + +### 集合成员关系和相等 {#set-membership-and-equality} + +下面的插图描述了三个集合 `a`、`b` 和 `c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的父集合,因为 `a` 包含了 `b` 中所有的元素,相反的,集合 `b` 是集合 `a` 的子集合,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 彼此不关联,因为它们之间没有共同的元素。 + +![](https://docs.swift.org/swift-book/_images/setEulerDiagram_2x.png) + +* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。 +* 使用 `isSubset(of:)` 方法来判断一个集合中的值是否也被包含在另外一个集合中。 +* 使用 `isSuperset(of:)` 方法来判断一个集合中包含另一个集合中所有的值。 +* 使用 `isStrictSubset(of:)` 或者 `isStrictSuperset(of:)` 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。 +* 使用 `isDisjoint(with:)` 方法来判断两个集合是否不含有相同的值(是否没有交集)。 + +```swift +let houseAnimals: Set = ["🐶", "🐱"] +let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] +let cityAnimals: Set = ["🐦", "🐭"] + +houseAnimals.isSubset(of: farmAnimals) +// true +farmAnimals.isSuperset(of: houseAnimals) +// true +farmAnimals.isDisjoint(with: cityAnimals) +// true +``` + +## 字典 {#dictionaries} + +*字典*是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 + +> 注意 +> +> Swift 的 `Dictionary` 类型被桥接到 `Foundation` 的 `NSDictionary` 类。 +> +> 更多关于在 `Foundation` 和 `Cocoa` 中使用 `Dictionary` 类型的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中 [使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6) 部分。 + +### 字典类型简化语法 {#dictionary-type-shorthand-syntax} + +Swift 的字典使用 `Dictionary` 定义,其中 `Key` 是字典中键的数据类型,`Value` 是字典中对应于这些键所存储值的数据类型。 + +> 注意 +> +> 一个字典的 `Key` 类型必须遵循 `Hashable` 协议,就像 `Set` 的值类型。 + +我们也可以用 `[Key: Value]` 这样简化的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。 + +### 创建一个空字典 {#creating-an-empty-dictionary} + +我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典: + +```swift +var namesOfIntegers = [Int: String]() +// namesOfIntegers 是一个空的 [Int: String] 字典 +``` + +这个例子创建了一个 `[Int: String]` 类型的空字典来储存整数的英语命名。它的键是 `Int` 型,值是 `String` 型。 + +如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作 `[:]`(中括号中放一个冒号): + +```swift +namesOfIntegers[16] = "sixteen" +// namesOfIntegers 现在包含一个键值对 +namesOfIntegers = [:] +// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典 +``` + +### 用字典字面量创建字典 {#creating-a-dictionary-with-a-dictionary-literal} + +我们可以使用*字典字面量*来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作 `Dictionary` 集合的快捷途径。 + +一个键值对是一个 `key` 和一个 `value` 的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割: + +```swift +[key 1: value 1, key 2: value 2, key 3: value 3] +``` + +下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: + +```swift +var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] +``` + +`airports` 字典被声明为一种 `[String: String]` 类型,这意味着这个字典的键和值都是 `String` 类型。 + +> 注意 +> +> `airports` 字典被声明为变量(用 `var` 关键字)而不是常量(`let` 关键字)因为后来更多的机场信息会被添加到这个示例字典中。 + +`airports` 字典使用字典字面量初始化,包含两个键值对。第一对的键是 `YYZ`,值是 `Toronto Pearson`。第二对的键是 `DUB`,值是 `Dublin`。 + +这个字典语句包含了两个 `String: String` 类型的键值对。它们对应 `airports` 变量声明的类型(一个只有 `String` 键和 `String` 值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的 `airport` 字典。 + +和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。 +`airports` 字典也可以用这种简短方式定义: + +```swift +var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] +``` + +因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出 `Dictionary` 是 `airports` 字典的正确类型。 + +### 访问和修改字典 {#accessing-and-modifying-a-dictionary} + +我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。 + +和数组一样,我们可以通过字典的只读属性 `count` 来获取某个字典的数据项数量: + +```swift +print("The dictionary of airports contains \(airports.count) items.") +// 打印“The dictionary of airports contains 2 items.”(这个字典有两个数据项) +``` + +使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`: + +```swift +if airports.isEmpty { + print("The airports dictionary is empty.") +} else { + print("The airports dictionary is not empty.") +} +// 打印“The airports dictionary is not empty.” +``` + +我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值: + +```swift +airports["LHR"] = "London" +// airports 字典现在有三个数据项 +``` + +我们也可以使用下标语法来改变特定键对应的值: + +```swift +airports["LHR"] = "London Heathrow" +// “LHR”对应的值被改为“London Heathrow” +``` + +作为另一种下标方法,字典的 `updateValue(_:forKey:)` 方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)` 方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)` 这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。 + +`updateValue(_:forKey:)` 方法会返回对应值的类型的可选值。举例来说:对于存储 `String` 值的字典,这个函数会返回一个 `String?` 或者“可选 `String`”类型的值。 + +如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是 `nil`。 + +```swift +if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") { + print("The old value for DUB was \(oldValue).") +} +// 输出“The old value for DUB was Dublin.” +``` + +我们也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值的类型的可选值。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选值,否则将返回 `nil`: + +```swift +if let airportName = airports["DUB"] { + print("The name of the airport is \(airportName).") +} else { + print("That airport is not in the airports dictionary.") +} +// 打印“The name of the airport is Dublin Airport.” +``` + +我们还可以使用下标语法来通过给某个键的对应值赋值为 `nil` 来从字典里移除一个键值对: + +```swift +airports["APL"] = "Apple Internation" +// “Apple Internation”不是真的 APL 机场,删除它 +airports["APL"] = nil +// APL 现在被移除了 +``` + +此外,`removeValue(forKey:)` 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回 `nil`: + +```swift +if let removedValue = airports.removeValue(forKey: "DUB") { + print("The removed airport's name is \(removedValue).") +} else { + print("The airports dictionary does not contain a value for DUB.") +} +// 打印“The removed airport's name is Dublin Airport.” +``` + +### 字典遍历 {#iterating-over-a-dictionary} + +我们可以使用 `for-in` 循环来遍历某个字典中的键值对。每一个字典中的数据项都以 `(key, value)` 元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: + +```swift +for (airportCode, airportName) in airports { + print("\(airportCode): \(airportName)") +} +// YYZ: Toronto Pearson +// LHR: London Heathrow +``` + +更多关于 `for-in` 循环的信息,参见 [For 循环](./05_Control_Flow.md#for_loops)。 + +通过访问 `keys` 或者 `values` 属性,我们也可以遍历字典的键或者值: + +```swift +for airportCode in airports.keys { + print("Airport code: \(airportCode)") +} +// Airport code: YYZ +// Airport code: LHR + +for airportName in airports.values { + print("Airport name: \(airportName)") +} +// Airport name: Toronto Pearson +// Airport name: London Heathrow +``` + +如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受 `Array` 实例的 API 的参数,可以直接使用 `keys` 或者 `values` 属性构造一个新数组: + +```swift +let airportCodes = [String](airports.keys) +// airportCodes 是 ["YYZ", "LHR"] + +let airportNames = [String](airports.values) +// airportNames 是 ["Toronto Pearson", "London Heathrow"] +``` + +Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的 `keys` 或 `values` 属性使用 `sorted()` 方法。 diff --git a/source/chapter2/05_Control_Flow.md b/source/chapter2/05_Control_Flow.md index 0164c46d..2507f1f4 100755 --- a/source/chapter2/05_Control_Flow.md +++ b/source/chapter2/05_Control_Flow.md @@ -35,7 +35,7 @@ for (animalName, legCount) in numberOfLegs { // spiders have 8 legs ``` -字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见[集合类型](./04_Collection_Types.md)。 +字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见 [集合类型](./04_Collection_Types.md)。 `for-in` 循环还可以使用数字范围。下面的例子用来输出乘法表的一部分内容: @@ -69,7 +69,7 @@ print("\(base) to the power of \(power) is \(answer)") 这个例子计算 base 这个数的 power 次幂(本例中,是 `3` 的 `10` 次幂),从 `1`(`3` 的 `0` 次幂)开始做 `3` 的乘法, 进行 `10` 次,使用 `1` 到 `10` 的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号 `_` (替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。 -在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅[区间运算符](./02_Basic_Operators.md#range_operators)。 +在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅 [区间运算符](./02_Basic_Operators.md#range_operators)。 ```swift let minutes = 60 @@ -332,7 +332,7 @@ default: > 注意 > -> 虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。 +> 虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见 [Switch 语句中的 break](#break_in_a_switch_statement)。 每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的: @@ -363,11 +363,11 @@ default: // 输出“The letter A” ``` -为了可读性,符合匹配可以写成多行形式,详情请参考[复合匹配](#compound_cases) +为了可读性,符合匹配可以写成多行形式,详情请参考 [复合匹配](#compound_cases) > 注意 > -> 如果想要显式贯穿 case 分支,请使用 `fallthrough` 语句,详情请参考[贯穿](#fallthrough)。 +> 如果想要显式贯穿 case 分支,请使用 `fallthrough` 语句,详情请参考 [贯穿](#fallthrough)。 #### 区间匹配 {#interval-matching} @@ -527,7 +527,7 @@ default: - `return` - `throw` -我们将会在下面讨论 `continue`、`break` 和 `fallthrough` 语句。`return` 语句将会在[函数](./06_Functions.md)章节讨论,`throw` 语句会在[错误抛出](./18_Error_Handling.md#throwing_errors)章节讨论。 +我们将会在下面讨论 `continue`、`break` 和 `fallthrough` 语句。`return` 语句将会在 [函数](./06_Functions.md) 章节讨论,`throw` 语句会在 [错误抛出](./18_Error_Handling.md#throwing_errors) 章节讨论。 ### Continue {#continue} @@ -754,7 +754,7 @@ if #available(iOS 10, macOS 10.12, *) { 以上可用性条件指定,`if` 语句的代码块仅仅在 iOS 10 或 macOS 10.12 及更高版本才运行。最后一个参数,`*`,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if 语句的代码块将会运行。 -在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 `iOS`,`macOS`,`watchOS` 和 `tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本号。 +在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 `iOS`,`macOS`,`watchOS` 和 `tvOS`——请访问 [声明属性](../chapter3/06_Attributes.html) 来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本号。 ```swift if #available(平台名称 版本号, ..., *) { diff --git a/source/chapter2/06_Functions.md b/source/chapter2/06_Functions.md index 62a4a8ae..ff737f3d 100755 --- a/source/chapter2/06_Functions.md +++ b/source/chapter2/06_Functions.md @@ -294,7 +294,7 @@ arithmeticMean(3, 8.25, 18.75) 函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为*输入输出参数(In-Out Parameters)*。 -定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个 `输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看[输入输出参数](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545)一节。 +定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个 `输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看 [输入输出参数](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545) 一节。 你只能传递变量给输入输出参数。你不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 `&` 符,表示这个值可以被函数修改。 diff --git a/source/chapter2/07_Closures.md b/source/chapter2/07_Closures.md index c05689b7..bfb70b97 100755 --- a/source/chapter2/07_Closures.md +++ b/source/chapter2/07_Closures.md @@ -6,9 +6,9 @@ > 注意 > -> 如果你不熟悉捕获(capturing)这个概念也不用担心,在[值捕获](#capturing_values)章节有它更详细的介绍。 +> 如果你不熟悉捕获(capturing)这个概念也不用担心,在 [值捕获](#capturing_values) 章节有它更详细的介绍。 -在[函数](./06_Functions.md)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一: +在 [函数](./06_Functions.md) 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一: * 全局函数是一个有名字但不会捕获任何值的闭包 * 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包 @@ -23,7 +23,7 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进 ## 闭包表达式 {#closure-expressions} -[嵌套函数](./06_Functions.md#Nested_Functions)作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。 +[嵌套函数](./06_Functions.md#Nested_Functions) 作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。 *闭包表达式*是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式。下面通过对 `sorted(by:)` 这一个案例的多次迭代改进来展示这个过程,每次迭代都使用了更加简明的方式描述了相同功能。。 @@ -129,7 +129,7 @@ reversedNames = names.sorted(by: { $0 > $1 } ) reversedNames = names.sorted(by: >) ``` -更多关于运算符方法的内容请查看[运算符方法](./26_Advanced_Operators.md#operator_methods)。 +更多关于运算符方法的内容请查看 [运算符方法](./26_Advanced_Operators.md#operator_methods)。 ## 尾随闭包 {#trailing-closures} @@ -151,7 +151,7 @@ someFunctionThatTakesAClosure() { } ``` -在[闭包表达式语法](#closure_expression_syntax)上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面: +在 [闭包表达式语法](#closure_expression_syntax) 上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面: ```swift reversedNames = names.sorted() { $0 > $1 } @@ -233,7 +233,7 @@ func makeIncrementer(forIncrement amount: Int) -> () -> Int { } ``` -`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.md#function_types_as_return_types)。 +`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看 [函数类型作为返回类型](./06_Functions.md#function_types_as_return_types)。 `makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementer` 的返回值。 @@ -290,7 +290,7 @@ incrementByTen() > 注意 > -> 如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_for_closures)。 +> 如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考 [闭包引起的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_for_closures)。 ## 闭包是引用类型 {#closures-are-reference-types} @@ -397,7 +397,7 @@ serve(customer: customersInLine.remove(at: 0)) > > 过度使用 `autoclosures` 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。 -如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性的讲解见上面的[逃逸闭包](#escaping_closures)。 +如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性的讲解见上面的 [逃逸闭包](#escaping_closures)。 ```swift // customersInLine i= ["Barry", "Daniella"] diff --git a/source/chapter2/08_Enumerations.md b/source/chapter2/08_Enumerations.md index e39b64c1..4eed2a8c 100755 --- a/source/chapter2/08_Enumerations.md +++ b/source/chapter2/08_Enumerations.md @@ -8,7 +8,7 @@ 在 Swift 中,枚举类型是一等(first-class)类型。它们采用了很多在传统上只被类(class)所支持的特性,例如计算属性(computed properties),用于提供枚举值的附加信息,实例方法(instance methods),用于提供和枚举值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始实现的基础上扩展它们的功能;还可以遵循协议(protocols)来提供标准的功能。 -想了解更多相关信息,请参见[属性](./10_Properties.md),[方法](./11_Methods.md),[构造过程](./14_Initialization.md),[扩展](./20_Extensions.md)和[协议](./21_Protocols.md)。 +想了解更多相关信息,请参见 [属性](./10_Properties.md),[方法](./11_Methods.md),[构造过程](./14_Initialization.md),[扩展](./20_Extensions.md) 和 [协议](./21_Protocols.md)。 ## 枚举语法 {#enumeration-syntax} @@ -84,7 +84,7 @@ case .west: ……以此类推。 -正如在[控制流](./05_Control_Flow.md)中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。 +正如在 [控制流](./05_Control_Flow.md) 中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。 当不需要匹配每个枚举成员的时候,你可以提供一个 `default` 分支来涵盖所有未明确处理的枚举成员: @@ -125,7 +125,7 @@ for beverage in Beverage.allCases { // juice ``` -在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见[协议](./21_Protocols.md)。 +在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见 [协议](./21_Protocols.md)。 ## 关联值 {#associated-values} @@ -200,7 +200,7 @@ case let .qrCode(productCode): ## 原始值 {#raw-values} -在[关联值](#associated_values)小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。 +在 [关联值](#associated_values) 小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。 这是一个使用 ASCII 码作为原始值的枚举: @@ -212,7 +212,7 @@ enum ASCIIControlCharacter: Character { } ``` -枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见[字符串和字符](./03_Strings_and_Characters.md)部分。 +枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见 [字符串和字符](./03_Strings_and_Characters.md) 部分。 原始值可以是字符串、字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。 @@ -273,7 +273,7 @@ let possiblePlanet = Planet(rawValue: 7) > 注意 > -> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations.html#failable_initializers) +> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见 [可失败构造器](../chapter3/05_Declarations.html#failable_initializers) 如果你试图寻找一个位置为 `11` 的行星,通过原始值构造器返回的可选 `Planet` 值将是 `nil`: diff --git a/source/chapter2/10_Properties.md b/source/chapter2/10_Properties.md index d8432148..10b7b459 100755 --- a/source/chapter2/10_Properties.md +++ b/source/chapter2/10_Properties.md @@ -10,7 +10,7 @@ 简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字 `var` 定义),也可以是*常量存储属性*(用关键字 `let` 定义)。 -可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.md#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.md#assigning_constant_properties_during_initialization)一节。 +可以在定义存储属性的时候指定默认值,请参考 [默认构造器](./14_Initialization.md#default_initializers) 一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考 [构造过程中常量属性的修改](./14_Initialization.md#assigning_constant_properties_during_initialization) 一节。 下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的区间,且这个范围值在被创建后不能被修改。 @@ -202,7 +202,7 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") 属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。 -你可以为除了延时加载存储属性之外的其他存储属性添加属性观察器,你也可以在子类中通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为你可以直接通过它的 setter 监控和响应值的变化。属性重写请参考[重写](./13_Inheritance.md#overriding)。 +你可以为除了延时加载存储属性之外的其他存储属性添加属性观察器,你也可以在子类中通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为你可以直接通过它的 setter 监控和响应值的变化。属性重写请参考 [重写](./13_Inheritance.md#overriding)。 可以为属性添加其中一个或两个观察器: @@ -217,7 +217,7 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") > > 在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 `willSet` 和 `didSet` 观察器。而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察器。 > -> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.md#initializer_delegation_for_value_types)和[类的构造器代理](./14_Initialization.md#initializer_delegation_for_class_types)。 +> 有关构造器代理的更多信息,请参考 [值类型的构造器代理](./14_Initialization.md#initializer_delegation_for_value_types) 和 [类的构造器代理](./14_Initialization.md#initializer_delegation_for_class_types)。 下面是一个 `willSet` 和 `didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。 @@ -256,7 +256,7 @@ stepCounter.totalSteps = 896 > 注意 > -> 如果将带有观察器的属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出内存模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。关于 in-out 参数详细的介绍,请参考[输入输出参数](../chapter3/05_Declarations.html#in-out_parameters) +> 如果将带有观察器的属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出内存模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。关于 in-out 参数详细的介绍,请参考 [输入输出参数](../chapter3/05_Declarations.html#in-out_parameters) ## 全局变量和局部变量 {#global-and-local-variables} @@ -268,7 +268,7 @@ stepCounter.totalSteps = 896 > 注意 > -> 全局的常量或变量都是延迟计算的,跟[延时加载存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符。 +> 全局的常量或变量都是延迟计算的,跟 [延时加载存储属性](#lazy_stored_properties) 相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符。 > > 局部范围的常量和变量从不延迟计算。 diff --git a/source/chapter2/11_Methods.md b/source/chapter2/11_Methods.md index 9c061788..93dff583 100755 --- a/source/chapter2/11_Methods.md +++ b/source/chapter2/11_Methods.md @@ -6,7 +6,7 @@ ## 实例方法(Instance Methods) {#instance-methods} -*实例方法*是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见[函数](./06_Functions.md)。 +*实例方法*是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见 [函数](./06_Functions.md)。 实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。 @@ -47,7 +47,7 @@ counter.reset() // 计数值现在是0 ``` -函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.md#specifying_external_parameter_names)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。 +函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见 [指定外部参数名](./06_Functions.md#specifying_external_parameter_names)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。 ### self 属性 {#the-self-property} @@ -107,7 +107,7 @@ print("The point is now at (\(somePoint.x), \(somePoint.y))") 上面的 `Point` 结构体定义了一个可变方法 `moveBy(x:y :)` 来移动 `Point` 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 `mutating` 关键字,从而允许修改属性。 -注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见[常量结构体的存储属性](./10_Properties.md#stored_properties_of_constant_structure_instances): +注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见 [常量结构体的存储属性](./10_Properties.md#stored_properties_of_constant_structure_instances): ```swift let fixedPoint = Point(x: 3.0, y: 3.0) @@ -213,7 +213,7 @@ struct LevelTracker { 除了类型属性和类型方法,`LevelTracker` 还监测每个玩家的进度。它用实例属性 `currentLevel` 来监测每个玩家当前的等级。 -为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@discardableResult` 属性,更多关于属性信息,请参考[特性](../chapter3/07_Attributes.html)章节。 +为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@discardableResult` 属性,更多关于属性信息,请参考 [特性](../chapter3/07_Attributes.html) 章节。 下面,`Player` 类使用 `LevelTracker` 来监测和更新每个玩家的发展进度: diff --git a/source/chapter2/13_Inheritance.md b/source/chapter2/13_Inheritance.md index c312f190..3b21502f 100755 --- a/source/chapter2/13_Inheritance.md +++ b/source/chapter2/13_Inheritance.md @@ -1,221 +1,221 @@ -# 继承 - -一个类可以*继承*另一个类的方法,属性和其它特性。当一个类继承其它类时,继承类叫*子类*,被继承类叫*超类(或父类)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。 - -在 Swift 中,类可以调用和访问超类的方法、属性和下标,并且可以重写这些方法,属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。 - -可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。 - -## 定义一个基类 {#defining-a-base-class} - -不继承于其它类的类,称之为*基类*。 - -> 注意 -> -> Swift 中的类并不是从一个通用的基类继承而来的。如果你不为自己定义的类指定一个超类的话,这个类就会自动成为基类。 - -下面的例子定义了一个叫 `Vehicle` 的基类。这个基类声明了一个名为 `currentSpeed`,默认值是 `0.0` 的存储型属性(属性类型推断为 `Double`)。`currentSpeed` 属性的值被一个 `String` 类型的只读计算型属性 `description` 使用,用来创建对于车辆的描述。 - -`Vehicle` 基类还定义了一个名为 `makeNoise` 的方法。这个方法实际上不为 `Vehicle` 实例做任何事,但之后将会被 `Vehicle` 的子类定制: - -```swift -class Vehicle { - var currentSpeed = 0.0 - var description: String { - return "traveling at \(currentSpeed) miles per hour" - } - func makeNoise() { - // 什么也不做——因为车辆不一定会有噪音 - } -} -``` - -可以用初始化语法创建一个 `Vehicle` 的新实例,即类名后面跟一个空括号: - -```swift -let someVehicle = Vehicle() -``` - -现在已经创建了一个 `Vehicle` 的新实例,你可以访问它的 `description` 属性来打印车辆的当前速度: - -```swift -print("Vehicle: \(someVehicle.description)") -// 打印“Vehicle: traveling at 0.0 miles per hour” -``` - -`Vehicle` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。 - -## 子类生成 {#subclassing} - -*子类生成*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。 - -为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔: - -```swift -class SomeClass: SomeSuperclass { - // 这里是子类的定义 -} -``` - -下一个例子,定义了一个叫 `Bicycle` 的子类,继承自父类 `Vehicle`: - -```swift -class Bicycle: Vehicle { - var hasBasket = false -} -``` - -新的 `Bicycle` 类自动继承 `Vehicle` 类的所有特性,比如 `currentSpeed` 和 `description` 属性,还有 `makeNoise()` 方法。 - -除了所继承的特性,`Bicycle` 类还定义了一个默认值为 `false` 的存储型属性 `hasBasket`(属性推断为 `Bool`)。 - -默认情况下,你创建的所有新的 `Bicycle` 实例不会有一个篮子(即 `hasBasket` 属性默认为 `false`)。创建该实例之后,你可以为 `Bicycle` 实例设置 `hasBasket` 属性为 `ture`: - -```swift -let bicycle = Bicycle() -bicycle.hasBasket = true -``` - -你还可以修改 `Bicycle` 实例所继承的 `currentSpeed` 属性,和查询实例所继承的 `description` 属性: - -```swift -bicycle.currentSpeed = 15.0 -print("Bicycle: \(bicycle.description)") -// 打印“Bicycle: traveling at 15.0 miles per hour” -``` - -子类还可以继续被其它类继承,下面的示例为 `Bicycle` 创建了一个名为 `Tandem`(双人自行车)的子类: - -```swift -class Tandem: Bicycle { - var currentNumberOfPassengers = 0 -} -``` - -`Tandem` 从 `Bicycle` 继承了所有的属性与方法,这又使它同时继承了 `Vehicle` 的所有属性与方法。`Tandem` 也增加了一个新的叫做 `currentNumberOfPassengers` 的存储型属性,默认值为 `0`。 - -如果你创建了一个 `Tandem` 的实例,你可以使用它所有的新属性和继承的属性,还能查询从 `Vehicle` 继承来的只读属性 `description`: - -```swift -let tandem = Tandem() -tandem.hasBasket = true -tandem.currentNumberOfPassengers = 2 -tandem.currentSpeed = 22.0 -print("Tandem: \(tandem.description)") -// 打印:“Tandem: traveling at 22.0 miles per hour” -``` - -## 重写 {#overriding} - -子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。 - -如果要重写某个特性,你需要在重写定义的前面加上 `override` 关键字。这么做,就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少 `override` 关键字的重写都会在编译时被认定为错误。 - -`override` 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。 - -### 访问超类的方法,属性及下标 {#accessing-superclass-methods-properties-and-subscripts} - -当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。 - -在合适的地方,你可以通过使用 `super` 前缀来访问超类版本的方法,属性或下标: - -* 在方法 `someMethod()` 的重写实现中,可以通过 `super.someMethod()` 来调用超类版本的 `someMethod()` 方法。 -* 在属性 `someProperty` 的 getter 或 setter 的重写实现中,可以通过 `super.someProperty` 来访问超类版本的 `someProperty` 属性。 -* 在下标的重写实现中,可以通过 `super[someIndex]` 来访问超类版本中的相同下标。 - -### 重写方法 {#overriding-methods} - -在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。 - -下面的例子定义了 `Vehicle` 的一个新的子类,叫 `Train`,它重写了从 `Vehicle` 类继承来的 `makeNoise()` 方法: - -```swift -class Train: Vehicle { - override func makeNoise() { - print("Choo Choo") - } -} -``` - -如果你创建一个 `Train` 的新实例,并调用了它的 `makeNoise()` 方法,你就会发现 `Train` 版本的方法被调用: - -```swift -let train = Train() -train.makeNoise() -// 打印“Choo Choo” -``` - -### 重写属性 {#overriding-properties} - -你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器,使重写的属性可以观察到底层的属性值什么时候发生改变。 - -#### 重写属性的 Getters 和 Setters {#overriding-property-etters-and-setters} - -你可以提供定制的 getter(或 setter)来重写任何一个继承来的属性,无论这个属性是存储型还是计算型属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必须将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。 - -你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。 - -> 注意 -> -> 如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过 `super.someProperty` 来返回继承来的值,其中 `someProperty` 是你要重写的属性的名字。 - -以下的例子定义了一个新类,叫 `Car`,它是 `Vehicle` 的子类。这个类引入了一个新的存储型属性叫做 `gear`,默认值为整数 `1`。`Car` 类重写了继承自 `Vehicle` 的 `description` 属性,提供包含当前档位的自定义描述: - -```swift -class Car: Vehicle { - var gear = 1 - override var description: String { - return super.description + " in gear \(gear)" - } -} -``` - -重写的 `description` 属性首先要调用 `super.description` 返回 `Vehicle` 类的 `description` 属性。之后,`Car` 类版本的 `description` 在末尾增加了一些额外的文本来提供关于当前档位的信息。 - -如果你创建了 `Car` 的实例并且设置了它的 `gear` 和 `currentSpeed` 属性,你可以看到它的 `description` 返回了 `Car` 中的自定义描述: - -```swift -let car = Car() -car.currentSpeed = 25.0 -car.gear = 3 -print("Car: \(car.description)") -// 打印“Car: traveling at 25.0 miles per hour in gear 3” -``` - -#### 重写属性观察器 {#overriding-property-observers} - -你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,无论被继承属性原本是如何实现的,当其属性值发生改变时,你就会被通知到。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。 - -> 注意 -> -> 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供 `willSet` 或 `didSet` 实现也是不恰当。 -此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。 - -下面的例子定义了一个新类叫 `AutomaticCar`,它是 `Car` 的子类。`AutomaticCar` 表示自动档汽车,它可以根据当前的速度自动选择合适的档位: - -```swift -class AutomaticCar: Car { - override var currentSpeed: Double { - didSet { - gear = Int(currentSpeed / 10.0) + 1 - } - } -} -``` - -当你设置 `AutomaticCar` 的 `currentSpeed` 属性,属性的 `didSet` 观察器就会自动地设置 `gear` 属性,为新的速度选择一个合适的档位。具体来说就是,属性观察器将新的速度值除以 `10`,然后向下取得最接近的整数值,最后加 `1` 来得到档位 `gear` 的值。例如,速度为 `35.0` 时,档位为 `4`: - -```swift -let automatic = AutomaticCar() -automatic.currentSpeed = 35.0 -print("AutomaticCar: \(automatic.description)") -// 打印“AutomaticCar: traveling at 35.0 miles per hour in gear 4” -``` - -## 防止重写 {#preventing-overrides} - -你可以通过把方法,属性或下标标记为 *`final`* 来防止它们被重写,只需要在声明关键字前加上 `final` 修饰符即可(例如:`final var`、`final func`、`final class func` 以及 `final subscript`)。 - -任何试图对带有 `final` 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 `final`。 - -可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。 +# 继承 + +一个类可以*继承*另一个类的方法,属性和其它特性。当一个类继承其它类时,继承类叫*子类*,被继承类叫*超类(或父类)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。 + +在 Swift 中,类可以调用和访问超类的方法、属性和下标,并且可以重写这些方法,属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。 + +可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。 + +## 定义一个基类 {#defining-a-base-class} + +不继承于其它类的类,称之为*基类*。 + +> 注意 +> +> Swift 中的类并不是从一个通用的基类继承而来的。如果你不为自己定义的类指定一个超类的话,这个类就会自动成为基类。 + +下面的例子定义了一个叫 `Vehicle` 的基类。这个基类声明了一个名为 `currentSpeed`,默认值是 `0.0` 的存储型属性(属性类型推断为 `Double`)。`currentSpeed` 属性的值被一个 `String` 类型的只读计算型属性 `description` 使用,用来创建对于车辆的描述。 + +`Vehicle` 基类还定义了一个名为 `makeNoise` 的方法。这个方法实际上不为 `Vehicle` 实例做任何事,但之后将会被 `Vehicle` 的子类定制: + +```swift +class Vehicle { + var currentSpeed = 0.0 + var description: String { + return "traveling at \(currentSpeed) miles per hour" + } + func makeNoise() { + // 什么也不做——因为车辆不一定会有噪音 + } +} +``` + +可以用初始化语法创建一个 `Vehicle` 的新实例,即类名后面跟一个空括号: + +```swift +let someVehicle = Vehicle() +``` + +现在已经创建了一个 `Vehicle` 的新实例,你可以访问它的 `description` 属性来打印车辆的当前速度: + +```swift +print("Vehicle: \(someVehicle.description)") +// 打印“Vehicle: traveling at 0.0 miles per hour” +``` + +`Vehicle` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。 + +## 子类生成 {#subclassing} + +*子类生成*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。 + +为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔: + +```swift +class SomeClass: SomeSuperclass { + // 这里是子类的定义 +} +``` + +下一个例子,定义了一个叫 `Bicycle` 的子类,继承自父类 `Vehicle`: + +```swift +class Bicycle: Vehicle { + var hasBasket = false +} +``` + +新的 `Bicycle` 类自动继承 `Vehicle` 类的所有特性,比如 `currentSpeed` 和 `description` 属性,还有 `makeNoise()` 方法。 + +除了所继承的特性,`Bicycle` 类还定义了一个默认值为 `false` 的存储型属性 `hasBasket`(属性推断为 `Bool`)。 + +默认情况下,你创建的所有新的 `Bicycle` 实例不会有一个篮子(即 `hasBasket` 属性默认为 `false`)。创建该实例之后,你可以为 `Bicycle` 实例设置 `hasBasket` 属性为 `ture`: + +```swift +let bicycle = Bicycle() +bicycle.hasBasket = true +``` + +你还可以修改 `Bicycle` 实例所继承的 `currentSpeed` 属性,和查询实例所继承的 `description` 属性: + +```swift +bicycle.currentSpeed = 15.0 +print("Bicycle: \(bicycle.description)") +// 打印“Bicycle: traveling at 15.0 miles per hour” +``` + +子类还可以继续被其它类继承,下面的示例为 `Bicycle` 创建了一个名为 `Tandem`(双人自行车)的子类: + +```swift +class Tandem: Bicycle { + var currentNumberOfPassengers = 0 +} +``` + +`Tandem` 从 `Bicycle` 继承了所有的属性与方法,这又使它同时继承了 `Vehicle` 的所有属性与方法。`Tandem` 也增加了一个新的叫做 `currentNumberOfPassengers` 的存储型属性,默认值为 `0`。 + +如果你创建了一个 `Tandem` 的实例,你可以使用它所有的新属性和继承的属性,还能查询从 `Vehicle` 继承来的只读属性 `description`: + +```swift +let tandem = Tandem() +tandem.hasBasket = true +tandem.currentNumberOfPassengers = 2 +tandem.currentSpeed = 22.0 +print("Tandem: \(tandem.description)") +// 打印:“Tandem: traveling at 22.0 miles per hour” +``` + +## 重写 {#overriding} + +子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。 + +如果要重写某个特性,你需要在重写定义的前面加上 `override` 关键字。这么做,就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少 `override` 关键字的重写都会在编译时被认定为错误。 + +`override` 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。 + +### 访问超类的方法,属性及下标 {#accessing-superclass-methods-properties-and-subscripts} + +当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。 + +在合适的地方,你可以通过使用 `super` 前缀来访问超类版本的方法,属性或下标: + +* 在方法 `someMethod()` 的重写实现中,可以通过 `super.someMethod()` 来调用超类版本的 `someMethod()` 方法。 +* 在属性 `someProperty` 的 getter 或 setter 的重写实现中,可以通过 `super.someProperty` 来访问超类版本的 `someProperty` 属性。 +* 在下标的重写实现中,可以通过 `super[someIndex]` 来访问超类版本中的相同下标。 + +### 重写方法 {#overriding-methods} + +在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。 + +下面的例子定义了 `Vehicle` 的一个新的子类,叫 `Train`,它重写了从 `Vehicle` 类继承来的 `makeNoise()` 方法: + +```swift +class Train: Vehicle { + override func makeNoise() { + print("Choo Choo") + } +} +``` + +如果你创建一个 `Train` 的新实例,并调用了它的 `makeNoise()` 方法,你就会发现 `Train` 版本的方法被调用: + +```swift +let train = Train() +train.makeNoise() +// 打印“Choo Choo” +``` + +### 重写属性 {#overriding-properties} + +你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器,使重写的属性可以观察到底层的属性值什么时候发生改变。 + +#### 重写属性的 Getters 和 Setters {#overriding-property-etters-and-setters} + +你可以提供定制的 getter(或 setter)来重写任何一个继承来的属性,无论这个属性是存储型还是计算型属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必须将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。 + +你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。 + +> 注意 +> +> 如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过 `super.someProperty` 来返回继承来的值,其中 `someProperty` 是你要重写的属性的名字。 + +以下的例子定义了一个新类,叫 `Car`,它是 `Vehicle` 的子类。这个类引入了一个新的存储型属性叫做 `gear`,默认值为整数 `1`。`Car` 类重写了继承自 `Vehicle` 的 `description` 属性,提供包含当前档位的自定义描述: + +```swift +class Car: Vehicle { + var gear = 1 + override var description: String { + return super.description + " in gear \(gear)" + } +} +``` + +重写的 `description` 属性首先要调用 `super.description` 返回 `Vehicle` 类的 `description` 属性。之后,`Car` 类版本的 `description` 在末尾增加了一些额外的文本来提供关于当前档位的信息。 + +如果你创建了 `Car` 的实例并且设置了它的 `gear` 和 `currentSpeed` 属性,你可以看到它的 `description` 返回了 `Car` 中的自定义描述: + +```swift +let car = Car() +car.currentSpeed = 25.0 +car.gear = 3 +print("Car: \(car.description)") +// 打印“Car: traveling at 25.0 miles per hour in gear 3” +``` + +#### 重写属性观察器 {#overriding-property-observers} + +你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,无论被继承属性原本是如何实现的,当其属性值发生改变时,你就会被通知到。关于属性观察器的更多内容,请看 [属性观察器](../chapter2/10_Properties.html#property_observers)。 + +> 注意 +> +> 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供 `willSet` 或 `didSet` 实现也是不恰当。 +此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。 + +下面的例子定义了一个新类叫 `AutomaticCar`,它是 `Car` 的子类。`AutomaticCar` 表示自动档汽车,它可以根据当前的速度自动选择合适的档位: + +```swift +class AutomaticCar: Car { + override var currentSpeed: Double { + didSet { + gear = Int(currentSpeed / 10.0) + 1 + } + } +} +``` + +当你设置 `AutomaticCar` 的 `currentSpeed` 属性,属性的 `didSet` 观察器就会自动地设置 `gear` 属性,为新的速度选择一个合适的档位。具体来说就是,属性观察器将新的速度值除以 `10`,然后向下取得最接近的整数值,最后加 `1` 来得到档位 `gear` 的值。例如,速度为 `35.0` 时,档位为 `4`: + +```swift +let automatic = AutomaticCar() +automatic.currentSpeed = 35.0 +print("AutomaticCar: \(automatic.description)") +// 打印“AutomaticCar: traveling at 35.0 miles per hour in gear 4” +``` + +## 防止重写 {#preventing-overrides} + +你可以通过把方法,属性或下标标记为 *`final`* 来防止它们被重写,只需要在声明关键字前加上 `final` 修饰符即可(例如:`final var`、`final func`、`final class func` 以及 `final subscript`)。 + +任何试图对带有 `final` 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 `final`。 + +可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。 diff --git a/source/chapter2/14_Initialization.md b/source/chapter2/14_Initialization.md index 111d9c79..f6bfe603 100755 --- a/source/chapter2/14_Initialization.md +++ b/source/chapter2/14_Initialization.md @@ -4,7 +4,7 @@ 你要通过定义*构造器*来实现构造过程,它就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器没有返回值。它们的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。 -类的实例也可以通过实现*析构器*来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.md)。 +类的实例也可以通过实现*析构器*来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参 考 [析构过程](./15_Deinitialization.md)。 ## 存储属性的初始赋值 {#setting-initial-values-for-stored-properties} @@ -131,7 +131,7 @@ let veryGreen = Color(0.0, 1.0, 0.0) 如果你不希望构造器的某个形参使用实参标签,可以使用下划线(`_`)来代替显式的实参标签来重写默认行为。 -下面是之前[形参的构造过程](#initialization_parameters)中 `Celsius` 例子的扩展,多了一个用已经的摄氏表示的 `Double` 类型值来创建新的 `Celsius` 实例的额外构造器: +下面是之前 [形参的构造过程](#initialization_parameters) 中 `Celsius` 例子的扩展,多了一个用已经的摄氏表示的 `Double` 类型值来创建新的 `Celsius` 实例的额外构造器: ```swift struct Celsius { @@ -244,7 +244,7 @@ let twoByTwo = Size(width: 2.0, height: 2.0) 构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为*构造器代理*,它能避免多个构造器间的代码重复。 -构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.md))。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。 +构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考 [继承](./13_Inheritance.md))。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节 [类的继承和构造过程](#class_inheritance_and_initialization) 中介绍。 对于值类型,你可以使用 `self.init` 在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 `self.init`。 @@ -252,7 +252,7 @@ let twoByTwo = Size(width: 2.0, height: 2.0) > 注意 > -> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./20_Extensions.md)章节。 +> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看 [扩展](./20_Extensions.md) 章节。 下面例子定义一个自定义结构体 `Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体 `Size` 和 `Point`,它们各自为其所有的属性提供了默认初始值 `0.0`。 @@ -314,7 +314,7 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0), > 注意 > -> 如果你想用另外一种不需要自己定义 `init()` 和 `init(origin:size:)` 的方式来实现这个例子,请参考[扩展](./21_Extensions.md)。 +> 如果你想用另外一种不需要自己定义 `init()` 和 `init(origin:size:)` 的方式来实现这个例子,请参考 [扩展](./21_Extensions.md)。 ## 类的继承和构造过程 {#class-inheritance-and-initialization} @@ -328,7 +328,7 @@ Swift 为类类型提供了两种构造器来确保实例中所有存储型属 类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器。指定构造器像一个个“漏斗”放在构造过程发生的地方,让构造过程沿着父类链继续往上进行。 -每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。 +每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节 [构造器的自动继承](#automatic_initializer_inheritance)。 *便利构造器*是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。 @@ -465,11 +465,11 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造 > 注意 > -> 父类的构造器仅会在安全和适当的某些情况下被继承。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。 +> 父类的构造器仅会在安全和适当的某些情况下被继承。具体内容请参考后续章节 [构造器的自动继承](#automatic_initializer_inheritance)。 假如你希望自定义的子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。 -当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上 `override` 修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上 `override` 修饰符,具体内容请参考[默认构造器](#default_initializers)。 +当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上 `override` 修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上 `override` 修饰符,具体内容请参考 [默认构造器](#default_initializers)。 正如重写属性,方法或者是下标,`override` 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否被按预想中被指定。 @@ -477,7 +477,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造 > > 当你重写一个父类的指定构造器时,你总是需要写 `override` 修饰符,即使是为了实现子类的便利构造器。 -相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。 +相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文 [类的构造器代理规则](#initializer_delegation_for_class_types) 有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。 在下面的例子中定义了一个叫 `Vehicle` 的基类。基类中声明了一个存储型属性 `numberOfWheels`,它是默认值为 `Int` 类型的 `0`。`numberOfWheels` 属性用在一个描述车辆特征 `String` 类型为 `descrpiption` 的计算型属性中: @@ -490,7 +490,7 @@ class Vehicle { } ``` -`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels` 为 `0` 的 `Vehicle` 实例: +`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考 [默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels` 为 `0` 的 `Vehicle` 实例: ```swift let vehicle = Vehicle() @@ -628,11 +628,11 @@ class RecipeIngredient: Food { ![RecipeIngredient 构造器](https://docs.swift.org/swift-book/_images/initializersExample02_2x.png) -`RecipeIngredient` 类拥有一个指定构造器 `init(name: String, quantity: Int)`,它可以用来填充 `RecipeIngredient` 实例的所有属性值。这个构造器一开始先将传入的 `quantity` 实参赋值给 `quantity` 属性,这个属性也是唯一在 `RecipeIngredient` 中新引入的属性。随后,构造器向上代理到父类 `Food` 的 `init(name: String)`。这个过程满足[两段式构造过程](#two_phase_initialization)中的安全检查 1。 +`RecipeIngredient` 类拥有一个指定构造器 `init(name: String, quantity: Int)`,它可以用来填充 `RecipeIngredient` 实例的所有属性值。这个构造器一开始先将传入的 `quantity` 实参赋值给 `quantity` 属性,这个属性也是唯一在 `RecipeIngredient` 中新引入的属性。随后,构造器向上代理到父类 `Food` 的 `init(name: String)`。这个过程满足 [两段式构造过程](#two_phase_initialization) 中的安全检查 1。 `RecipeIngredient` 也定义了一个便利构造器 `init(name: String)`,它只通过 `name` 来创建 `RecipeIngredient` 的实例。这个便利构造器假设任意 `RecipeIngredient` 实例的 `quantity` 为 `1`,所以不需要显式的质量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个 `quantity` 为 `1` 的 `RecipeIngredient` 实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为 `quantity` 参数传递 `1`。 -`RecipeIngredient` 的便利构造器 `init(name: String)` 使用了跟 `Food` 中指定构造器 `init(name: String)` 相同的形参。由于这个便利构造器重写了父类的指定构造器 `init(name: String)`,因此必须在前面使用 `override` 修饰符(参见[构造器的继承和重写](#initializer_inheritance_and_overriding))。 +`RecipeIngredient` 的便利构造器 `init(name: String)` 使用了跟 `Food` 中指定构造器 `init(name: String)` 相同的形参。由于这个便利构造器重写了父类的指定构造器 `init(name: String)`,因此必须在前面使用 `override` 修饰符(参见 [构造器的继承和重写](#initializer_inheritance_and_overriding))。 尽管 `RecipeIngredient` 将父类的指定构造器重写为了便利构造器,但是它依然提供了父类的所有指定构造器的实现。因此,`RecipeIngredient` 会自动继承父类的所有便利构造器。 diff --git a/source/chapter2/15_Deinitialization.md b/source/chapter2/15_Deinitialization.md index f17efacb..f01b7c89 100755 --- a/source/chapter2/15_Deinitialization.md +++ b/source/chapter2/15_Deinitialization.md @@ -4,7 +4,7 @@ ## 析构过程原理 {#how-deinitialization-works} -Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./23_Automatic_Reference_Counting.md)章节中所讲述,Swift 通过*自动引用计数(ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。 +Swift 会自动释放不再需要的实例以释放资源。如 [自动引用计数](./23_Automatic_Reference_Counting.md) 章节中所讲述,Swift 通过*自动引用计数(ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。 在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数和圆括号,如下所示: diff --git a/source/chapter2/16_Optional_Chaining.md b/source/chapter2/16_Optional_Chaining.md index 26942c7b..f848ea29 100755 --- a/source/chapter2/16_Optional_Chaining.md +++ b/source/chapter2/16_Optional_Chaining.md @@ -156,7 +156,7 @@ class Address { ## 通过可选链式调用访问属性 {#accessing-properties-through-optional-chaining} -正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。 +正如 [使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping) 中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。 使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性: @@ -212,7 +212,7 @@ func printNumberOfRooms() { } ``` -这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如[无返回值函数](./06_Functions.md#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。 +这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如 [无返回值函数](./06_Functions.md#functions_without_return_values) 中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。 如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 `Void?`,而不是 `Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 `if` 语句来判断能否成功调用 `printNumberOfRooms()` 方法,即使方法本身没有定义返回值。通过判断返回值是否为 `nil` 可以判断调用是否成功: @@ -225,7 +225,7 @@ if john.residence?.printNumberOfRooms() != nil { // 打印“It was not possible to print the number of rooms.” ``` -同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence` 为 `nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功: +同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的 [通过可选链式调用访问属性](#accessing_properties_through_optional_chaining) 的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence` 为 `nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功: ```swift if (john.residence?.address = someAddress) != nil { diff --git a/source/chapter2/18_Type_Casting.md b/source/chapter2/18_Type_Casting.md index 76077f2a..26058961 100644 --- a/source/chapter2/18_Type_Casting.md +++ b/source/chapter2/18_Type_Casting.md @@ -4,7 +4,7 @@ 类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符分别提供了一种简单达意的方式去检查值的类型或者转换它的类型。 -你也可以用它来检查一个类型是否遵循了某个协议,就像在[检验协议遵循](./21_Protocols.md#checking_for_protocol_conformance)部分讲述的一样。 +你也可以用它来检查一个类型是否遵循了某个协议,就像在 [检验协议遵循](./21_Protocols.md#checking_for_protocol_conformance) 部分讲述的一样。 ## 为类型转换定义类层次 {#defining-a-class-hierarchy-for-type-casting} diff --git a/source/chapter2/19_Nested_Types.md b/source/chapter2/19_Nested_Types.md index ef0bca88..64aa26cb 100755 --- a/source/chapter2/19_Nested_Types.md +++ b/source/chapter2/19_Nested_Types.md @@ -1,85 +1,85 @@ -# 嵌套类型 - -枚举常被用于为特定类或结构体实现某些功能。类似地,枚举可以方便的定义工具类或结构体,从而为某个复杂的类型所使用。为了实现这种功能,Swift 允许你定义*嵌套类型*,可以在支持的类型中定义嵌套的枚举、类和结构体。 - -要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的 `{}` 内,而且可以根据需要定义多级嵌套。 - -## 嵌套类型实践 {#nested-types-in-action} - -下面这个例子定义了一个结构体 `BlackjackCard`(二十一点),用来模拟 `BlackjackCard` 中的扑克牌点数。`BlackjackCard` 结构体包含两个嵌套定义的枚举类型 `Suit` 和 `Rank`。 - -在 `BlackjackCard` 中,`Ace` 牌可以表示 `1` 或者 `11`,`Ace` 牌的这一特征通过一个嵌套在 `Rank` 枚举中的结构体 `Values` 来表示: - -```swift -struct BlackjackCard { - - // 嵌套的 Suit 枚举 - enum Suit: Character { - case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣" - } - - // 嵌套的 Rank 枚举 - enum Rank: Int { - case two = 2, three, four, five, six, seven, eight, nine, ten - case jack, queen, king, ace - struct Values { - let first: Int, second: Int? - } - var values: Values { - switch self { - case .ace: - return Values(first: 1, second: 11) - case .jack, .queen, .king: - return Values(first: 10, second: nil) - default: - return Values(first: self.rawValue, second: nil) - } - } - } - - // BlackjackCard 的属性和方法 - let rank: Rank, suit: Suit - var description: String { - var output = "suit is \(suit.rawValue)," - output += " value is \(rank.values.first)" - if let second = rank.values.second { - output += " or \(second)" - } - return output - } -} -``` - -`Suit` 枚举用来描述扑克牌的四种花色,并用一个 `Character` 类型的原始值表示花色符号。 - -`Rank` 枚举用来描述扑克牌从 `Ace`~`10`,以及 `J`、`Q`、`K`,这 `13` 种牌,并用一个 `Int` 类型的原始值表示牌的面值。(这个 `Int` 类型的原始值未用于 `Ace`、`J`、`Q`、`K` 这 `4` 种牌。) - -如上所述,`Rank` 枚举在内部定义了一个嵌套结构体 `Values`。结构体 `Values` 中定义了两个属性,用于反映只有 `Ace` 有两个数值,其余牌都只有一个数值: - -- `first` 的类型为 `Int` -- `second` 的类型为 `Int?`,或者说“可选 `Int`” - -`Rank` 还定义了一个计算型属性 `values`,它将会返回一个 `Values` 结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化 `Values` 实例。对于 `J`、`Q`、`K`、`Ace` 这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的 `Int` 类型的原始值。 - -`BlackjackCard` 结构体拥有两个属性——`rank` 与 `suit`。它也同样定义了一个计算型属性 `description`,`description` 属性用 `rank` 和 `suit` 中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型 `second` 是否有值,若有值,则在原有的描述中增加对 `second` 的描述。 - -因为 `BlackjackCard` 是一个没有自定义构造器的结构体,在[结构体的逐一成员构造器](./14_Initialization.md#memberwise_initializers_for_structure_types)中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量 `theAceOfSpades`: - -```swift -let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades) -print("theAceOfSpades: \(theAceOfSpades.description)") -// 打印“theAceOfSpades: suit is ♠, value is 1 or 11” -``` - -尽管 `Rank` 和 `Suit` 嵌套在 `BlackjackCard` 中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.ace` 和 `.spades`)引用枚举实例。在上面的例子中,`description` 属性正确地反映了黑桃 A 牌具有 `1` 和 `11` 两个值。 - -## 引用嵌套类型 {#referring-to-nested-types} - -在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀: - -```swift -let heartsSymbol = BlackjackCard.Suit.hearts.rawValue -// 红心符号为“♡” -``` - -对于上面这个例子,这样可以使 `Suit`、`Rank` 和 `Values` 的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。 +# 嵌套类型 + +枚举常被用于为特定类或结构体实现某些功能。类似地,枚举可以方便的定义工具类或结构体,从而为某个复杂的类型所使用。为了实现这种功能,Swift 允许你定义*嵌套类型*,可以在支持的类型中定义嵌套的枚举、类和结构体。 + +要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的 `{}` 内,而且可以根据需要定义多级嵌套。 + +## 嵌套类型实践 {#nested-types-in-action} + +下面这个例子定义了一个结构体 `BlackjackCard`(二十一点),用来模拟 `BlackjackCard` 中的扑克牌点数。`BlackjackCard` 结构体包含两个嵌套定义的枚举类型 `Suit` 和 `Rank`。 + +在 `BlackjackCard` 中,`Ace` 牌可以表示 `1` 或者 `11`,`Ace` 牌的这一特征通过一个嵌套在 `Rank` 枚举中的结构体 `Values` 来表示: + +```swift +struct BlackjackCard { + + // 嵌套的 Suit 枚举 + enum Suit: Character { + case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣" + } + + // 嵌套的 Rank 枚举 + enum Rank: Int { + case two = 2, three, four, five, six, seven, eight, nine, ten + case jack, queen, king, ace + struct Values { + let first: Int, second: Int? + } + var values: Values { + switch self { + case .ace: + return Values(first: 1, second: 11) + case .jack, .queen, .king: + return Values(first: 10, second: nil) + default: + return Values(first: self.rawValue, second: nil) + } + } + } + + // BlackjackCard 的属性和方法 + let rank: Rank, suit: Suit + var description: String { + var output = "suit is \(suit.rawValue)," + output += " value is \(rank.values.first)" + if let second = rank.values.second { + output += " or \(second)" + } + return output + } +} +``` + +`Suit` 枚举用来描述扑克牌的四种花色,并用一个 `Character` 类型的原始值表示花色符号。 + +`Rank` 枚举用来描述扑克牌从 `Ace`~`10`,以及 `J`、`Q`、`K`,这 `13` 种牌,并用一个 `Int` 类型的原始值表示牌的面值。(这个 `Int` 类型的原始值未用于 `Ace`、`J`、`Q`、`K` 这 `4` 种牌。) + +如上所述,`Rank` 枚举在内部定义了一个嵌套结构体 `Values`。结构体 `Values` 中定义了两个属性,用于反映只有 `Ace` 有两个数值,其余牌都只有一个数值: + +- `first` 的类型为 `Int` +- `second` 的类型为 `Int?`,或者说“可选 `Int`” + +`Rank` 还定义了一个计算型属性 `values`,它将会返回一个 `Values` 结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化 `Values` 实例。对于 `J`、`Q`、`K`、`Ace` 这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的 `Int` 类型的原始值。 + +`BlackjackCard` 结构体拥有两个属性——`rank` 与 `suit`。它也同样定义了一个计算型属性 `description`,`description` 属性用 `rank` 和 `suit` 中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型 `second` 是否有值,若有值,则在原有的描述中增加对 `second` 的描述。 + +因为 `BlackjackCard` 是一个没有自定义构造器的结构体,在 [结构体的逐一成员构造器](./14_Initialization.md#memberwise_initializers_for_structure_types) 中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量 `theAceOfSpades`: + +```swift +let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades) +print("theAceOfSpades: \(theAceOfSpades.description)") +// 打印“theAceOfSpades: suit is ♠, value is 1 or 11” +``` + +尽管 `Rank` 和 `Suit` 嵌套在 `BlackjackCard` 中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.ace` 和 `.spades`)引用枚举实例。在上面的例子中,`description` 属性正确地反映了黑桃 A 牌具有 `1` 和 `11` 两个值。 + +## 引用嵌套类型 {#referring-to-nested-types} + +在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀: + +```swift +let heartsSymbol = BlackjackCard.Suit.hearts.rawValue +// 红心符号为“♡” +``` + +对于上面这个例子,这样可以使 `Suit`、`Rank` 和 `Values` 的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。 diff --git a/source/chapter2/21_Protocols.md b/source/chapter2/21_Protocols.md index 59c3f48b..187de4d9 100644 --- a/source/chapter2/21_Protocols.md +++ b/source/chapter2/21_Protocols.md @@ -656,7 +656,7 @@ beginConcert(in: seattle) ## 检查协议一致性 {#checking-for-protocol-conformance} -你可以使用[类型转换](./18_Type_Casting.md)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的: +你可以使用 [类型转换](./18_Type_Casting.md) 中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的: * `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`; * `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`; @@ -733,7 +733,7 @@ for object in objects { 使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。 -协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./16_Optional_Chaining.md)章节中查看。 +协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在 [可选链式调用](./16_Optional_Chaining.md) 章节中查看。 下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,它包含两个可选要求: @@ -772,7 +772,7 @@ class Counter { 这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `increment(forCount:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `increment(forCount:)` 方法,因为这个方法是可选的。因此,`increment(forCount:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `increment(forCount:)` 方法后边也加上了 `?`。 -调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅 [连接多层可选链式调用](./16_Optional_Chaining) +调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅 [连接多层可选链式调用](./16_Optional_Chaining)。 在调用 `increment(forCount:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。 @@ -881,7 +881,7 @@ extension PrettyTextRepresentable { ### 为协议扩展添加限制条件 {#adding-constraints-to-protocol-extensions} -在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.md#where_clauses)中所描述的。 +在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如 [泛型 Where 子句](./22_Generics.md#where_clauses) 中所描述的。 例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。 diff --git a/source/chapter2/22_Generics.md b/source/chapter2/22_Generics.md index 11794432..5183ba1c 100644 --- a/source/chapter2/22_Generics.md +++ b/source/chapter2/22_Generics.md @@ -16,7 +16,7 @@ func swapTwoInts(_ a: inout Int, _ b: inout Int) { } ``` -这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,具体请参考[输入输出参数](./06_Functions.md#in_out_parameters)。 +这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,具体请参考 [输入输出参数](./06_Functions.md#in_out_parameters)。 `swapTwoInts(_:_:)` 函数将 `b` 的原始值换成了 `a`,将 `a` 的原始值换成了 `b`,你可以调用这个函数来交换两个 `Int` 类型变量: @@ -408,7 +408,7 @@ struct Stack: Container { ### 扩展现有类型来指定关联类型 {#extending-an-existing-type-to-specify-an-associated-type} -[在扩展添加协议一致性](./21_Protocols.md#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型协议。 +[在扩展添加协议一致性](./21_Protocols.md#adding_protocol_conformance_with_an_extension) 中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型协议。 Swift 的 `Array` 类型已经提供 `append(_:)` 方法,`count` 属性,以及带有 `Int` 索引的下标来检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需声明 `Array` 遵循`Container` 协议,就可以扩展 Array,使其遵从 Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述: @@ -484,7 +484,7 @@ extension IntStack: SuffixableContainer { ## 泛型 Where 语句 {#where-clauses} -[类型约束](#type_constraints)让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。 +[类型约束](#type_constraints) 让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。 对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 `where` 子句来实现。通过泛型 `where` 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 `where` 子句。 diff --git a/source/chapter2/23_Automatic_Reference_Counting.md b/source/chapter2/23_Automatic_Reference_Counting.md index 0d40fbed..4200122d 100755 --- a/source/chapter2/23_Automatic_Reference_Counting.md +++ b/source/chapter2/23_Automatic_Reference_Counting.md @@ -1,560 +1,560 @@ -# 自动引用计数 - -Swift 使用*自动引用计数(ARC)*机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。 - -然而在少数情况下,为了能帮助你管理内存,ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。在 Swift 使用 ARC 与在 Obejctive-C 中使用 ARC 非常类似,具体请参考[过渡到 ARC 的发布说明](https://developer.apple.com/library/content/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226) - -> 注意 -> -> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。 - -## 自动引用计数的工作机制 {#how-arc-works} - -当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。 - -此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。 - -然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。 - -为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为 1,ARC 都不会销毁这个实例。 - -为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。 - -## 自动引用计数实践 {#arc-in-action} - -下面的例子展示了自动引用计数的工作机制。例子以一个简单的 `Person` 类开始,并定义了一个叫 `name` 的常量属性: - -```swift -class Person { - let name: String - init(name: String) { - self.name = name - print("\(name) is being initialized") - } - deinit { - print("\(name) is being deinitialized") - } -} -``` - -`Person` 类有一个构造器,此构造器为实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构器,这个析构器会在实例被销毁时打印一条消息。 - -接下来的代码片段定义了三个类型为 `Person?` 的变量,用来按照代码片段中的顺序,为新的 `Person` 实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是 `Person`),它们的值会被自动初始化为 `nil`,目前还不会引用到 `Person` 类的实例。 - -```swift -var reference1: Person? -var reference2: Person? -var reference3: Person? -``` - -现在你可以创建 `Person` 类的新实例,并且将它赋值给三个变量中的一个: - -```swift -reference1 = Person(name: "John Appleseed") -// 打印“John Appleseed is being initialized” -``` - -应当注意到当你调用 `Person` 类的构造器的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造器被执行。 - -由于 `Person` 类的新实例被赋值给了 `reference1` 变量,所以 `reference1` 到 `Person` 类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证 `Person` 实例被保持在内存中不被销毁。 - -如果你将同一个 `Person` 实例也赋值给其他两个变量,该实例又会多出两个强引用: - -```swift -reference2 = reference1 -reference3 = reference1 -``` - -现在这一个 `Person` 实例已经有三个强引用了。 - -如果你通过给其中两个变量赋值 `nil` 的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,`Person` 实例不会被销毁: - -```swift -reference1 = nil -reference2 = nil -``` - -在你清楚地表明不再使用这个 `Person` 实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它: - -```swift -reference3 = nil -// 打印“John Appleseed is being deinitialized” -``` - -## 类实例之间的循环强引用 {#strong-reference-cycles-between-class-instances} - -在上面的例子中,ARC 会跟踪你所新创建的 `Person` 实例的引用数量,并且会在 `Person` 实例不再被需要时销毁它。 - -然而,我们可能会写出一个类实例的强引用数*永远不能*变成 `0` 的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的*循环强引用*。 - -你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。具体的过程在[解决类实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances)中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。 - -下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person` 和 `Apartment`,用来建模公寓和它其中的居民: - -```swift -class Person { - let name: String - init(name: String) { self.name = name } - var apartment: Apartment? - deinit { print("\(name) is being deinitialized") } -} - -class Apartment { - let unit: String - init(unit: String) { self.unit = unit } - var tenant: Person? - deinit { print("Apartment \(unit) is being deinitialized") } -} -``` - -每一个 `Person` 实例有一个类型为 `String`,名字为 `name` 的属性,并有一个可选的初始化为 `nil` 的 `apartment` 属性。`apartment` 属性是可选的,因为一个人并不总是拥有公寓。 - -类似的,每个 `Apartment` 实例有一个叫 `unit`,类型为 `String` 的属性,并有一个可选的初始化为 `nil` 的 `tenant` 属性。`tenant` 属性是可选的,因为一栋公寓并不总是有居民。 - -这两个类都定义了析构器,用以在类实例被析构的时候输出信息。这让你能够知晓 `Person` 和 `Apartment` 的实例是否像预期的那样被销毁。 - -接下来的代码片段定义了两个可选类型的变量 `john` 和 `unit4A`,并分别被设定为下面的 `Apartment` 和 `Person` 的实例。这两个变量都被初始化为 `nil`,这正是可选类型的优点: - -```swift -var john: Person? -var unit4A: Apartment? -``` - -现在你可以创建特定的 `Person` 和 `Apartment` 实例并将赋值给 `john` 和 `unit4A` 变量: - -```swift -john = Person(name: "John Appleseed") -unit4A = Apartment(unit: "4A") -``` - -在两个实例被创建和赋值后,下图表现了强引用的关系。变量 `john` 现在有一个指向 `Person` 实例的强引用,而变量 `unit4A` 有一个指向 `Apartment` 实例的强引用: - -![](https://docs.swift.org/swift-book/_images/referenceCycle01_2x.png) - -现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来展开和访问可选变量 `john` 和 `unit4A` 中的实例,这样实例的属性才能被赋值: - -```swift -john!.apartment = unit4A -unit4A!.tenant = john -``` - -在将两个实例联系在一起之后,强引用的关系如图所示: - -![](https://docs.swift.org/swift-book/_images/referenceCycle02_2x.png) - -不幸的是,这两个实例关联后会产生一个循环强引用。`Person` 实例现在有了一个指向 `Apartment` 实例的强引用,而 `Apartment` 实例也有了一个指向 `Person` 实例的强引用。因此,当你断开 `john` 和 `unit4A` 变量所持有的强引用时,引用计数并不会降为 `0`,实例也不会被 ARC 销毁: - -```swift -john = nil -unit4A = nil -``` - -注意,当你把这两个变量设为 `nil` 时,没有任何一个析构器被调用。循环强引用会一直阻止 `Person` 和 `Apartment` 类实例的销毁,这就在你的应用程序中造成了内存泄漏。 - -在你将 `john` 和 `unit4A` 赋值为 `nil` 后,强引用关系如下图: - -![](https://docs.swift.org/swift-book/_images/referenceCycle03_2x.png) - -`Person` 和 `Apartment` 实例之间的强引用关系保留了下来并且不会被断开。 - -## 解决实例之间的循环强引用 {#resolving-strong-reference-cycles-between-class-instances} - -Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。 - -弱引用和无主引用允许循环引用中的一个实例引用另一个实例而*不*保持强引用。这样实例能够互相引用而不产生循环强引用。 - -当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。 - -### 弱引用 {#weak-references} - -*弱引用*不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 `weak` 关键字表明这是一个弱引用。 - -因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其弱引用赋值为 `nil`。并且因为弱引用需要在运行时允许被赋值为 `nil`,所以它们会被定义为可选类型变量,而不是常量。 - -你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。 - -> 注意 -> -> 当 ARC 设置弱引用为 `nil` 时,属性观察不会被触发。 - -下面的例子跟上面 `Person` 和 `Apartment` 的例子一致,但是有一个重要的区别。这一次,`Apartment` 的 `tenant` 属性被声明为弱引用: - -```swift -class Person { - let name: String - init(name: String) { self.name = name } - var apartment: Apartment? - deinit { print("\(name) is being deinitialized") } -} - -class Apartment { - let unit: String - init(unit: String) { self.unit = unit } - weak var tenant: Person? - deinit { print("Apartment \(unit) is being deinitialized") } -} -``` - -然后跟之前一样,建立两个变量(`john` 和 `unit4A`)之间的强引用,并关联两个实例: - -```swift -var john: Person? -var unit4A: Apartment? - -john = Person(name: "John Appleseed") -unit4A = Apartment(unit: "4A") - -john!.apartment = unit4A -unit4A!.tenant = john -``` - -现在,两个关联在一起的实例的引用关系如下图所示: - -![](https://docs.swift.org/swift-book/_images/weakReference01_2x.png) - -`Person` 实例依然保持对 `Apartment` 实例的强引用,但是 `Apartment` 实例只持有对 `Person` 实例的弱引用。这意味着当你通过把 `john` 变量赋值为 `nil` 而断开其所保持的强引用时,再也没有指向 `Person` 实例的强引用了: - -```swift -john = nil -// 打印“John Appleseed is being deinitialized” -``` - -由于再也没有指向 `Person` 实例的强引用,该实例会被销毁,且 `tenant` 属性会被赋值为 `nil`: - -![](https://docs.swift.org/swift-book/_images/weakReference02_2x.png) - -唯一剩下的指向 `Apartment` 实例的强引用来自于变量 `unit4A`。如果你断开这个强引用,再也没有指向 `Apartment` 实例的强引用了: - -```swift -unit4A = nil -// 打印“Apartment 4A is being deinitialized” -``` - -由于再也没有指向 `Person` 实例的强引用,该实例会被销毁: - -![](https://docs.swift.org/swift-book/_images/weakReference03_2x.png) - -> 注意 -> -> 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。 - -### 无主引用 {#unowned-references} - -和弱引用类似,*无主引用*不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 `unowned` 表示这是一个无主引用。 - -无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为 `nil`,因为非可选类型的变量不允许被赋值为 `nil`。 - -> 重点 -> -> 使用无主引用,你*必须*确保引用始终指向一个未销毁的实例。 -> -> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。 - -下面的例子定义了两个类,`Customer` 和 `CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。 - -`Customer` 和 `CreditCard` 之间的关系与前面弱引用例子中 `Apartment` 和 `Person` 的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer` 类有一个可选类型的 `card` 属性,但是 `CreditCard` 类有一个非可选类型的 `customer` 属性。 - -此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造器的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。 - -由于信用卡总是关联着一个客户,因此将 `customer` 属性定义为无主引用,用以避免循环强引用: - -```swift -class Customer { - let name: String - var card: CreditCard? - init(name: String) { - self.name = name - } - deinit { print("\(name) is being deinitialized") } -} - -class CreditCard { - let number: UInt64 - unowned let customer: Customer - init(number: UInt64, customer: Customer) { - self.number = number - self.customer = customer - } - deinit { print("Card #\(number) is being deinitialized") } -} -``` - -> 注意 -> -> `CreditCard` 类的 `number` 属性被定义为 `UInt64` 类型而不是 `Int` 类型,以确保 `number` 属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。 - -下面的代码片段定义了一个叫 `john` 的可选类型 `Customer` 变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为 `nil`: - -```swift -var john: Customer? -``` - -现在你可以创建 `Customer` 类的实例,用它初始化 `CreditCard` 实例,并将新创建的 `CreditCard` 实例赋值为客户的 `card` 属性: - -```swift -john = Customer(name: "John Appleseed") -john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!) -``` - -在你关联两个实例后,它们的引用关系如下图所示: - -![](https://docs.swift.org/swift-book/_images/unownedReference01_2x.png) - -`Customer` 实例持有对 `CreditCard` 实例的强引用,而 `CreditCard` 实例持有对 `Customer` 实例的无主引用。 - -由于 `customer` 的无主引用,当你断开 `john` 变量持有的强引用时,再也没有指向 `Customer` 实例的强引用了: - -![](https://docs.swift.org/swift-book/_images/unownedReference02_2x.png) - -由于再也没有指向 `Customer` 实例的强引用,该实例被销毁了。其后,再也没有指向 `CreditCard` 实例的强引用,该实例也随之被销毁了: - -```swift -john = nil -// 打印“John Appleseed is being deinitialized” -// 打印“Card #1234567890123456 is being deinitialized” -``` - -最后的代码展示了在 `john` 变量被设为 `nil` 后 `Customer` 实例和 `CreditCard` 实例的析构器都打印出了“销毁”的信息。 - -> 注意 -> -> 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如,出于性能方面的原因),Swift 还提供了不安全的无主引用。与所有不安全的操作一样,你需要负责检查代码以确保其安全性。 -> 你可以通过 `unowned(unsafe)` 来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。 - -### 无主引用和隐式解包可选值属性 {#unowned-references-and-implicitly-unwrapped-optional-properties} - -上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。 - -`Person` 和 `Apartment` 的例子展示了两个属性的值都允许为 `nil`,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。 - -`Customer` 和 `CreditCard` 的例子展示了一个属性的值允许为 `nil`,而另一个属性的值不允许为 `nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。 - -然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 `nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。 - -这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。 - -下面的例子定义了两个类,`Country` 和 `City`,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,`Country` 类拥有一个 `capitalCity` 属性,而 `City` 类有一个 `country` 属性: - -```swift -class Country { - let name: String - var capitalCity: City! - init(name: String, capitalName: String) { - self.name = name - self.capitalCity = City(name: capitalName, country: self) - } -} - -class City { - let name: String - unowned let country: Country - init(name: String, country: Country) { - self.name = name - self.country = country - } -} -``` - -为了建立两个类的依赖关系,`City` 的构造器接受一个 `Country` 实例作为参数,并且将实例保存到 `country` 属性。 - -`Country` 的构造器调用了 `City` 的构造器。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造器才能把 `self` 传给 `City` 的构造器。在[两段式构造过程](./14_Initialization.md#two_phase_initialization)中有具体描述。 - -为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country` 的 `capitalCity` 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要展开它的值就能访问它。在[隐式解包可选值](./01_The_Basics.md#implicityly_unwrapped_optionals)中有描述。 - -由于 `capitalCity` 默认值为 `nil`,一旦 `Country` 的实例在构造器中给 `name` 属性赋值后,整个初始化过程就完成了。这意味着一旦 `name` 属性被赋值后,`Country` 的构造器就能引用并传递隐式的 `self`。`Country` 的构造器在赋值 `capitalCity` 时,就能将 `self` 作为参数传递给 `City` 的构造器。 - -以上的意义在于你可以通过一条语句同时创建 `Country` 和 `City` 的实例,而不产生循环强引用,并且 `capitalCity` 的属性能被直接访问,而不需要通过感叹号来展开它的可选值: - -```swift -var country = Country(name: "Canada", capitalName: "Ottawa") -print("\(country.name)'s capital city is called \(country.capitalCity.name)") -// 打印“Canada's capital city is called Ottawa” -``` - -在上面的例子中,使用隐式解包可选值值意味着满足了类的构造器的两个构造阶段的要求。`capitalCity` 属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。 - -## 闭包的循环强引用 {#strong-reference-cycles-for-closures} - -前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。 - -循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如 `self.someProperty`,或者闭包中调用了实例的某个方法,例如 `self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。 - -循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。 - -Swift 提供了一种优雅的方法来解决这个问题,称之为 `闭包捕获列表`(closure capture list)。同样的,在学习如何用闭包捕获列表打破循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。 - -下面的例子为你展示了当一个闭包引用了 `self` 后是如何产生一个循环强引用的。例子中定义了一个叫 `HTMLElement` 的类,用一种简单的模型表示 HTML 文档中的一个单独的元素: - -```swift -class HTMLElement { - - let name: String - let text: String? - - lazy var asHTML: () -> String = { - if let text = self.text { - return "<\(self.name)>\(text)" - } else { - return "<\(self.name) />" - } - } - - init(name: String, text: String? = nil) { - self.name = name - self.text = text - } - - deinit { - print("\(name) is being deinitialized") - } - -} -``` - -`HTMLElement` 类定义了一个 `name` 属性来表示这个元素的名称,例如代表头部元素的 `"h1"`,代表段落的 `"p"`,或者代表换行的 `"br"`。`HTMLElement` 还定义了一个可选属性 `text`,用来设置 HTML 元素呈现的文本。 - -除了上面的两个属性,`HTMLElement` 还定义了一个 `lazy` 属性 `asHTML`。这个属性引用了一个将 `name` 和 `text` 组合成 HTML 字符串片段的闭包。该属性是 `Void -> String` 类型,或者可以理解为“一个没有参数,返回 `String` 的函数”。 - -默认情况下,闭包赋值给了 `asHTML` 属性,这个闭包返回一个代表 HTML 标签的字符串。如果 `text` 值存在,该标签就包含可选值 `text`;如果 `text` 不存在,该标签就不包含文本。对于段落元素,根据 `text` 是 `"some text"` 还是 `nil`,闭包会返回 `"

some text

"` 或者 `"

"`。 - -可以像实例方法那样去命名、使用 `asHTML` 属性。然而,由于 `asHTML` 是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。 - -例如,可以将一个闭包赋值给 `asHTML` 属性,这个闭包能在 `text` 属性是 `nil` 时使用默认文本,这是为了避免返回一个空的 HTML 标签: - -```swift -let heading = HTMLElement(name: "h1") -let defaultText = "some default text" -heading.asHTML = { - return "<\(heading.name)>\(heading.text ?? defaultText)" -} -print(heading.asHTML()) -// 打印“

some default text

” -``` - -> 注意 -> -> `asHTML` 声明为 `lazy` 属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用 `asHTML`。也就是说,在默认的闭包中可以使用 `self`,因为只有当初始化完成以及 `self` 确实存在后,才能访问 `lazy` 属性。 - -`HTMLElement` 类只提供了一个构造器,通过 `name` 和 `text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构器,当 `HTMLElement` 实例被销毁时,打印一条消息。 - -下面的代码展示了如何用 `HTMLElement` 类创建实例并打印消息: - -```swift -var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") -print(paragraph!.asHTML()) -// 打印“

hello, world

” -``` - -> 注意 -> -> 上面的 `paragraph` 变量定义为可选类型的 `HTMLElement`,因此我们可以赋值 `nil` 给它来演示循环强引用。 - -不幸的是,上面写的 `HTMLElement` 类产生了类实例和作为 `asHTML` 默认值的闭包之间的循环强引用。循环强引用如下图所示: - -![](https://docs.swift.org/swift-book/_images/closureReferenceCycle01_2x.png) - -实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name` 和 `self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.md#capturing_values))。 - -> 注意 -> -> 虽然闭包多次使用了 `self`,它只捕获 `HTMLElement` 实例的一个强引用。 - -如果设置 `paragraph` 变量为 `nil`,打破它持有的 `HTMLElement` 实例的强引用,`HTMLElement` 实例和它的闭包都不会被销毁,也是因为循环强引用: - -```swift -paragraph = nil -``` - -注意,`HTMLElement` 的析构器中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。 - -## 解决闭包的循环强引用 {#resolving-strong-reference-cycles-for-closures} - -在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。 - -> 注意 -> -> Swift 有如下要求:只要在闭包内使用 `self` 的成员,就要用 `self.someProperty` 或者 `self.someMethod()`(而不只是 `someProperty` 或 `someMethod()`)。这提醒你可能会一不小心就捕获了 `self`。 - -### 定义捕获列表 {#defining-a-capture-list} - -捕获列表中的每一项都由一对元素组成,一个元素是 `weak` 或 `unowned` 关键字,另一个元素是类实例的引用(例如 `self`)或初始化过的变量(如 `delegate = self.delegate!`)。这些项在方括号中用逗号分开。 - -如果闭包有参数列表和返回类型,把捕获列表放在它们前面: - -```swift -lazy var someClosure: (Int, String) -> String = { - [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in - // 这里是闭包的函数体 -} -``` - -如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 `in` 放在闭包最开始的地方: - -```swift -lazy var someClosure: () -> String = { - [unowned self, weak delegate = self.delegate!] in - // 这里是闭包的函数体 -} -``` - -### 弱引用和无主引用 {#weak-and-unowned-references} - -在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 `无主引用`。 - -相反的,在被捕获的引用可能会变为 `nil` 时,将闭包内的捕获定义为 `弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 `nil`。这使我们可以在闭包体内检查它们是否存在。 - -> 注意 -> -> 如果被捕获的引用绝对不会变为 `nil`,应该用无主引用,而不是弱引用。 - -前面的 `HTMLElement` 例子中,无主引用是正确的解决循环强引用的方法。这样编写 `HTMLElement` 类来避免循环强引用: - -```swift -class HTMLElement { - - let name: String - let text: String? - - lazy var asHTML: () -> String = { - [unowned self] in - if let text = self.text { - return "<\(self.name)>\(text)" - } else { - return "<\(self.name) />" - } - } - - init(name: String, text: String? = nil) { - self.name = name - self.text = text - } - - deinit { - print("\(name) is being deinitialized") - } - -} -``` - -上面的 `HTMLElement` 实现和之前的实现一致,除了在 `asHTML` 闭包中多了一个捕获列表。这里,捕获列表是 `[unowned self]`,表示“将 `self` 捕获为无主引用而不是强引用”。 - -和之前一样,我们可以创建并打印 `HTMLElement` 实例: - -```swift -var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") -print(paragraph!.asHTML()) -// 打印“

hello, world

” -``` - -使用捕获列表后引用关系如下图所示: - -![](https://docs.swift.org/swift-book/_images/closureReferenceCycle02_2x.png) - -这一次,闭包以无主引用的形式捕获 `self`,并不会持有 `HTMLElement` 实例的强引用。如果将 `paragraph` 赋值为 `nil`,`HTMLElement` 实例将会被销毁,并能看到它的析构器打印出的消息: - -```swift -paragraph = nil -// 打印“p is being deinitialized” -``` - -你可以查看[捕获列表](../chapter3/04_Expressions.html)章节,获取更多关于捕获列表的信息。 +# 自动引用计数 + +Swift 使用*自动引用计数(ARC)*机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。 + +然而在少数情况下,为了能帮助你管理内存,ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。在 Swift 使用 ARC 与在 Obejctive-C 中使用 ARC 非常类似,具体请参考 [过渡到 ARC 的发布说明](https://developer.apple.com/library/content/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226)。 + +> 注意 +> +> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。 + +## 自动引用计数的工作机制 {#how-arc-works} + +当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。 + +此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。 + +然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。 + +为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为 1,ARC 都不会销毁这个实例。 + +为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。 + +## 自动引用计数实践 {#arc-in-action} + +下面的例子展示了自动引用计数的工作机制。例子以一个简单的 `Person` 类开始,并定义了一个叫 `name` 的常量属性: + +```swift +class Person { + let name: String + init(name: String) { + self.name = name + print("\(name) is being initialized") + } + deinit { + print("\(name) is being deinitialized") + } +} +``` + +`Person` 类有一个构造器,此构造器为实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构器,这个析构器会在实例被销毁时打印一条消息。 + +接下来的代码片段定义了三个类型为 `Person?` 的变量,用来按照代码片段中的顺序,为新的 `Person` 实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是 `Person`),它们的值会被自动初始化为 `nil`,目前还不会引用到 `Person` 类的实例。 + +```swift +var reference1: Person? +var reference2: Person? +var reference3: Person? +``` + +现在你可以创建 `Person` 类的新实例,并且将它赋值给三个变量中的一个: + +```swift +reference1 = Person(name: "John Appleseed") +// 打印“John Appleseed is being initialized” +``` + +应当注意到当你调用 `Person` 类的构造器的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造器被执行。 + +由于 `Person` 类的新实例被赋值给了 `reference1` 变量,所以 `reference1` 到 `Person` 类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证 `Person` 实例被保持在内存中不被销毁。 + +如果你将同一个 `Person` 实例也赋值给其他两个变量,该实例又会多出两个强引用: + +```swift +reference2 = reference1 +reference3 = reference1 +``` + +现在这一个 `Person` 实例已经有三个强引用了。 + +如果你通过给其中两个变量赋值 `nil` 的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,`Person` 实例不会被销毁: + +```swift +reference1 = nil +reference2 = nil +``` + +在你清楚地表明不再使用这个 `Person` 实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它: + +```swift +reference3 = nil +// 打印“John Appleseed is being deinitialized” +``` + +## 类实例之间的循环强引用 {#strong-reference-cycles-between-class-instances} + +在上面的例子中,ARC 会跟踪你所新创建的 `Person` 实例的引用数量,并且会在 `Person` 实例不再被需要时销毁它。 + +然而,我们可能会写出一个类实例的强引用数*永远不能*变成 `0` 的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的*循环强引用*。 + +你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。具体的过程在 [解决类实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances) 中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。 + +下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person` 和 `Apartment`,用来建模公寓和它其中的居民: + +```swift +class Person { + let name: String + init(name: String) { self.name = name } + var apartment: Apartment? + deinit { print("\(name) is being deinitialized") } +} + +class Apartment { + let unit: String + init(unit: String) { self.unit = unit } + var tenant: Person? + deinit { print("Apartment \(unit) is being deinitialized") } +} +``` + +每一个 `Person` 实例有一个类型为 `String`,名字为 `name` 的属性,并有一个可选的初始化为 `nil` 的 `apartment` 属性。`apartment` 属性是可选的,因为一个人并不总是拥有公寓。 + +类似的,每个 `Apartment` 实例有一个叫 `unit`,类型为 `String` 的属性,并有一个可选的初始化为 `nil` 的 `tenant` 属性。`tenant` 属性是可选的,因为一栋公寓并不总是有居民。 + +这两个类都定义了析构器,用以在类实例被析构的时候输出信息。这让你能够知晓 `Person` 和 `Apartment` 的实例是否像预期的那样被销毁。 + +接下来的代码片段定义了两个可选类型的变量 `john` 和 `unit4A`,并分别被设定为下面的 `Apartment` 和 `Person` 的实例。这两个变量都被初始化为 `nil`,这正是可选类型的优点: + +```swift +var john: Person? +var unit4A: Apartment? +``` + +现在你可以创建特定的 `Person` 和 `Apartment` 实例并将赋值给 `john` 和 `unit4A` 变量: + +```swift +john = Person(name: "John Appleseed") +unit4A = Apartment(unit: "4A") +``` + +在两个实例被创建和赋值后,下图表现了强引用的关系。变量 `john` 现在有一个指向 `Person` 实例的强引用,而变量 `unit4A` 有一个指向 `Apartment` 实例的强引用: + +![](https://docs.swift.org/swift-book/_images/referenceCycle01_2x.png) + +现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来展开和访问可选变量 `john` 和 `unit4A` 中的实例,这样实例的属性才能被赋值: + +```swift +john!.apartment = unit4A +unit4A!.tenant = john +``` + +在将两个实例联系在一起之后,强引用的关系如图所示: + +![](https://docs.swift.org/swift-book/_images/referenceCycle02_2x.png) + +不幸的是,这两个实例关联后会产生一个循环强引用。`Person` 实例现在有了一个指向 `Apartment` 实例的强引用,而 `Apartment` 实例也有了一个指向 `Person` 实例的强引用。因此,当你断开 `john` 和 `unit4A` 变量所持有的强引用时,引用计数并不会降为 `0`,实例也不会被 ARC 销毁: + +```swift +john = nil +unit4A = nil +``` + +注意,当你把这两个变量设为 `nil` 时,没有任何一个析构器被调用。循环强引用会一直阻止 `Person` 和 `Apartment` 类实例的销毁,这就在你的应用程序中造成了内存泄漏。 + +在你将 `john` 和 `unit4A` 赋值为 `nil` 后,强引用关系如下图: + +![](https://docs.swift.org/swift-book/_images/referenceCycle03_2x.png) + +`Person` 和 `Apartment` 实例之间的强引用关系保留了下来并且不会被断开。 + +## 解决实例之间的循环强引用 {#resolving-strong-reference-cycles-between-class-instances} + +Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。 + +弱引用和无主引用允许循环引用中的一个实例引用另一个实例而*不*保持强引用。这样实例能够互相引用而不产生循环强引用。 + +当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。 + +### 弱引用 {#weak-references} + +*弱引用*不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 `weak` 关键字表明这是一个弱引用。 + +因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其弱引用赋值为 `nil`。并且因为弱引用需要在运行时允许被赋值为 `nil`,所以它们会被定义为可选类型变量,而不是常量。 + +你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。 + +> 注意 +> +> 当 ARC 设置弱引用为 `nil` 时,属性观察不会被触发。 + +下面的例子跟上面 `Person` 和 `Apartment` 的例子一致,但是有一个重要的区别。这一次,`Apartment` 的 `tenant` 属性被声明为弱引用: + +```swift +class Person { + let name: String + init(name: String) { self.name = name } + var apartment: Apartment? + deinit { print("\(name) is being deinitialized") } +} + +class Apartment { + let unit: String + init(unit: String) { self.unit = unit } + weak var tenant: Person? + deinit { print("Apartment \(unit) is being deinitialized") } +} +``` + +然后跟之前一样,建立两个变量(`john` 和 `unit4A`)之间的强引用,并关联两个实例: + +```swift +var john: Person? +var unit4A: Apartment? + +john = Person(name: "John Appleseed") +unit4A = Apartment(unit: "4A") + +john!.apartment = unit4A +unit4A!.tenant = john +``` + +现在,两个关联在一起的实例的引用关系如下图所示: + +![](https://docs.swift.org/swift-book/_images/weakReference01_2x.png) + +`Person` 实例依然保持对 `Apartment` 实例的强引用,但是 `Apartment` 实例只持有对 `Person` 实例的弱引用。这意味着当你通过把 `john` 变量赋值为 `nil` 而断开其所保持的强引用时,再也没有指向 `Person` 实例的强引用了: + +```swift +john = nil +// 打印“John Appleseed is being deinitialized” +``` + +由于再也没有指向 `Person` 实例的强引用,该实例会被销毁,且 `tenant` 属性会被赋值为 `nil`: + +![](https://docs.swift.org/swift-book/_images/weakReference02_2x.png) + +唯一剩下的指向 `Apartment` 实例的强引用来自于变量 `unit4A`。如果你断开这个强引用,再也没有指向 `Apartment` 实例的强引用了: + +```swift +unit4A = nil +// 打印“Apartment 4A is being deinitialized” +``` + +由于再也没有指向 `Person` 实例的强引用,该实例会被销毁: + +![](https://docs.swift.org/swift-book/_images/weakReference03_2x.png) + +> 注意 +> +> 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。 + +### 无主引用 {#unowned-references} + +和弱引用类似,*无主引用*不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 `unowned` 表示这是一个无主引用。 + +无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为 `nil`,因为非可选类型的变量不允许被赋值为 `nil`。 + +> 重点 +> +> 使用无主引用,你*必须*确保引用始终指向一个未销毁的实例。 +> +> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。 + +下面的例子定义了两个类,`Customer` 和 `CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。 + +`Customer` 和 `CreditCard` 之间的关系与前面弱引用例子中 `Apartment` 和 `Person` 的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer` 类有一个可选类型的 `card` 属性,但是 `CreditCard` 类有一个非可选类型的 `customer` 属性。 + +此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造器的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。 + +由于信用卡总是关联着一个客户,因此将 `customer` 属性定义为无主引用,用以避免循环强引用: + +```swift +class Customer { + let name: String + var card: CreditCard? + init(name: String) { + self.name = name + } + deinit { print("\(name) is being deinitialized") } +} + +class CreditCard { + let number: UInt64 + unowned let customer: Customer + init(number: UInt64, customer: Customer) { + self.number = number + self.customer = customer + } + deinit { print("Card #\(number) is being deinitialized") } +} +``` + +> 注意 +> +> `CreditCard` 类的 `number` 属性被定义为 `UInt64` 类型而不是 `Int` 类型,以确保 `number` 属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。 + +下面的代码片段定义了一个叫 `john` 的可选类型 `Customer` 变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为 `nil`: + +```swift +var john: Customer? +``` + +现在你可以创建 `Customer` 类的实例,用它初始化 `CreditCard` 实例,并将新创建的 `CreditCard` 实例赋值为客户的 `card` 属性: + +```swift +john = Customer(name: "John Appleseed") +john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!) +``` + +在你关联两个实例后,它们的引用关系如下图所示: + +![](https://docs.swift.org/swift-book/_images/unownedReference01_2x.png) + +`Customer` 实例持有对 `CreditCard` 实例的强引用,而 `CreditCard` 实例持有对 `Customer` 实例的无主引用。 + +由于 `customer` 的无主引用,当你断开 `john` 变量持有的强引用时,再也没有指向 `Customer` 实例的强引用了: + +![](https://docs.swift.org/swift-book/_images/unownedReference02_2x.png) + +由于再也没有指向 `Customer` 实例的强引用,该实例被销毁了。其后,再也没有指向 `CreditCard` 实例的强引用,该实例也随之被销毁了: + +```swift +john = nil +// 打印“John Appleseed is being deinitialized” +// 打印“Card #1234567890123456 is being deinitialized” +``` + +最后的代码展示了在 `john` 变量被设为 `nil` 后 `Customer` 实例和 `CreditCard` 实例的析构器都打印出了“销毁”的信息。 + +> 注意 +> +> 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如,出于性能方面的原因),Swift 还提供了不安全的无主引用。与所有不安全的操作一样,你需要负责检查代码以确保其安全性。 +> 你可以通过 `unowned(unsafe)` 来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。 + +### 无主引用和隐式解包可选值属性 {#unowned-references-and-implicitly-unwrapped-optional-properties} + +上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。 + +`Person` 和 `Apartment` 的例子展示了两个属性的值都允许为 `nil`,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。 + +`Customer` 和 `CreditCard` 的例子展示了一个属性的值允许为 `nil`,而另一个属性的值不允许为 `nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。 + +然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 `nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。 + +这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。 + +下面的例子定义了两个类,`Country` 和 `City`,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,`Country` 类拥有一个 `capitalCity` 属性,而 `City` 类有一个 `country` 属性: + +```swift +class Country { + let name: String + var capitalCity: City! + init(name: String, capitalName: String) { + self.name = name + self.capitalCity = City(name: capitalName, country: self) + } +} + +class City { + let name: String + unowned let country: Country + init(name: String, country: Country) { + self.name = name + self.country = country + } +} +``` + +为了建立两个类的依赖关系,`City` 的构造器接受一个 `Country` 实例作为参数,并且将实例保存到 `country` 属性。 + +`Country` 的构造器调用了 `City` 的构造器。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造器才能把 `self` 传给 `City` 的构造器。在 [两段式构造过程](./14_Initialization.md#two_phase_initialization) 中有具体描述。 + +为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country` 的 `capitalCity` 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要展开它的值就能访问它。在 [隐式解包可选值](./01_The_Basics.md#implicityly_unwrapped_optionals) 中有描述。 + +由于 `capitalCity` 默认值为 `nil`,一旦 `Country` 的实例在构造器中给 `name` 属性赋值后,整个初始化过程就完成了。这意味着一旦 `name` 属性被赋值后,`Country` 的构造器就能引用并传递隐式的 `self`。`Country` 的构造器在赋值 `capitalCity` 时,就能将 `self` 作为参数传递给 `City` 的构造器。 + +以上的意义在于你可以通过一条语句同时创建 `Country` 和 `City` 的实例,而不产生循环强引用,并且 `capitalCity` 的属性能被直接访问,而不需要通过感叹号来展开它的可选值: + +```swift +var country = Country(name: "Canada", capitalName: "Ottawa") +print("\(country.name)'s capital city is called \(country.capitalCity.name)") +// 打印“Canada's capital city is called Ottawa” +``` + +在上面的例子中,使用隐式解包可选值值意味着满足了类的构造器的两个构造阶段的要求。`capitalCity` 属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。 + +## 闭包的循环强引用 {#strong-reference-cycles-for-closures} + +前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。 + +循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如 `self.someProperty`,或者闭包中调用了实例的某个方法,例如 `self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。 + +循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。 + +Swift 提供了一种优雅的方法来解决这个问题,称之为 `闭包捕获列表`(closure capture list)。同样的,在学习如何用闭包捕获列表打破循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。 + +下面的例子为你展示了当一个闭包引用了 `self` 后是如何产生一个循环强引用的。例子中定义了一个叫 `HTMLElement` 的类,用一种简单的模型表示 HTML 文档中的一个单独的元素: + +```swift +class HTMLElement { + + let name: String + let text: String? + + lazy var asHTML: () -> String = { + if let text = self.text { + return "<\(self.name)>\(text)" + } else { + return "<\(self.name) />" + } + } + + init(name: String, text: String? = nil) { + self.name = name + self.text = text + } + + deinit { + print("\(name) is being deinitialized") + } + +} +``` + +`HTMLElement` 类定义了一个 `name` 属性来表示这个元素的名称,例如代表头部元素的 `"h1"`,代表段落的 `"p"`,或者代表换行的 `"br"`。`HTMLElement` 还定义了一个可选属性 `text`,用来设置 HTML 元素呈现的文本。 + +除了上面的两个属性,`HTMLElement` 还定义了一个 `lazy` 属性 `asHTML`。这个属性引用了一个将 `name` 和 `text` 组合成 HTML 字符串片段的闭包。该属性是 `Void -> String` 类型,或者可以理解为“一个没有参数,返回 `String` 的函数”。 + +默认情况下,闭包赋值给了 `asHTML` 属性,这个闭包返回一个代表 HTML 标签的字符串。如果 `text` 值存在,该标签就包含可选值 `text`;如果 `text` 不存在,该标签就不包含文本。对于段落元素,根据 `text` 是 `"some text"` 还是 `nil`,闭包会返回 `"

some text

"` 或者 `"

"`。 + +可以像实例方法那样去命名、使用 `asHTML` 属性。然而,由于 `asHTML` 是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。 + +例如,可以将一个闭包赋值给 `asHTML` 属性,这个闭包能在 `text` 属性是 `nil` 时使用默认文本,这是为了避免返回一个空的 HTML 标签: + +```swift +let heading = HTMLElement(name: "h1") +let defaultText = "some default text" +heading.asHTML = { + return "<\(heading.name)>\(heading.text ?? defaultText)" +} +print(heading.asHTML()) +// 打印“

some default text

” +``` + +> 注意 +> +> `asHTML` 声明为 `lazy` 属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用 `asHTML`。也就是说,在默认的闭包中可以使用 `self`,因为只有当初始化完成以及 `self` 确实存在后,才能访问 `lazy` 属性。 + +`HTMLElement` 类只提供了一个构造器,通过 `name` 和 `text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构器,当 `HTMLElement` 实例被销毁时,打印一条消息。 + +下面的代码展示了如何用 `HTMLElement` 类创建实例并打印消息: + +```swift +var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") +print(paragraph!.asHTML()) +// 打印“

hello, world

” +``` + +> 注意 +> +> 上面的 `paragraph` 变量定义为可选类型的 `HTMLElement`,因此我们可以赋值 `nil` 给它来演示循环强引用。 + +不幸的是,上面写的 `HTMLElement` 类产生了类实例和作为 `asHTML` 默认值的闭包之间的循环强引用。循环强引用如下图所示: + +![](https://docs.swift.org/swift-book/_images/closureReferenceCycle01_2x.png) + +实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name` 和 `self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考 [值捕获](./07_Closures.md#capturing_values))。 + +> 注意 +> +> 虽然闭包多次使用了 `self`,它只捕获 `HTMLElement` 实例的一个强引用。 + +如果设置 `paragraph` 变量为 `nil`,打破它持有的 `HTMLElement` 实例的强引用,`HTMLElement` 实例和它的闭包都不会被销毁,也是因为循环强引用: + +```swift +paragraph = nil +``` + +注意,`HTMLElement` 的析构器中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。 + +## 解决闭包的循环强引用 {#resolving-strong-reference-cycles-for-closures} + +在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。 + +> 注意 +> +> Swift 有如下要求:只要在闭包内使用 `self` 的成员,就要用 `self.someProperty` 或者 `self.someMethod()`(而不只是 `someProperty` 或 `someMethod()`)。这提醒你可能会一不小心就捕获了 `self`。 + +### 定义捕获列表 {#defining-a-capture-list} + +捕获列表中的每一项都由一对元素组成,一个元素是 `weak` 或 `unowned` 关键字,另一个元素是类实例的引用(例如 `self`)或初始化过的变量(如 `delegate = self.delegate!`)。这些项在方括号中用逗号分开。 + +如果闭包有参数列表和返回类型,把捕获列表放在它们前面: + +```swift +lazy var someClosure: (Int, String) -> String = { + [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in + // 这里是闭包的函数体 +} +``` + +如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 `in` 放在闭包最开始的地方: + +```swift +lazy var someClosure: () -> String = { + [unowned self, weak delegate = self.delegate!] in + // 这里是闭包的函数体 +} +``` + +### 弱引用和无主引用 {#weak-and-unowned-references} + +在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 `无主引用`。 + +相反的,在被捕获的引用可能会变为 `nil` 时,将闭包内的捕获定义为 `弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 `nil`。这使我们可以在闭包体内检查它们是否存在。 + +> 注意 +> +> 如果被捕获的引用绝对不会变为 `nil`,应该用无主引用,而不是弱引用。 + +前面的 `HTMLElement` 例子中,无主引用是正确的解决循环强引用的方法。这样编写 `HTMLElement` 类来避免循环强引用: + +```swift +class HTMLElement { + + let name: String + let text: String? + + lazy var asHTML: () -> String = { + [unowned self] in + if let text = self.text { + return "<\(self.name)>\(text)" + } else { + return "<\(self.name) />" + } + } + + init(name: String, text: String? = nil) { + self.name = name + self.text = text + } + + deinit { + print("\(name) is being deinitialized") + } + +} +``` + +上面的 `HTMLElement` 实现和之前的实现一致,除了在 `asHTML` 闭包中多了一个捕获列表。这里,捕获列表是 `[unowned self]`,表示“将 `self` 捕获为无主引用而不是强引用”。 + +和之前一样,我们可以创建并打印 `HTMLElement` 实例: + +```swift +var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") +print(paragraph!.asHTML()) +// 打印“

hello, world

” +``` + +使用捕获列表后引用关系如下图所示: + +![](https://docs.swift.org/swift-book/_images/closureReferenceCycle02_2x.png) + +这一次,闭包以无主引用的形式捕获 `self`,并不会持有 `HTMLElement` 实例的强引用。如果将 `paragraph` 赋值为 `nil`,`HTMLElement` 实例将会被销毁,并能看到它的析构器打印出的消息: + +```swift +paragraph = nil +// 打印“p is being deinitialized” +``` + +你可以查看 [捕获列表](../chapter3/04_Expressions.html) 章节,获取更多关于捕获列表的信息。 diff --git a/source/chapter2/25_Access_Control.md b/source/chapter2/25_Access_Control.md index 680b217e..fb018e37 100644 --- a/source/chapter2/25_Access_Control.md +++ b/source/chapter2/25_Access_Control.md @@ -87,7 +87,7 @@ fileprivate func someFilePrivateFunction() {} private func somePrivateFunction() {} ``` -除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的 `internal`: +除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅 [默认访问级别](#default_access_levels) 这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的 `internal`: ```swift class SomeInternalClass {} // 隐式 internal @@ -147,7 +147,7 @@ func someFunction() -> (SomeInternalClass, SomePrivateClass) { } ``` -我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅[自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。 +我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅 [自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。 因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符,明确指定该函数的访问级别: @@ -282,13 +282,13 @@ public struct TrackedString { ## 构造器 {#initializers} -自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.md#required_initializers),它的访问级别必须和所属类型的访问级别相同。 +自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是 [必要构造器](./14_Initialization.md#required_initializers),它的访问级别必须和所属类型的访问级别相同。 如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。 ### 默认构造器 {#default-initializers} -如[默认构造器](./14_Initialization.md#default_initializers)所述,Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。 +如 [默认构造器](./14_Initialization.md#default_initializers) 所述,Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。 默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。 diff --git a/source/chapter2/26_Advanced_Operators.md b/source/chapter2/26_Advanced_Operators.md index ee1a99ae..fbd4d978 100644 --- a/source/chapter2/26_Advanced_Operators.md +++ b/source/chapter2/26_Advanced_Operators.md @@ -1,6 +1,6 @@ # 高级运算符 -除了之前介绍过的[基本运算符](./02_Basic_Operators.md),Swift 还提供了数种可以对数值进行复杂运算的高级运算符。它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。 +除了之前介绍过的 [基本运算符](./02_Basic_Operators.md),Swift 还提供了数种可以对数值进行复杂运算的高级运算符。它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。 与 C 语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。 @@ -210,7 +210,7 @@ unsignedOverflow = unsignedOverflow &- 1 ![Art/overflowUnsignedSubtraction_2x.png](https://docs.swift.org/swift-book/_images/overflowUnsignedSubtraction_2x.png) -溢出也会发生在有符号整型上。针对有符号整型的所有溢出加法或者减法运算都是按位运算的方式执行的,符号位也需要参与计算,正如[按位左移、右移运算符](#bitwise_left_and_right_shift_operators)所描述的。 +溢出也会发生在有符号整型上。针对有符号整型的所有溢出加法或者减法运算都是按位运算的方式执行的,符号位也需要参与计算,正如 [按位左移、右移运算符](#bitwise_left_and_right_shift_operators) 所描述的。 ```Swift var signedOverflow = Int8.min @@ -266,7 +266,7 @@ signedOverflow = signedOverflow &- 1 因此计算结果为 `17`。 -有关 Swift 标准库提供的操作符信息,包括操作符优先级组和结核性设置的完整列表,请参见[操作符声明](https://developer.apple.com/documentation/swift/operator_declarations)。 +有关 Swift 标准库提供的操作符信息,包括操作符优先级组和结核性设置的完整列表,请参见 [操作符声明](https://developer.apple.com/documentation/swift/operator_declarations)。 > 注意 > @@ -414,7 +414,7 @@ if twoThreeFour == anotherTwoThreeFour { ## 自定义运算符 {#custom-operators} -除了实现标准运算符,在 Swift 中还可以声明和实现*自定义运算符*。可以用来自定义运算符的字符列表请参考[运算符](../chapter3/02_Lexical_Structure.html#operators)。 +除了实现标准运算符,在 Swift 中还可以声明和实现*自定义运算符*。可以用来自定义运算符的字符列表请参考 [运算符](../chapter3/02_Lexical_Structure.html#operators)。 新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix`、`infix` 或者 `postfix` 修饰符: @@ -440,7 +440,7 @@ let afterDoubling = +++toBeDoubled ### 自定义中缀运算符的优先级 {#precedence-and-associativity-for-custom-infix-operators} -每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。 +每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence_and_associativity) 中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。 而没有明确放入某个优先级组的自定义中缀运算符将会被放到一个默认的优先级组内,其优先级高于三元运算符。 @@ -459,7 +459,7 @@ let plusMinusVector = firstVector +- secondVector // plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0) ``` -这个运算符把两个向量的 `x` 值相加,同时从第一个向量的 `y` 中减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置在 `+` 和 `-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考[运算符声明](./06_Declarations.md#operator_declaration)。 +这个运算符把两个向量的 `x` 值相加,同时从第一个向量的 `y` 中减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置在 `+` 和 `-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考 [运算符声明](./06_Declarations.md#operator_declaration)。 > 注意 > diff --git a/source/chapter3/03_Types.md b/source/chapter3/03_Types.md index d03f180c..314abac3 100644 --- a/source/chapter3/03_Types.md +++ b/source/chapter3/03_Types.md @@ -190,7 +190,7 @@ func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) { 上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为参数 `first` 和 `second` 是非逃逸函数,它们不能够作为参数被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不会产生编译错误。这些函数调用不会违反限制,因为 `external` 不是 `takesTwoFunctions(first:second:)` 的参数之一。 -如果你需要避免这个限制,标记其中之一的参数为逃逸,或者使用 `withoutActuallyEscaping(_:do:)` 函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅[内存安全](../chapter2/24_Memory_Safety.md)。 +如果你需要避免这个限制,标记其中之一的参数为逃逸,或者使用 `withoutActuallyEscaping(_:do:)` 函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅 [内存安全](../chapter2/24_Memory_Safety.md)。 > 函数类型语法 > diff --git a/source/chapter3/04_Expressions.md b/source/chapter3/04_Expressions.md index 72e1a935..586d57d8 100644 --- a/source/chapter3/04_Expressions.md +++ b/source/chapter3/04_Expressions.md @@ -1,1248 +1,1248 @@ -# 表达式(Expressions) - -Swift 中存在四种表达式:前缀表达式,二元表达式,基本表达式和后缀表达式。表达式在返回一个值的同时还可以引发副作用。 - -通过前缀表达式和二元表达式可以对简单表达式使用各种运算符。基本表达式从概念上讲是最简单的一种表达式,它是一种访问值的方式。后缀表达式则允许你建立复杂的表达式,例如函数调用和成员访问。每种表达式都在下面有详细论述。 - -> 表达式语法 -> - -#### expression {#expression} -> *表达式* → [*try 运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) [*二元表达式列表*](#binary-expressions)可选 -> - -#### expression-list {#expression-list} -> *表达式列表* → [*表达式*](#expression) | [*表达式*](#expression) **,** [*表达式列表*](#expression-list) -> - -## 前缀表达式 {#prefix-expressions} -前缀表达式由可选的前缀运算符和表达式组成。前缀运算符只接收一个参数,表达式则紧随其后。 - -关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md)。 - -关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Operators Declarations*](https://developer.apple.com/documentation/swift/operator_declarations)。 - -除了标准库运算符,你也可以对某个变量使用 `&` 运算符,从而将其传递给函数的输入输出参数。更多信息,请参阅 [输入输出参数](../chapter2/06_Functions.md#in_out_parameters)。 - -> 前缀表达式语法 -> - -#### prefix-expression {#prefix-expression} -> *前缀表达式* → [*前缀运算符*](./02_Lexical_Structure.md#prefix-operator)可选 [*后缀表达式*](#postfix-expression) -> -> *前缀表达式* → [*输入输出表达式*](#in-out-expression) -> - -#### in-out-expression {#in-out-expression} -> *输入输出表达式* → **&** [*标识符*](./02_Lexical_Structure.md#identifier) -> - -### Try 运算符 {#try-operator} -try 表达式由 `try` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: - -> try `可抛出错误的表达式` -> - -可选的 try 表达式由 `try?` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: - -> try? `可抛出错误的表达式` -> - -如果可抛出错误的表达式没有抛出错误,整个表达式返回的可选值将包含可抛出错误的表达式的返回值,否则,该可选值为 `nil`。 - -强制的 try 表达式由 `try!` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: - -> try! `可抛出错误的表达式` -> - -如果可抛出错误的表达式抛出了错误,将会引发运行时错误。 - -在二元运算符左侧的表达式被标记上 `try`、`try?` 或者 `try!` 时,这个运算符对整个二元表达式都产生作用。也就是说,你可以使用括号来明确运算符的作用范围。 - -```swift -sum = try someThrowingFunction() + anotherThrowingFunction() // try 对两个函数调用都产生作用 -sum = try (someThrowingFunction() + anotherThrowingFunction()) // try 对两个函数调用都产生作用 -sum = (try someThrowingFunction()) + anotherThrowingFunction() // 错误:try 只对第一个函数调用产生作用 -``` - -`try` 表达式不能出现在二元运算符的的右侧,除非二元运算符是赋值运算符或者 `try` 表达式是被圆括号括起来的。 - -关于 `try`、`try?` 和 `try!` 的更多信息,以及该如何使用的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md)。 -> Try 表达式语法 -> - -#### try-operator {#try-operator} -> *try 运算符* → **try** | **try?** | **try!** -> - -## 二元表达式 {#binary-expressions} -*二元表达式*由中缀运算符和左右参数表达式组成。形式如下: - -> `左侧参数` `二元运算符` `右侧参数` -> - -关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md)。 - -关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/documentation/swift/operator_declarations)。 - -> 注意 -> -> 在解析时,一个二元表达式将作为一个扁平列表表示,然后根据运算符的优先级,再进一步进行组合。例如,`2 + 3 * 5` 首先被看作具有五个元素的列表,即 `2`、`+`、`3`、`*`、`5`,随后根据运算符优先级组合为 `(2 + (3 * 5))`。 -> - - -#### binary-expression {#binary-expression} -> 二元表达式语法 -> -> *二元表达式* → [*二元运算符*](./02_Lexical_Structure.md#binary-operator) [*前缀表达式*](#prefix-expression) -> -> *二元表达式* → [*赋值运算符*](#assignment-operator) [*try 运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) -> -> *二元表达式* → [*条件运算符*](#conditional-operator) [*try 运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) -> -> *二元表达式* → [*类型转换运算符*](#type-casting-operator) -> - -#### binary-expressions {#binary-expressions} -> *二元表达式列表* → [*二元表达式*](#binary-expression) [*二元表达式列表*](#binary-expressions)可选 -> - -### 赋值表达式 {#assignment-operator} -赋值表达式会为某个给定的表达式赋值,形式如下; - -> `表达式` = `值` -> - -右边的值会被赋值给左边的表达式。如果左边表达式是一个元组,那么右边必须是一个具有同样元素个数的元组。(嵌套元组也是允许的。)右边的值中的每一部分都会被赋值给左边的表达式中的相应部分。例如: - -```swift -(a, _, (b, c)) = ("test", 9.45, (12, 3)) -// a 为 "test",b 为 12,c 为 3,9.45 会被忽略 -``` - -赋值运算符不返回任何值。 - -> 赋值运算符语法 -> - -#### assignment-operator {#assignment-operator} -> *赋值运算符* → **=** -> - -### 三元条件运算符 {#ternary-conditional-operator} -*三元条件运算符*会根据条件来对两个给定表达式中的一个进行求值,形式如下: - -> `条件` ? `表达式(条件为真则使用)` : `表达式(条件为假则使用)` -> - -如果条件为真,那么对第一个表达式进行求值并返回结果。否则,对第二个表达式进行求值并返回结果。未使用的表达式不会进行求值。 - -关于使用三元条件运算符的例子,请参阅 [三元条件运算符](../chapter2/02_Basic_Operators.md#ternary_conditional_operator)。 - -> 三元条件运算符语法 -> - -#### conditional-operator {#conditional-operator} -> *三元条件运算符* → **?** [*表达式*](#expression) **:** -> - -### 类型转换运算符 {#type-casting-operators} -有 4 种类型转换运算符:`is`、`as`、`as? ` 和 `as!`。它们有如下的形式: - -> `表达式` is `类型` -> -> `表达式` as `类型` -> -> `表达式` as? `类型` -> -> `表达式` as! `类型` -> - -`is` 运算符在运行时检查表达式能否向下转化为指定的类型,如果可以则返回 `ture`,否则返回 `false`。 - -`as` 运算符在编译时执行向上转换和桥接。向上转换可将表达式转换成父类的实例而无需使用任何中间变量。以下表达式是等价的: - -```swift -func f(any: Any) { print("Function for Any") } -func f(int: Int) { print("Function for Int") } -let x = 10 -f(x) -// 打印“Function for Int” - -let y: Any = x -f(y) -// 打印“Function for Any” - -f(x as Any) -// 打印“Function for Any” -``` - -桥接可将 Swift 标准库中的类型(例如 `String`)作为一个与之相关的 Foundation 类型(例如 `NSString`)来使用,而不需要新建一个实例。关于桥接的更多信息,请参阅 [*Working with Foundation Types*](https://developer.apple.com/documentation/swift/imported_c_and_objective_c_apis/working_with_foundation_types)。 - -`as?` 运算符有条件地执行类型转换,返回目标类型的可选值。在运行时,如果转换成功,返回的可选值将包含转换后的值,否则返回 `nil`。如果在编译时就能确定转换一定会成功或是失败,则会导致编译报错。 - -`as!` 运算符执行强制类型转换,返回目标类型的非可选值。如果转换失败,则会导致运行时错误。表达式 `x as! T` 效果等同于 `(x as? T)!`。 - -关于类型转换的更多内容和例子,请参阅 [类型转换](../chapter2/18_Type_Casting.md)。 - - -#### type-casting-operator {#type-casting-operator} -> 类型转换运算符语法 -> -> *类型转换运算符* → **is** [*类型*](./03_Types.md#type) -> -> *类型转换运算符* → **as** [*类型*](./03_Types.md#type) -> -> *类型转换运算符* → **as** **?** [*类型*](./03_Types.md#type) -> -> *类型转换运算符* → **as** **!** [*类型*](./03_Types.md#type) -> - -## 基本表达式 {#primary-expressions} -*基本表达式*是最基本的表达式。它们可以单独使用,也可以跟前缀表达式、二元表达式、后缀表达式组合使用。 - -> 基本表达式语法 -> - -#### primary-expression {#primary-expression} -> *基本表达式* → [*标识符*](./02_Lexical_Structure.md#identifier) [*泛型实参子句*](./09_Generic_Parameters_and_Arguments.md#generic-argument-clause)可选 -> -> *基本表达式* → [*字面量表达式*](#literal-expression) -> -> *基本表达式* → [*self 表达式*](#self-expression) -> -> *基本表达式* → [*父类表达式*](#superclass-expression) -> -> *基本表达式* → [*闭包表达式*](#closure-expression) -> -> *基本表达式* → [*圆括号表达式*](#parenthesized-expression) -> -> *基本表达式* → [*隐式成员表达式*](#implicit-member-expression) -> -> *基本表达式* → [*通配符表达式*](#wildcard-expression) -> -> *基本表达式* → [*选择器表达式*](#selector-expression) -> -> *基本表达式* → [*key-path字符串表达式*](#key-patch-string-expression) -> - -### 字面量表达式 {#literal-expression} -*字面量表达式*可由普通字面量(例如字符串或者数字),字典或者数组字面量,或者下面列表中的特殊字面量组成: - -字面量 | 类型 | 值 -:------------- | :---------- | :---------- -`#file` | `String` | 所在的文件名 -`#line` | `Int` | 所在的行数 -`#column` | `Int` | 所在的列数 -`#function` | `String` | 所在的声明的名字 - - -对于 `#function`,在函数中会返回当前函数的名字,在方法中会返回当前方法的名字,在属性的存取器中会返回属性的名字,在特殊的成员如 `init` 或 `subscript` 中会返回这个关键字的名字,在某个文件中会返回当前模块的名字。 - -当其作为函数或者方法的默认参数值时,该字面量的值取决于函数或方法的调用环境。 - -```swift -func logFunctionName(string: String = #function) { - print(string) -} -func myFunction() { - logFunctionName() -} -myFunction() // 打印“myFunction()” -``` - -数组字面量是值的有序集合,形式如下: - -> [`值 1`, `值 2`, `...`] -> - -数组中的最后一个表达式可以紧跟一个逗号。数组字面量的类型是 `[T]`,这个 `T` 就是数组中元素的类型。如果数组中包含多种类型,`T` 则是跟这些类型最近的的公共父类型。空数组字面量由一组方括号定义,可用来创建特定类型的空数组。 - -```swift -var emptyArray: [Double] = [] -``` - -字典字面量是一个包含无序键值对的集合,形式如下: - -> [`键 1` : `值 1`, `键 2` : `值 2`, `...`] -> - -字典中的最后一个表达式可以紧跟一个逗号。字典字面量的类型是 `[Key : Value]`,`Key` 表示键的类型,`Value` 表示值的类型。如果字典中包含多种类型,那么 `Key` 表示的类型则为所有键最接近的公共父类型,`Value` 与之相似。一个空的字典字面量由方括号中加一个冒号组成(`[:]`),从而与空数组字面量区分开,可以使用空字典字面量来创建特定类型的字典。 - -```swift -var emptyDictionary: [String : Double] = [:] -``` - -Xcode 使用 playground 字面量对程序编辑器中的颜色、文件或者图片创建可交互的展示。在 Xcode 之外的空白文本中,playground 字面量使用一种特殊的字面量语法来展示。 - -更多关于在 Xcode 中使用 playground 字面量的信息,请参阅 [添加颜色、文件或图片字面量](https://help.apple.com/xcode/mac/current/#/dev4c60242fc) - -> 字面量表达式语法 -> -> -> -#### literal-expression {#literal-expression} -> -> *字面量表达式* → [*字面量*](./02_Lexical_Structure.md#literal) -> -> *字面量表达式* → [*数组字面量*](#array-literal) | [*字典字面量*](#dictionary-literal) | [*练习场字面量*](#playground-literal) -> -> *字面量表达式* → **#file** | **#line** | **#column** | **#function** -> - - -> -#### array-literal {#array-literal} -> -> *数组字面量* → [[*数组字面量项列表*](#array-literal-items)可选 **]** -> -> -#### array-literal-items {#array-literal-items} -> -> *数组字面量项列表* → [*数组字面量项*](#array-literal-item) **,**可选 | [*数组字面量项*](#array-literal-item) **,** [*数组字面量项列表*](#array-literal-items) -> -> -#### array-literal-item {#array-literal-item} -> -> *数组字面量项* → [*表达式*](#expression) -> -> -> -#### dictionary-literal {#dictionary-literal} -> -> *字典字面量* → [[*字典字面量项列表*](#dictionary-literal-items) **]** | **[** **:** **]** -> -> -#### dictionary-literal-items {#dictionary-literal-items} -> -> *字典字面量项列表* → [*字典字面量项*](#dictionary-literal-item) **,**可选 | [*字典字面量项*](#dictionary-literal-item) **,** [*字典字面量项列表*](#dictionary-literal-items) -> -> -#### dictionary-literal-item {#dictionary-literal-item} -> -> *字典字面量项* → [*表达式*](#expression) **:** [*表达式*](#expression)。 -> -> -#### playground-literal {#playground-literal} -> -> *playground 字面量* → **#colorLiteral ( red : [*表达式*](#expression) , green :[*表达式*](#expression) [*表达式*](#e[*表达式*](#expression) xpression) , blue :[*表达式*](#expression) , alpha : [*表达式*](#expression) )** -> -> *playground 字面量* → **#fileLiteral ( resourceName : [*表达式*](#expression) )** -> -> -#### playground 字面量* → **#imageLiteral ( resourceName : [*表达式*](#expression) )**self_expression {#self-expression} -> - -### Self 表达式 - -`self` 表达式是对当前类型或者当前实例的显式引用,它有如下形式: - -> self -> -> self.`成员名称` -> -> self[`下标索引`] -> -> self(`构造器参数`) -> -> self.init(`构造器参数`) -> - -如果在构造器、下标、实例方法中,`self` 引用的是当前类型的实例。在一个类型方法中,`self` 引用的是当前的类型。 - -当访问成员时,`self` 可用来区分重名变量,例如函数的参数: - -```swift -class SomeClass { - var greeting: String - init(greeting: String) { - self.greeting = greeting - } -} -``` - -在 `mutating` 方法中,你可以对 `self` 重新赋值: - -```swift -struct Point { - var x = 0.0, y = 0.0 - mutating func moveByX(deltaX: Double, y deltaY: Double) { - self = Point(x: x + deltaX, y: y + deltaY) - } -} -``` - -> Self 表达式语法 -> - -#### self-expression {#self-expression} -> *self 表达式* → **self** | [*self 方法表达式*](#self-method-expression) | [*self 下标表达式*](#self-subscript-expression) | [*self 构造器表达式*](#self-initializer-expression) -> -> - -#### self-method-expression {#self-method-expression} -> *self 方法表达式* → **self** **.** [*标识符*](./02_Lexical_Structure.md#identifier) -> - -#### self-subscript-expression {#self-subscript-expression} -> *self 下标表达式* → **self** **[** [*函数调用参数表*](#function-call-argument-list­) **]** -> - -#### self-initializer-expression {#self-initializer-expression} -> *self 构造器表达式* → **self** **.** **init** -> - -### 父类表达式 {#superclass-expression} -*父类*表达式可以使我们在某个类中访问它的父类,它有如下形式: - -> super.`成员名称` -> -> super[`下标索引`] -> -> super.init(`构造器参数`) -> - -第一种形式用来访问父类的某个成员,第二种形式用来访问父类的下标,第三种形式用来访问父类的构造器。 - -子类可以通过父类表达式在它们的成员、下标和构造器中使用父类中的实现。 - -> 父类表达式语法 -> - -#### superclass-expression {#superclass-expression} -> *父类表达式* → [*父类方法表达式*](#superclass-method-expression) | [*父类下标表达式*](#superclass-subscript-expression) | [*父类构造器表达式*](#superclass-initializer-expression) -> - -#### superclass-method-expression {#superclass-method-expression} -> *父类方法表达式* → **super** **.** [*标识符*](./02_Lexical_Structure.md#identifier) -> - -#### superclass-subscript-expression {#superclass-subscript-expression} -> *父类下标表达式* → **super** [[*函数调用参数表*](#function-call-argument-list­) **]** -> - -#### superclass-initializer-expression {#superclass-initializer-expression} -> *父类构造器表达式* → **super** **.** **init** -> - -### 闭包表达式 {#closure-expression} -*闭包表达式*会创建一个闭包,在其他语言中也叫 *lambda* 或*匿名*函数。跟函数一样,闭包包含了待执行的代码,不同的是闭包还会捕获所在环境中的常量和变量。它的形式如下: - -```swift -{ (parameters) -> return type in - statements -} -``` - -闭包的参数声明形式跟函数一样,请参阅 [函数声明](./06_Declarations.md#function_declaration)。 - -闭包还有几种特殊的形式,能让闭包使用起来更加简洁: - -- 闭包可以省略它的参数和返回值的类型。如果省略了参数名和所有的类型,也要省略 `in` 关键字。如果被省略的类型无法被编译器推断,那么就会导致编译错误。 -- 闭包可以省略参数名,参数会被隐式命名为 `$` 加上其索引位置,例如 `$0`、`$1`、`$2` 分别表示第一个、第二个、第三个参数,以此类推。 -- 如果闭包中只包含一个表达式,那么该表达式的结果就会被视为闭包的返回值。表达式结果的类型也会被推断为闭包的返回类型。 - -下面几个闭包表达式是等价的: - -```swift -myFunction { - (x: Int, y: Int) -> Int in - return x + y -} - -myFunction { - (x, y) in - return x + y -} - -myFunction { return $0 + $1 } - -myFunction { $0 + $1 } -``` - -关于如何将闭包作为参数来传递的内容,请参阅 [函数调用表达式](#function_call_expression)。 - -使用闭包表达式时,可以不必将其存储在一个变量或常量中,例如作为函数调用的一部分来立即使用一个闭包。在上面的例子中,传入 `myFunction` 的闭包表达式就是这种立即使用类型的闭包。因此,一个闭包是否逃逸与其使用时的上下文相关。一个会被立即调用或者作为函数的非逃逸参数传递的闭包表达式是非逃逸的,否则,这个闭包表达式是逃逸的。 - -关于逃逸闭包的内容,请参阅[逃逸闭包](./chapter2/07_Closures.md#escaping_closures) - -## 捕获列表 {#capture-lists} -默认情况下,闭包会捕获附近作用域中的常量和变量,并使用强引用指向它们。你可以通过一个*捕获列表*来显式指定它的捕获行为。 - -捕获列表在参数列表之前,由中括号括起来,里面是由逗号分隔的一系列表达式。一旦使用了捕获列表,就必须使用 `in` 关键字,即使省略了参数名、参数类型和返回类型。 - -捕获列表中的项会在闭包创建时被初始化。每一项都会用闭包附近作用域中的同名常量或者变量的值初始化。例如下面的代码示例中,捕获列表包含 `a` 而不包含 `b`,这将导致这两个变量具有不同的行为。 - -```swift -var a = 0 -var b = 0 -let closure = { [a] in - print(a, b) -} - -a = 10 -b = 10 -closure() -// 打印“0 10” -``` - -在示例中,变量 `b` 只有一个,然而,变量 `a` 有两个,一个在闭包外,一个在闭包内。闭包内的变量 `a` 会在闭包创建时用闭包外的变量 `a` 的值来初始化,除此之外它们并无其他联系。这意味着在闭包创建后,改变某个 `a` 的值都不会对另一个 `a` 的值造成任何影响。与此相反,闭包内外都是同一个变量 `b`,因此在闭包外改变其值,闭包内的值也会受影响。 - -如果闭包捕获的值具有引用语义则有所不同。例如,下面示例中,有两个变量 `x`,一个在闭包外,一个在闭包内,由于它们的值是引用语义,虽然这是两个不同的变量,它们却都引用着同一实例。 - -```swift -class SimpleClass { - var value: Int = 0 -} -var x = SimpleClass() -var y = SimpleClass() -let closure = { [x] in - print(x.value, y.value) -} - -x.value = 10 -y.value = 10 -closure() -// 打印“10 10” -``` - -如果捕获列表中的值是类类型,你可以使用 `weak` 或者 `unowned` 来修饰它,闭包会分别用弱引用和无主引用来捕获该值。 - -```swift -myFunction { print(self.title) } // 隐式强引用捕获 -myFunction { [self] in print(self.title) } // 显式强引用捕获 -myFunction { [weak self] in print(self!.title) } // 弱引用捕获 -myFunction { [unowned self] in print(self.title) } // 无主引用捕获 -``` - -在捕获列表中,也可以将任意表达式的值绑定到一个常量上。该表达式会在闭包被创建时进行求值,闭包会按照指定的引用类型来捕获表达式的值。例如: - -```swift -// 以弱引用捕获 self.parent 并赋值给 parent -myFunction { [weak parent = self.parent] in print(parent!.title) } -``` - -关于闭包表达式的更多信息和例子,请参阅 [闭包表达式](../chapter2/07_Closures.md#closure_expressions)。关于捕获列表的更多信息和例子,请参阅 [解决闭包引起的循环强引用](../chapter2/23_Automatic_Reference_Counting.md#resolving_strong_reference_cycles_for_closures)。 - -> 闭包表达式语法 -> -> -> -#### closure-expression {#closure-expression} -> -> *闭包表达式* → **{** [*闭包签名*](#closure-signature)可选 [*语句*](#statements) **}** -> -> -> -#### closure-signature {#closure-signature} -> -> -> 闭包签名* → [*参数子句*](#parameter-clause) [*函数结果*](05_Declarations.md#function-result)可选 **in** -> -> *闭包签名* → [*标识符列表*](#identifier-list) [*函数结果*](05_Declarations.md#function-result)可选 **in** -> -> *闭包签名* → [*捕获列表*](#capture-list) [*参数子句*](05_Declarations.md#parameter-clause) [*函数结果*](./06_Declarations.md#function-result)可选 **in** -> -> *闭包签名* → [*捕获列表*](#capture-list) [*标识符列表*](02_Lexical_Structure.md#identifier-list) [*函数结果*](./06_Declarations.md#function-result)可选 **in** -> -> *闭包签名* → [*捕获列表*](#capture-list) **in** -> -> -> -#### capture-list {#capture-list} -> -> -> 捕获列表* → [ [*捕获列表项列表*](#capture-list-items) **]** -> -> -#### capture-list-items {#capture-list-items} -> -> *捕获列表项列表* → [*捕获列表项*](#capture-list-item) | [*捕获列表项*](#capture-list-item) **,** [*捕获列表项列表*](#capture-list-items) -> -> -#### capture-list-item {#capture-list-item} -> -> *捕获列表项* → [*捕获说明符*](#capture-specifier)可选 [*表达式*](#expression) -> -> -#### capture-specifier {#capture-specifier} -> -> *捕获说明符* → **weak** | **unowned** | **unowned(safe)** | **unowned(unsafe)** -> - -### 隐式成员表达式 {#implicit-member-expression} -若类型可被推断出来,可以使用*隐式成员表达式*来访问某个类型的成员(例如某个枚举成员或某个类型方法),形式如下: - -> .`成员名称` -> - -例如: - -```swift -var x = MyEnumeration.SomeValue -x = .AnotherValue -``` - -> 隐式成员表达式语法 -> - -#### implicit-member-expression {#implicit-member-expression} -> *隐式成员表达式* → **.** [*标识符*](./02_Lexical_Structure.md#identifier) -> - -### 圆括号表达式 {#parenthesized-expression} -*圆括号表达式*是由圆括号包围的表达式。你可以用圆括号说明成组的表达式的先后操作。成组的圆括号不会改变表达式的类型 - 例如 `(1)` 的类型就是简单的 `Int`。 - -> 圆括号表达式语法 -> - -#### parenthesized-expression {#parenthesized-expression} -> *圆括号表达式* → **( [*表达式*](#expression) )** -> - -### 元组表达式 {#Tuple-Expression} -*元组表达式*由圆括号和其中多个逗号分隔的子表达式组成。每个子表达式前面可以有一个标识符,用冒号隔开。元组表达式形式如下: - -> (`标识符 1` : `表达式 1`, `标识符 2` : `表达式 2`, `...`) -> - -元组表达式可以一个表达式都没有,也可以包含两个或是更多的表达式。单个表达式用括号括起来就是括号表达式了。 - -> 注意 -> -> -> 在 Swift 中,空的元组表达式和空的元组类型都写作 `()`。由于 `Void` 是 `()` 的类型别名,因此可以使用它来表示空的元组类型。虽然如此,`Void` 就像所有的类型别名一样,永远是一个类型——不能表示空的元组表达式。 -> - - -> 元组表达式语法 -> - -#### tuple-expression {#tuple-expression} -> *元组表达式* → **( )** | **(**[*元组元素*](#tuple-element), [*元组元素列表*](#tuple-element-list) **)** -> - -#### tuple-element-list {#tuple-element-list} -> *元组元素列表* → [*元组元素*](#tuple-element) | [*元组元素*](#tuple-element) **,** [*元组元素列表*](#tuple-element-list) -> - -#### tuple-element {#tuple-element} -> *元组元素* → [*表达式*](#expression) | [*标识符*](identifier) **:** [*表达式*](#expression) -> - -### 通配符表达式 {#wildcard-expression} -*通配符表达式*可以在赋值过程中显式忽略某个值。例如下面的代码中,`10` 被赋值给 `x`,而 `20` 则被忽略: - -```swift -(x, _) = (10, 20) -// x 为 10,20 被忽略 -``` - -> 通配符表达式语法 -> - -#### wildcard-expression {#wildcard-expression} -> *通配符表达式* → **_** -> - - -### Key-path 表达式 {#key-path-expression} -Key-path 表达式引用一个类型的属性或下标。在动态语言中使场景可以使用 Key-path 表达式,例如观察键值对。格式为: - -> **\类型名.路径** -> - -*类型名*是一个具体类型的名称,包含任何泛型参数,例如 `String`、`[Int]` 或 `Set`。 - -*路径*可由属性名称、下标、可选链表达式或者强制解包表达式组成。以上任意 key-path 组件可以以任何顺序重复多次。 - -在编译期,key-path 表达式会被一个 [KeyPath](https://developer.apple.com/documentation/swift/keypath) 类的实例替换。 - -对于所有类型,都可以通过传递 key-path 参数到下标方法 `subscript(keyPath:)` 来访问它的值。例如: - -```swift -struct SomeStructure { - var someValue: Int -} - -let s = SomeStructure(someValue: 12) -let pathToProperty = \SomeStructure.someValue - -let value = s[keyPath: pathToProperty] -// 值为 12 -``` - -在一些可以通过类型推断来确定所访问的具体类型的上下文中,可以省略 key-path 前的类型名字。下面的代码使用 `\.someProperty` 代替了 `SomeClass.someProperty` : - -```swift -class SomeClass: NSObject { - @objc var someProperty: Int - init(someProperty: Int) { - self.someProperty = someProperty - } -} - -let c = SomeClass(someProperty: 10) -c.observe(\.someProperty) { object, change in - // ... -} -``` - -使用 `self` 作为路径可以创建一个恒等 key path (`\.self`)。恒等 key path 可以作为整个实例的引用,因此你仅需一步操作便可以利用它来访问以及修改其存储的所有数据。例如: - -```swift -var compoundValue = (a: 1, b: 2) -// 等价于 compoundValue = (a: 10, b: 20) -compoundValue[keyPath: \.self] = (a: 10, b: 20) -``` - -通过点语法,可以让路径包含多个属性名称,以此来访问某实例的属性的属性。下面的代码使用 key-path 表达式 `\OuterStructure.outer.someValue` 来访问 `OuterStructure` 类型中 `outer` 属性的 `someValue` 属性。 - -```swift -struct OuterStructure { - var outer: SomeStructure - init(someValue: Int) { - self.outer = SomeStructure(someValue: someValue) - } -} - -let nested = OuterStructure(someValue: 24) -let nestedKeyPath = \OuterStructure.outer.someValue - -let nestedValue = nested[keyPath: nestedKeyPath] -// nestedValue 的值为 24 -``` - -路径中也可以包含使用中括号的下标访问,只要下标访问的参数类型满足 `Hashable` 协议即可。下面的例子在 key path 中使用了下标来访问数组的第二个元素。 - -```swift -let greetings = ["hello", "hola", "bonjour", "안녕"] -let myGreeting = greetings[keyPath: \[String].[1]] -// myGreeting 的值为 'hola' -``` - -下标访问中使用的值可以是一个变量或者字面量,并且 key-path 表达式会使用值语义来捕获此值。下面的代码在 key-path 表达式和闭包中都使用了 `index` 变量来访问 `greetings` 数组的第三个元素。当 `index` 被修改时,key-path 表达式仍旧引用数组第三个元素,而闭包则使用了新的索引值。 - -```swift -var index = 2 -let path = \[String].[index] -let fn: ([String]) -> String = { strings in strings[index] } - -print(greetings[keyPath: path]) -// 打印 "bonjour" -print(fn(greetings)) -// 打印 "bonjour" - -// 将 'index' 设置为一个新的值不会影响到 'path' -index += 1 -print(greetings[keyPath: path]) -// 打印 "bonjour" - -// 'fn' 闭包使用了新值。 -print(fn(greetings)) -// 打印 "안녕" -``` - -路径可以使用可选链和强制解包。下面的代码在 key path 中使用了可选链来访问可选字符串的属性。 - -```swift -let firstGreeting: String? = greetings.first -print(firstGreeting?.count as Any) -// 打印 "Optional(5)" - -// 使用 key path 实现同样的功能 -let count = greetings[keyPath: \[String].first?.count] -print(count as Any) -// 打印 "Optional(5)" -``` - -可以混合使用各种 key-path 组件来访问一些深度嵌套类型的值。下面的代码通过组合不同的组件,使用 key-path 表达式访问了一个字典数组中不同的值和属性。 - -```swift -let interestingNumbers = ["prime": [2, 3, 5, 7, 11, 13, 17], - "triangular": [1, 3, 6, 10, 15, 21, 28], - "hexagonal": [1, 6, 15, 28, 45, 66, 91]] -print(interestingNumbers[keyPath: \[String: [Int]].["prime"]] as Any) -// 打印 "Optional([2, 3, 5, 7, 11, 13, 17])" -print(interestingNumbers[keyPath: \[String: [Int]].["prime"]![0]]) -// 打印 "2" -print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count]) -// 打印 "7" -print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count.bitWidth]) -// 打印 "64" -``` - -关于更多如何使用 key path 与 Objective-C APIs 交互的信息,请参阅 [在 Swift 中使用 Objective-C 运行时特性](https://developer.apple.com/documentation/swift/using_objective_c_runtime_features_in_swift)。关于更多 key-value 编程和 key-value 观察的信息,请参阅 [Key-Value 编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107i) 和 [Key-Value 观察编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i) - -> key-path 表达式语法 -> -> -> -#### key-path-expression {#key-path-expression} -> -> *key-path 表达式* → **\\** [类型](./03_Types.md#type)可选 **.** [多个 key-path 组件](#key-path-components) -> -> -#### key-path-components {#key-path-components} -> -> *多个 key-path 组件* → [key-path 组件](#key-path-component) | [key-path 组件](#key-path-component) **.** [多个 key-path 组件](#key-path-components) -> -> -#### key-path-component {#key-path-component} -> -> *key-path 组件* → [标识符](./02_Lexical_Structure.md#identifier) [多个 key-path 后缀](#key-path-postfixes)可选 | [多个 key-path 后缀](#key-path-postfixes) -> -> -#### key-path-postfixes {#key-path-postfixes} -> -> -#### *多个 key-path 后缀* → [key-path 后缀](#key-path-postfix) [多个 key-path 后缀](#key-path-postfixes)可选 key-path-postfixes {#key-path-postfixes} -> -> *key-path 后缀* → **?** | **!** | **self** | **\[** [函数调用参数表](#function-call-argument-list) **\]** -> - - - -### 选择器表达式 {#selector-expression} -*选择器表达式*可以让你通过选择器来引用在 Objective-C 中方法(method)和属性(property)的 setter 和 getter 方法。 - -> \#selector(方法名) -> -\#selector(getter: 属性名) -\#selector(setter: 属性名) - -方法名和属性名必须是存在于 Objective-C 运行时中的方法和属性的引用。选择器表达式的返回值是一个 Selector 类型的实例。例如: - -```swift -class SomeClass: NSObject { - let property: String - @objc(doSomethingWithInt:) - func doSomething(_ x: Int) { } - - init(property: String) { - self.property = property - } -} -let selectorForMethod = #selector(SomeClass.doSomething(_:)) -let selectorForPropertyGetter = #selector(getter: SomeClass.property) -``` - -当为属性的 getter 创建选择器时,属性名可以是变量属性或者常量属性的引用。但是当为属性的 setter 创建选择器时,属性名只可以是对变量属性的引用。 - -方法名称可以包含圆括号来进行分组,并使用 as 操作符来区分具有相同方法名但类型不同的方法,例如: - -```swift -extension SomeClass { - @objc(doSomethingWithString:) - func doSomething(_ x: String) { } -} -let anotherSelector = #selector(SomeClass.doSomething(_:) as (SomeClass) -> (String) -> Void) -``` - -由于选择器是在编译时创建的,因此编译器可以检查方法或者属性是否存在,以及是否在运行时暴露给了 Objective-C 。 - -> 注意 -> -> 虽然方法名或者属性名是个表达式,但是它不会被求值。 -> - -更多关于如何在 Swift 代码中使用选择器来与 Objective-C API 进行交互的信息,请参阅 [在 Swift 中使用 Objective-C 运行时特性](https://developer.apple.com/documentation/swift/using_objective_c_runtime_features_in_swift)。 - -> 选择器表达式语法 -> - -#### selector-expression {#selector-expression} -> *选择器表达式* → __#selector__ **(** [*表达式*](#expression) **)** -> -> *选择器表达式* → __#selector__ **(** [*getter:表达式*](#expression) **)** -> -> *选择器表达式* → __#selector__ **(** [*setter:表达式*](#expression) **)** -> - -## Key-path 字符串表达式 {#key-path-string-expressions} -key-path 字符串表达式可以访问一个引用 Objective-C 属性的字符串,通常在 key-value 编程和 key-value 观察 APIs 中使用。其格式如下: - -> `#keyPath` ( `属性名` ) -> - -属性名必须是一个可以在 Objective-C 运行时使用的属性的引用。在编译期,key-path 字符串表达式会被一个字符串字面量替换。例如: - -```swift -class SomeClass: NSObject { - @objc var someProperty: Int - init(someProperty: Int) { - self.someProperty = someProperty - } -} - -let c = SomeClass(someProperty: 12) -let keyPath = #keyPath(SomeClass.someProperty) - -if let value = c.value(forKey: keyPath) { - print(value) -} -// 打印 "12" -``` - -当在一个类中使用 key-path 字符串表达式时,可以省略类名,直接使用属性名来访问这个类的某个属性。 - -```swift -extension SomeClass { - func getSomeKeyPath() -> String { -> - return #keyPath(someProperty) - } -} -print(keyPath == c.getSomeKeyPath()) -// 打印 "true" -``` - -由于 key-path 字符串表达式在编译期才创建,编译期可以检查属性是否存在,以及属性是否暴露给 Objective-C 运行时。 - -关于更多如何使用 key path 与 Objective-C APIs 交互的信息,请参阅 [在 Swift 中使用 Objective-C 运行时特性](./https://developer.apple.com/documentation/swift/using_objective_c_runtime_features_in_swift)。关于更多 key-value 编程和 key-value 观察的信息,请参阅 [Key-Value 编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.md#//apple_ref/doc/uid/10000107i) 和 [Key-Value 观察编程](./https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.md#//apple_ref/doc/uid/10000177i) - -> 注意 -> -> -> 尽管*属性名*是一个表达式,但它永远不会被求值 -> - -> key-path 字符串表达式语法 -> -> -#### key-path-string-expression {#key-path-string-expression} -> -> *key-path 字符串表达式* → **#keyPath (** [表达式](#expression) **)** -> - -## 后缀表达式 {#postfix-expressions} -*后缀表达式*就是在某个表达式的后面运用后缀运算符或其他后缀语法。从语法构成上来看,基本表达式也是后缀表达式。 - -关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md)。 - -关于 Swift 标准库提供的运算符的更多信息,请参阅 [运算符定义](https://developer.apple.com/documentation/swift/operator_declarations)。 - -> 后缀表达式语法 -> - -#### postfix-expression {#postfix-expression} -> *后缀表达式* → [*基本表达式*](#primary-expression) -> -> *后缀表达式* → [*后缀表达式*](#postfix-expression) [*后缀运算符*](02_Lexical_Structure.md#postfix-operator) -> -> *后缀表达式* → [*函数调用表达式*](#function-call-expression) -> -> *后缀表达式* → [*构造器表达式*](#initializer-expression) -> -> *后缀表达式* → [*显式成员表达式*](#explicit-member-expression) -> -> *后缀表达式* → [*后缀 self 表达式*](#postfix-self-expression) -> -> *后缀表达式* → [*dynamicType 表达式*](#dynamic-type-expression) -> -> *后缀表达式* → [*下标表达式*](#subscript-expression) -> -> *后缀表达式* → [*强制取值表达式*](#forced-value-expression) -> -> *后缀表达式* → [*可选链表达式*](#optional-chaining-expression) -> - -### 函数调用表达式 {#function-call-expression} -*函数调用表达式*由函数名和参数列表组成,形式如下: - -> `函数名`(`参数 1`, `参数 2`) -> - -函数名可以是值为函数类型的任意表达式。 - -如果函数声明中指定了参数的名字,那么在调用的时候也必须得写出来。这种函数调用表达式具有以下形式: - -> `函数名`(`参数名 1`: `参数 1`, `参数名 2`: `参数 2`) -> - -如果函数的最后一个参数是函数类型,可以在函数调用表达式的尾部(右圆括号之后)加上一个闭包,该闭包会作为函数的最后一个参数。如下两种写法是等价的: - -```swift -// someFunction 接受整数和闭包参数 -someFunction(x, f: {$0 == 13}) -someFunction(x) {$0 == 13} -``` - -如果闭包是该函数的唯一参数,那么圆括号可以省略。 - -```swift -// someFunction 只接受一个闭包参数 -myData.someMethod() {$0 == 13} -myData.someMethod {$0 == 13} -``` - -> 函数调用表达式语法 -> -> -#### function-call-expression {#function-call-expression} -> -> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*函数调用参数子句*](#function-call-argument-clause) -> -> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*函数调用参数子句*](#function-call-argument-clause)可选 [*尾随闭包*](#trailing-closure) -> -> -> -#### function-call-argument-clause {#function-call-argument-clause} -> -> *函数调用参数子句* → **(** **)** | **(** [*函数调用参数表*](#function-call-argument-list) **)** -> -> -#### function-call-argument-list {#function-call-argument-list} -> -> *函数调用参数表* → [函数调用参数](#function-call-argument) | [函数调用参数](#function-call-argument) **,** [*函数调用参数表*](#function-call-argument-list) -> -> -#### function-call-argument {#function-call-argument} -> -> *函数调用参数* → [表达式](#expression) | [标识符](02_Lexical_Structure.md#identifier) **:** [*表达式*](#expression) -> -> *函数调用参数* → [运算符](./02_Lexical_Structure.md#operator) | [标识符](./02_Lexical_Structure.md#identifier) **:** [*运算符*](./02_Lexical_Structure.md#operator) -> -> -> -#### trailing-closure {#trailing-closure} -> -> *尾随闭包* → [*闭包表达式*](#closure-expression) -> - -### 构造器表达式 {#initializer-expression} -*构造器表达式*用于访问某个类型的构造器,形式如下: - -> `表达式`.init(`构造器参数`) -> - -你可以在函数调用表达式中使用构造器表达式来初始化某个类型的新实例。也可以使用构造器表达式来代理给父类构造器。 - -```swift -class SomeSubClass: SomeSuperClass { - override init() { - // 此处为子类构造过程 - super.init() - } -} -``` - -和函数类似,构造器表达式可以作为一个值。 例如: - -```swift -// 类型注解是必须的,因为 String 类型有多种构造器 -let initializer: Int -> String = String.init -let oneTwoThree = [1, 2, 3].map(initializer).reduce("", combine: +) -print(oneTwoThree) -// 打印“123” -``` - -如果通过名字来指定某个类型,可以不用构造器表达式而直接使用类型的构造器。在其他情况下,你必须使用构造器表达式。 - -```swift -let s1 = SomeType.init(data: 3) // 有效 -let s2 = SomeType(data: 1) // 有效 - -let s4 = someValue.dynamicType(data: 5) // 错误 -let s3 = someValue.dynamicType.init(data: 7) // 有效 -``` - -> 构造器表达式语法 -> - -#### initializer-expression {#initializer-expression} -> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init** -> -> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init** **(** [*参数名称*](#argument-names) **)** -> - -### 显式成员表达式 {#explicit-member-expression} -*显式成员表达式*允许我们访问命名类型、元组或者模块的成员,其形式如下: - -> `表达式`.`成员名` -> - -命名类型的某个成员在原始实现或者扩展中定义,例如: - -```swift -class SomeClass { - var someProperty = 42 -} -let c = SomeClass() -let y = c.someProperty // 访问成员 -``` - -元组的成员会隐式地根据表示它们出现顺序的整数来命名,以 0 开始,例如: - -```swift -var t = (10, 20, 30) -t.0 = t.1 -// 现在元组 t 为 (20, 20, 30) -``` - -对于模块的成员来说,只能直接访问顶级声明中的成员。 - -使用 `dynamicMemberLookup` 属性声明的类型包含可以在运行时查找的成员,具体请参阅 [属性](./07_Attributes.md) - -为了区分只有参数名有所不同的方法或构造器,在圆括号中写出参数名,参数名后紧跟一个冒号,对于没有参数名的参数,使用下划线代替参数名。而对于重载方法,则需使用类型注解进行区分。例如: - -```swift -class SomeClass { - func someMethod(x: Int, y: Int) {} - func someMethod(x: Int, z: Int) {} - func overloadedMethod(x: Int, y: Int) {} - func overloadedMethod(x: Int, y: Bool) {} -} -let instance = SomeClass() - -let a = instance.someMethod // 有歧义 -let b = instance.someMethod(_:y:) // 无歧义 - -let d = instance.overloadedMethod // 有歧义 -let d = instance.overloadedMethod(_:y:) // 有歧义 -let d: (Int, Bool) -> Void = instance.overloadedMethod(_:y:) // 无歧义 -``` - -如果点号(`.`)出现在行首,它会被视为显式成员表达式的一部分,而不是隐式成员表达式的一部分。例如如下代码所展示的被分为多行的链式方法调用: - -```swift -let x = [10, 3, 20, 15, 4] - .sort() - .filter { $0 > 5 } - .map { $0 * 100 } -``` - -> 显式成员表达式语法 -> - -#### explicit-member-expression {#explicit-member-expression} -> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*十进制数字*] (02_Lexical_Structure.md#decimal-digit) -> -> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*](02_Lexical_Structure.md#identifier) [*泛型实参子句*](./09_Generic_Parameters_and_Arguments.md#generic-argument-clause)可选
-> -> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*] (02_Lexical_Structure.md#identifier) **(** [*参数名称*](#argument-names) **)** -> - -#### argument-names {#argument-names} -> *参数名称* → [*参数名*](#argument-name) [*参数名称*](#argument-names)可选
-> - -#### argument-name {#argument-name} -> *参数名* → [*标识符*](./02_Lexical_Structure.md#identifier) **:** -> - -### 后缀 self 表达式 {#postfix-self-expression} -后缀 `self` 表达式由某个表达式或类型名紧跟 `.self` 组成,其形式如下: - -> `表达式`.self -> -> `类型`.self -> - -第一种形式返回表达式的值。例如:`x.self` 返回 `x`。 - -第二种形式返回相应的类型。我们可以用它来获取某个实例的类型作为一个值来使用。例如,`SomeClass.self` 会返回 `SomeClass` 类型本身,你可以将其传递给相应函数或者方法作为参数。 - -> 后缀 self 表达式语法 -> - -#### postfix-self-expression {#postfix-self-expression} -> *后缀 self 表达式* → [*后缀表达式*](#postfix-expression) **.** **self** -> - - -### 下标表达式 {#subscript-expression} -可通过*下标表达式*访问相应的下标,形式如下: - -> `表达式`[`索引表达式`] -> - -要获取下标表达式的值,可将索引表达式作为下标表达式的参数来调用下标 getter。下标 setter 的调用方式与之一样。 - -关于下标的声明,请参阅 [协议下标声明](./06_Declarations.md#protocol_subscript_declaration)。 - -> 下标表达式语法 -> - -#### subscript-expression {#subscript-expression} -> *下标表达式* → [*后缀表达式*](#postfix-expression) **[** [*表达式列表*](#expression-list) **]** -> - -### 强制取值表达式 {#forced-Value-expression} -当你确定可选值不是 `nil` 时,可以使用*强制取值表达式*来强制解包,形式如下: - -> `表达式`! -> - -如果该表达式的值不是 `nil`,则返回解包后的值。否则,抛出运行时错误。 - -返回的值可以被修改,无论是修改值本身,还是修改值的成员。例如: - -```swift -var x: Int? = 0 -x!++ -// x 现在是 1 - -var someDictionary = ["a": [1, 2, 3], "b": [10, 20]] -someDictionary["a"]![0] = 100 -// someDictionary 现在是 [b: [10, 20], a: [100, 2, 3]] -``` - -> 强制取值语法 -> - -#### forced-value-expression {#forced-value-expression} -> *强制取值表达式* → [*后缀表达式*](#postfix-expression) **!** -> - -### 可选链表达式 {#optional-chaining-expression} -*可选链表达式*提供了一种使用可选值的便捷方法,形式如下: - -> `表达式`? -> - -后缀 `?` 运算符会根据表达式生成可选链表达式而不会改变表达式的值。 - -如果某个后缀表达式包含可选链表达式,那么它的执行过程会比较特殊。如果该可选链表达式的值是 `nil`,整个后缀表达式会直接返回 `nil`。如果该可选链表达式的值不是 `nil`,则返回可选链表达式解包后的值,并将该值用于后缀表达式中剩余的表达式。在这两种情况下,整个后缀表达式的值都会是可选类型。 - -如果某个后缀表达式中包含了可选链表达式,那么只有最外层的表达式会返回一个可选类型。例如,在下面的例子中,如果 `c` 不是 `nil`,那么它的值会被解包,然后通过 `.property` 访问它的属性,接着进一步通过 `.performAction()` 调用相应方法。整个 `c?.property.performAction()` 表达式返回一个可选类型的值,而不是多重可选类型。 - -```swift -var c: SomeClass? -var result: Bool? = c?.property.performAction() -``` - -上面的例子跟下面的不使用可选链表达式的例子等价: - -```swift -var result: Bool? = nil -if let unwrappedC = c { - result = unwrappedC.property.performAction() -} -``` - -可选链表达式解包后的值可以被修改,无论是修改值本身,还是修改值的成员。如果可选链表达式的值为 `nil`,则表达式右侧的赋值操作不会被执行。例如: - -```swift -func someFunctionWithSideEffects() -> Int { - // 译者注:为了能看出此函数是否被执行,加上了一句打印 - print("someFunctionWithSideEffects") - return 42 -} -var someDictionary = ["a": [1, 2, 3], "b": [10, 20]] - -someDictionary["not here"]?[0] = someFunctionWithSideEffects() -// someFunctionWithSideEffects 不会被执行 -// someDictionary 依然是 ["b": [10, 20], "a": [1, 2, 3]] - -someDictionary["a"]?[0] = someFunctionWithSideEffects() -// someFunctionWithSideEffects 被执行并返回 42 -// someDictionary 现在是 ["b": [10, 20], "a": [42, 2, 3]] -``` - -> 可选链表达式语法 -> - -#### optional-chaining-expression {#optional-chaining-expression} -> *可选链表达式* → [*后缀表达式*](#postfix-expression) **?** -> +# 表达式(Expressions) + +Swift 中存在四种表达式:前缀表达式,二元表达式,基本表达式和后缀表达式。表达式在返回一个值的同时还可以引发副作用。 + +通过前缀表达式和二元表达式可以对简单表达式使用各种运算符。基本表达式从概念上讲是最简单的一种表达式,它是一种访问值的方式。后缀表达式则允许你建立复杂的表达式,例如函数调用和成员访问。每种表达式都在下面有详细论述。 + +> 表达式语法 +> + +#### expression {#expression} +> *表达式* → [*try 运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) [*二元表达式列表*](#binary-expressions)可选 +> + +#### expression-list {#expression-list} +> *表达式列表* → [*表达式*](#expression) | [*表达式*](#expression) **,** [*表达式列表*](#expression-list) +> + +## 前缀表达式 {#prefix-expressions} +前缀表达式由可选的前缀运算符和表达式组成。前缀运算符只接收一个参数,表达式则紧随其后。 + +关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md)。 + +关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Operators Declarations*](https://developer.apple.com/documentation/swift/operator_declarations)。 + +除了标准库运算符,你也可以对某个变量使用 `&` 运算符,从而将其传递给函数的输入输出参数。更多信息,请参阅 [输入输出参数](../chapter2/06_Functions.md#in_out_parameters)。 + +> 前缀表达式语法 +> + +#### prefix-expression {#prefix-expression} +> *前缀表达式* → [*前缀运算符*](./02_Lexical_Structure.md#prefix-operator)可选 [*后缀表达式*](#postfix-expression) +> +> *前缀表达式* → [*输入输出表达式*](#in-out-expression) +> + +#### in-out-expression {#in-out-expression} +> *输入输出表达式* → **&** [*标识符*](./02_Lexical_Structure.md#identifier) +> + +### Try 运算符 {#try-operator} +try 表达式由 `try` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: + +> try `可抛出错误的表达式` +> + +可选的 try 表达式由 `try?` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: + +> try? `可抛出错误的表达式` +> + +如果可抛出错误的表达式没有抛出错误,整个表达式返回的可选值将包含可抛出错误的表达式的返回值,否则,该可选值为 `nil`。 + +强制的 try 表达式由 `try!` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: + +> try! `可抛出错误的表达式` +> + +如果可抛出错误的表达式抛出了错误,将会引发运行时错误。 + +在二元运算符左侧的表达式被标记上 `try`、`try?` 或者 `try!` 时,这个运算符对整个二元表达式都产生作用。也就是说,你可以使用括号来明确运算符的作用范围。 + +```swift +sum = try someThrowingFunction() + anotherThrowingFunction() // try 对两个函数调用都产生作用 +sum = try (someThrowingFunction() + anotherThrowingFunction()) // try 对两个函数调用都产生作用 +sum = (try someThrowingFunction()) + anotherThrowingFunction() // 错误:try 只对第一个函数调用产生作用 +``` + +`try` 表达式不能出现在二元运算符的的右侧,除非二元运算符是赋值运算符或者 `try` 表达式是被圆括号括起来的。 + +关于 `try`、`try?` 和 `try!` 的更多信息,以及该如何使用的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md)。 +> Try 表达式语法 +> + +#### try-operator {#try-operator} +> *try 运算符* → **try** | **try?** | **try!** +> + +## 二元表达式 {#binary-expressions} +*二元表达式*由中缀运算符和左右参数表达式组成。形式如下: + +> `左侧参数` `二元运算符` `右侧参数` +> + +关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md)。 + +关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/documentation/swift/operator_declarations)。 + +> 注意 +> +> 在解析时,一个二元表达式将作为一个扁平列表表示,然后根据运算符的优先级,再进一步进行组合。例如,`2 + 3 * 5` 首先被看作具有五个元素的列表,即 `2`、`+`、`3`、`*`、`5`,随后根据运算符优先级组合为 `(2 + (3 * 5))`。 +> + + +#### binary-expression {#binary-expression} +> 二元表达式语法 +> +> *二元表达式* → [*二元运算符*](./02_Lexical_Structure.md#binary-operator) [*前缀表达式*](#prefix-expression) +> +> *二元表达式* → [*赋值运算符*](#assignment-operator) [*try 运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) +> +> *二元表达式* → [*条件运算符*](#conditional-operator) [*try 运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) +> +> *二元表达式* → [*类型转换运算符*](#type-casting-operator) +> + +#### binary-expressions {#binary-expressions} +> *二元表达式列表* → [*二元表达式*](#binary-expression) [*二元表达式列表*](#binary-expressions)可选 +> + +### 赋值表达式 {#assignment-operator} +赋值表达式会为某个给定的表达式赋值,形式如下; + +> `表达式` = `值` +> + +右边的值会被赋值给左边的表达式。如果左边表达式是一个元组,那么右边必须是一个具有同样元素个数的元组。(嵌套元组也是允许的。)右边的值中的每一部分都会被赋值给左边的表达式中的相应部分。例如: + +```swift +(a, _, (b, c)) = ("test", 9.45, (12, 3)) +// a 为 "test",b 为 12,c 为 3,9.45 会被忽略 +``` + +赋值运算符不返回任何值。 + +> 赋值运算符语法 +> + +#### assignment-operator {#assignment-operator} +> *赋值运算符* → **=** +> + +### 三元条件运算符 {#ternary-conditional-operator} +*三元条件运算符*会根据条件来对两个给定表达式中的一个进行求值,形式如下: + +> `条件` ? `表达式(条件为真则使用)` : `表达式(条件为假则使用)` +> + +如果条件为真,那么对第一个表达式进行求值并返回结果。否则,对第二个表达式进行求值并返回结果。未使用的表达式不会进行求值。 + +关于使用三元条件运算符的例子,请参阅 [三元条件运算符](../chapter2/02_Basic_Operators.md#ternary_conditional_operator)。 + +> 三元条件运算符语法 +> + +#### conditional-operator {#conditional-operator} +> *三元条件运算符* → **?** [*表达式*](#expression) **:** +> + +### 类型转换运算符 {#type-casting-operators} +有 4 种类型转换运算符:`is`、`as`、`as? ` 和 `as!`。它们有如下的形式: + +> `表达式` is `类型` +> +> `表达式` as `类型` +> +> `表达式` as? `类型` +> +> `表达式` as! `类型` +> + +`is` 运算符在运行时检查表达式能否向下转化为指定的类型,如果可以则返回 `ture`,否则返回 `false`。 + +`as` 运算符在编译时执行向上转换和桥接。向上转换可将表达式转换成父类的实例而无需使用任何中间变量。以下表达式是等价的: + +```swift +func f(any: Any) { print("Function for Any") } +func f(int: Int) { print("Function for Int") } +let x = 10 +f(x) +// 打印“Function for Int” + +let y: Any = x +f(y) +// 打印“Function for Any” + +f(x as Any) +// 打印“Function for Any” +``` + +桥接可将 Swift 标准库中的类型(例如 `String`)作为一个与之相关的 Foundation 类型(例如 `NSString`)来使用,而不需要新建一个实例。关于桥接的更多信息,请参阅 [*Working with Foundation Types*](https://developer.apple.com/documentation/swift/imported_c_and_objective_c_apis/working_with_foundation_types)。 + +`as?` 运算符有条件地执行类型转换,返回目标类型的可选值。在运行时,如果转换成功,返回的可选值将包含转换后的值,否则返回 `nil`。如果在编译时就能确定转换一定会成功或是失败,则会导致编译报错。 + +`as!` 运算符执行强制类型转换,返回目标类型的非可选值。如果转换失败,则会导致运行时错误。表达式 `x as! T` 效果等同于 `(x as? T)!`。 + +关于类型转换的更多内容和例子,请参阅 [类型转换](../chapter2/18_Type_Casting.md)。 + + +#### type-casting-operator {#type-casting-operator} +> 类型转换运算符语法 +> +> *类型转换运算符* → **is** [*类型*](./03_Types.md#type) +> +> *类型转换运算符* → **as** [*类型*](./03_Types.md#type) +> +> *类型转换运算符* → **as** **?** [*类型*](./03_Types.md#type) +> +> *类型转换运算符* → **as** **!** [*类型*](./03_Types.md#type) +> + +## 基本表达式 {#primary-expressions} +*基本表达式*是最基本的表达式。它们可以单独使用,也可以跟前缀表达式、二元表达式、后缀表达式组合使用。 + +> 基本表达式语法 +> + +#### primary-expression {#primary-expression} +> *基本表达式* → [*标识符*](./02_Lexical_Structure.md#identifier) [*泛型实参子句*](./09_Generic_Parameters_and_Arguments.md#generic-argument-clause)可选 +> +> *基本表达式* → [*字面量表达式*](#literal-expression) +> +> *基本表达式* → [*self 表达式*](#self-expression) +> +> *基本表达式* → [*父类表达式*](#superclass-expression) +> +> *基本表达式* → [*闭包表达式*](#closure-expression) +> +> *基本表达式* → [*圆括号表达式*](#parenthesized-expression) +> +> *基本表达式* → [*隐式成员表达式*](#implicit-member-expression) +> +> *基本表达式* → [*通配符表达式*](#wildcard-expression) +> +> *基本表达式* → [*选择器表达式*](#selector-expression) +> +> *基本表达式* → [*key-path字符串表达式*](#key-patch-string-expression) +> + +### 字面量表达式 {#literal-expression} +*字面量表达式*可由普通字面量(例如字符串或者数字),字典或者数组字面量,或者下面列表中的特殊字面量组成: + +字面量 | 类型 | 值 +:------------- | :---------- | :---------- +`#file` | `String` | 所在的文件名 +`#line` | `Int` | 所在的行数 +`#column` | `Int` | 所在的列数 +`#function` | `String` | 所在的声明的名字 + + +对于 `#function`,在函数中会返回当前函数的名字,在方法中会返回当前方法的名字,在属性的存取器中会返回属性的名字,在特殊的成员如 `init` 或 `subscript` 中会返回这个关键字的名字,在某个文件中会返回当前模块的名字。 + +当其作为函数或者方法的默认参数值时,该字面量的值取决于函数或方法的调用环境。 + +```swift +func logFunctionName(string: String = #function) { + print(string) +} +func myFunction() { + logFunctionName() +} +myFunction() // 打印“myFunction()” +``` + +数组字面量是值的有序集合,形式如下: + +> [`值 1`, `值 2`, `...`] +> + +数组中的最后一个表达式可以紧跟一个逗号。数组字面量的类型是 `[T]`,这个 `T` 就是数组中元素的类型。如果数组中包含多种类型,`T` 则是跟这些类型最近的的公共父类型。空数组字面量由一组方括号定义,可用来创建特定类型的空数组。 + +```swift +var emptyArray: [Double] = [] +``` + +字典字面量是一个包含无序键值对的集合,形式如下: + +> [`键 1` : `值 1`, `键 2` : `值 2`, `...`] +> + +字典中的最后一个表达式可以紧跟一个逗号。字典字面量的类型是 `[Key : Value]`,`Key` 表示键的类型,`Value` 表示值的类型。如果字典中包含多种类型,那么 `Key` 表示的类型则为所有键最接近的公共父类型,`Value` 与之相似。一个空的字典字面量由方括号中加一个冒号组成(`[:]`),从而与空数组字面量区分开,可以使用空字典字面量来创建特定类型的字典。 + +```swift +var emptyDictionary: [String : Double] = [:] +``` + +Xcode 使用 playground 字面量对程序编辑器中的颜色、文件或者图片创建可交互的展示。在 Xcode 之外的空白文本中,playground 字面量使用一种特殊的字面量语法来展示。 + +更多关于在 Xcode 中使用 playground 字面量的信息,请参阅 [添加颜色、文件或图片字面量](https://help.apple.com/xcode/mac/current/#/dev4c60242fc)。 + +> 字面量表达式语法 +> +> +> +#### literal-expression {#literal-expression} +> +> *字面量表达式* → [*字面量*](./02_Lexical_Structure.md#literal) +> +> *字面量表达式* → [*数组字面量*](#array-literal) | [*字典字面量*](#dictionary-literal) | [*练习场字面量*](#playground-literal) +> +> *字面量表达式* → **#file** | **#line** | **#column** | **#function** +> + + +> +#### array-literal {#array-literal} +> +> *数组字面量* → [[*数组字面量项列表*](#array-literal-items)可选 **]** +> +> +#### array-literal-items {#array-literal-items} +> +> *数组字面量项列表* → [*数组字面量项*](#array-literal-item) **,**可选 | [*数组字面量项*](#array-literal-item) **,** [*数组字面量项列表*](#array-literal-items) +> +> +#### array-literal-item {#array-literal-item} +> +> *数组字面量项* → [*表达式*](#expression) +> +> +> +#### dictionary-literal {#dictionary-literal} +> +> *字典字面量* → [[*字典字面量项列表*](#dictionary-literal-items) **]** | **[** **:** **]** +> +> +#### dictionary-literal-items {#dictionary-literal-items} +> +> *字典字面量项列表* → [*字典字面量项*](#dictionary-literal-item) **,**可选 | [*字典字面量项*](#dictionary-literal-item) **,** [*字典字面量项列表*](#dictionary-literal-items) +> +> +#### dictionary-literal-item {#dictionary-literal-item} +> +> *字典字面量项* → [*表达式*](#expression) **:** [*表达式*](#expression)。 +> +> +#### playground-literal {#playground-literal} +> +> *playground 字面量* → **#colorLiteral ( red : [*表达式*](#expression) , green :[*表达式*](#expression) [*表达式*](#e[*表达式*](#expression) xpression) , blue :[*表达式*](#expression) , alpha : [*表达式*](#expression) )** +> +> *playground 字面量* → **#fileLiteral ( resourceName : [*表达式*](#expression) )** +> +> +#### playground 字面量* → **#imageLiteral ( resourceName : [*表达式*](#expression) )**self_expression {#self-expression} +> + +### Self 表达式 + +`self` 表达式是对当前类型或者当前实例的显式引用,它有如下形式: + +> self +> +> self.`成员名称` +> +> self[`下标索引`] +> +> self(`构造器参数`) +> +> self.init(`构造器参数`) +> + +如果在构造器、下标、实例方法中,`self` 引用的是当前类型的实例。在一个类型方法中,`self` 引用的是当前的类型。 + +当访问成员时,`self` 可用来区分重名变量,例如函数的参数: + +```swift +class SomeClass { + var greeting: String + init(greeting: String) { + self.greeting = greeting + } +} +``` + +在 `mutating` 方法中,你可以对 `self` 重新赋值: + +```swift +struct Point { + var x = 0.0, y = 0.0 + mutating func moveByX(deltaX: Double, y deltaY: Double) { + self = Point(x: x + deltaX, y: y + deltaY) + } +} +``` + +> Self 表达式语法 +> + +#### self-expression {#self-expression} +> *self 表达式* → **self** | [*self 方法表达式*](#self-method-expression) | [*self 下标表达式*](#self-subscript-expression) | [*self 构造器表达式*](#self-initializer-expression) +> +> + +#### self-method-expression {#self-method-expression} +> *self 方法表达式* → **self** **.** [*标识符*](./02_Lexical_Structure.md#identifier) +> + +#### self-subscript-expression {#self-subscript-expression} +> *self 下标表达式* → **self** **[** [*函数调用参数表*](#function-call-argument-list­) **]** +> + +#### self-initializer-expression {#self-initializer-expression} +> *self 构造器表达式* → **self** **.** **init** +> + +### 父类表达式 {#superclass-expression} +*父类*表达式可以使我们在某个类中访问它的父类,它有如下形式: + +> super.`成员名称` +> +> super[`下标索引`] +> +> super.init(`构造器参数`) +> + +第一种形式用来访问父类的某个成员,第二种形式用来访问父类的下标,第三种形式用来访问父类的构造器。 + +子类可以通过父类表达式在它们的成员、下标和构造器中使用父类中的实现。 + +> 父类表达式语法 +> + +#### superclass-expression {#superclass-expression} +> *父类表达式* → [*父类方法表达式*](#superclass-method-expression) | [*父类下标表达式*](#superclass-subscript-expression) | [*父类构造器表达式*](#superclass-initializer-expression) +> + +#### superclass-method-expression {#superclass-method-expression} +> *父类方法表达式* → **super** **.** [*标识符*](./02_Lexical_Structure.md#identifier) +> + +#### superclass-subscript-expression {#superclass-subscript-expression} +> *父类下标表达式* → **super** [[*函数调用参数表*](#function-call-argument-list­) **]** +> + +#### superclass-initializer-expression {#superclass-initializer-expression} +> *父类构造器表达式* → **super** **.** **init** +> + +### 闭包表达式 {#closure-expression} +*闭包表达式*会创建一个闭包,在其他语言中也叫 *lambda* 或*匿名*函数。跟函数一样,闭包包含了待执行的代码,不同的是闭包还会捕获所在环境中的常量和变量。它的形式如下: + +```swift +{ (parameters) -> return type in + statements +} +``` + +闭包的参数声明形式跟函数一样,请参阅 [函数声明](./06_Declarations.md#function_declaration)。 + +闭包还有几种特殊的形式,能让闭包使用起来更加简洁: + +- 闭包可以省略它的参数和返回值的类型。如果省略了参数名和所有的类型,也要省略 `in` 关键字。如果被省略的类型无法被编译器推断,那么就会导致编译错误。 +- 闭包可以省略参数名,参数会被隐式命名为 `$` 加上其索引位置,例如 `$0`、`$1`、`$2` 分别表示第一个、第二个、第三个参数,以此类推。 +- 如果闭包中只包含一个表达式,那么该表达式的结果就会被视为闭包的返回值。表达式结果的类型也会被推断为闭包的返回类型。 + +下面几个闭包表达式是等价的: + +```swift +myFunction { + (x: Int, y: Int) -> Int in + return x + y +} + +myFunction { + (x, y) in + return x + y +} + +myFunction { return $0 + $1 } + +myFunction { $0 + $1 } +``` + +关于如何将闭包作为参数来传递的内容,请参阅 [函数调用表达式](#function_call_expression)。 + +使用闭包表达式时,可以不必将其存储在一个变量或常量中,例如作为函数调用的一部分来立即使用一个闭包。在上面的例子中,传入 `myFunction` 的闭包表达式就是这种立即使用类型的闭包。因此,一个闭包是否逃逸与其使用时的上下文相关。一个会被立即调用或者作为函数的非逃逸参数传递的闭包表达式是非逃逸的,否则,这个闭包表达式是逃逸的。 + +关于逃逸闭包的内容,请参阅 [逃逸闭包](./chapter2/07_Closures.md#escaping_closures)。 + +## 捕获列表 {#capture-lists} +默认情况下,闭包会捕获附近作用域中的常量和变量,并使用强引用指向它们。你可以通过一个*捕获列表*来显式指定它的捕获行为。 + +捕获列表在参数列表之前,由中括号括起来,里面是由逗号分隔的一系列表达式。一旦使用了捕获列表,就必须使用 `in` 关键字,即使省略了参数名、参数类型和返回类型。 + +捕获列表中的项会在闭包创建时被初始化。每一项都会用闭包附近作用域中的同名常量或者变量的值初始化。例如下面的代码示例中,捕获列表包含 `a` 而不包含 `b`,这将导致这两个变量具有不同的行为。 + +```swift +var a = 0 +var b = 0 +let closure = { [a] in + print(a, b) +} + +a = 10 +b = 10 +closure() +// 打印“0 10” +``` + +在示例中,变量 `b` 只有一个,然而,变量 `a` 有两个,一个在闭包外,一个在闭包内。闭包内的变量 `a` 会在闭包创建时用闭包外的变量 `a` 的值来初始化,除此之外它们并无其他联系。这意味着在闭包创建后,改变某个 `a` 的值都不会对另一个 `a` 的值造成任何影响。与此相反,闭包内外都是同一个变量 `b`,因此在闭包外改变其值,闭包内的值也会受影响。 + +如果闭包捕获的值具有引用语义则有所不同。例如,下面示例中,有两个变量 `x`,一个在闭包外,一个在闭包内,由于它们的值是引用语义,虽然这是两个不同的变量,它们却都引用着同一实例。 + +```swift +class SimpleClass { + var value: Int = 0 +} +var x = SimpleClass() +var y = SimpleClass() +let closure = { [x] in + print(x.value, y.value) +} + +x.value = 10 +y.value = 10 +closure() +// 打印“10 10” +``` + +如果捕获列表中的值是类类型,你可以使用 `weak` 或者 `unowned` 来修饰它,闭包会分别用弱引用和无主引用来捕获该值。 + +```swift +myFunction { print(self.title) } // 隐式强引用捕获 +myFunction { [self] in print(self.title) } // 显式强引用捕获 +myFunction { [weak self] in print(self!.title) } // 弱引用捕获 +myFunction { [unowned self] in print(self.title) } // 无主引用捕获 +``` + +在捕获列表中,也可以将任意表达式的值绑定到一个常量上。该表达式会在闭包被创建时进行求值,闭包会按照指定的引用类型来捕获表达式的值。例如: + +```swift +// 以弱引用捕获 self.parent 并赋值给 parent +myFunction { [weak parent = self.parent] in print(parent!.title) } +``` + +关于闭包表达式的更多信息和例子,请参阅 [闭包表达式](../chapter2/07_Closures.md#closure_expressions)。关于捕获列表的更多信息和例子,请参阅 [解决闭包引起的循环强引用](../chapter2/23_Automatic_Reference_Counting.md#resolving_strong_reference_cycles_for_closures)。 + +> 闭包表达式语法 +> +> +> +#### closure-expression {#closure-expression} +> +> *闭包表达式* → **{** [*闭包签名*](#closure-signature)可选 [*语句*](#statements) **}** +> +> +> +#### closure-signature {#closure-signature} +> +> +> 闭包签名* → [*参数子句*](#parameter-clause) [*函数结果*](05_Declarations.md#function-result)可选 **in** +> +> *闭包签名* → [*标识符列表*](#identifier-list) [*函数结果*](05_Declarations.md#function-result)可选 **in** +> +> *闭包签名* → [*捕获列表*](#capture-list) [*参数子句*](05_Declarations.md#parameter-clause) [*函数结果*](./06_Declarations.md#function-result)可选 **in** +> +> *闭包签名* → [*捕获列表*](#capture-list) [*标识符列表*](02_Lexical_Structure.md#identifier-list) [*函数结果*](./06_Declarations.md#function-result)可选 **in** +> +> *闭包签名* → [*捕获列表*](#capture-list) **in** +> +> +> +#### capture-list {#capture-list} +> +> +> 捕获列表* → [ [*捕获列表项列表*](#capture-list-items) **]** +> +> +#### capture-list-items {#capture-list-items} +> +> *捕获列表项列表* → [*捕获列表项*](#capture-list-item) | [*捕获列表项*](#capture-list-item) **,** [*捕获列表项列表*](#capture-list-items) +> +> +#### capture-list-item {#capture-list-item} +> +> *捕获列表项* → [*捕获说明符*](#capture-specifier)可选 [*表达式*](#expression) +> +> +#### capture-specifier {#capture-specifier} +> +> *捕获说明符* → **weak** | **unowned** | **unowned(safe)** | **unowned(unsafe)** +> + +### 隐式成员表达式 {#implicit-member-expression} +若类型可被推断出来,可以使用*隐式成员表达式*来访问某个类型的成员(例如某个枚举成员或某个类型方法),形式如下: + +> .`成员名称` +> + +例如: + +```swift +var x = MyEnumeration.SomeValue +x = .AnotherValue +``` + +> 隐式成员表达式语法 +> + +#### implicit-member-expression {#implicit-member-expression} +> *隐式成员表达式* → **.** [*标识符*](./02_Lexical_Structure.md#identifier) +> + +### 圆括号表达式 {#parenthesized-expression} +*圆括号表达式*是由圆括号包围的表达式。你可以用圆括号说明成组的表达式的先后操作。成组的圆括号不会改变表达式的类型 - 例如 `(1)` 的类型就是简单的 `Int`。 + +> 圆括号表达式语法 +> + +#### parenthesized-expression {#parenthesized-expression} +> *圆括号表达式* → **( [*表达式*](#expression) )** +> + +### 元组表达式 {#Tuple-Expression} +*元组表达式*由圆括号和其中多个逗号分隔的子表达式组成。每个子表达式前面可以有一个标识符,用冒号隔开。元组表达式形式如下: + +> (`标识符 1` : `表达式 1`, `标识符 2` : `表达式 2`, `...`) +> + +元组表达式可以一个表达式都没有,也可以包含两个或是更多的表达式。单个表达式用括号括起来就是括号表达式了。 + +> 注意 +> +> +> 在 Swift 中,空的元组表达式和空的元组类型都写作 `()`。由于 `Void` 是 `()` 的类型别名,因此可以使用它来表示空的元组类型。虽然如此,`Void` 就像所有的类型别名一样,永远是一个类型——不能表示空的元组表达式。 +> + + +> 元组表达式语法 +> + +#### tuple-expression {#tuple-expression} +> *元组表达式* → **( )** | **(**[*元组元素*](#tuple-element), [*元组元素列表*](#tuple-element-list) **)** +> + +#### tuple-element-list {#tuple-element-list} +> *元组元素列表* → [*元组元素*](#tuple-element) | [*元组元素*](#tuple-element) **,** [*元组元素列表*](#tuple-element-list) +> + +#### tuple-element {#tuple-element} +> *元组元素* → [*表达式*](#expression) | [*标识符*](identifier) **:** [*表达式*](#expression) +> + +### 通配符表达式 {#wildcard-expression} +*通配符表达式*可以在赋值过程中显式忽略某个值。例如下面的代码中,`10` 被赋值给 `x`,而 `20` 则被忽略: + +```swift +(x, _) = (10, 20) +// x 为 10,20 被忽略 +``` + +> 通配符表达式语法 +> + +#### wildcard-expression {#wildcard-expression} +> *通配符表达式* → **_** +> + + +### Key-path 表达式 {#key-path-expression} +Key-path 表达式引用一个类型的属性或下标。在动态语言中使场景可以使用 Key-path 表达式,例如观察键值对。格式为: + +> **\类型名.路径** +> + +*类型名*是一个具体类型的名称,包含任何泛型参数,例如 `String`、`[Int]` 或 `Set`。 + +*路径*可由属性名称、下标、可选链表达式或者强制解包表达式组成。以上任意 key-path 组件可以以任何顺序重复多次。 + +在编译期,key-path 表达式会被一个 [KeyPath](https://developer.apple.com/documentation/swift/keypath) 类的实例替换。 + +对于所有类型,都可以通过传递 key-path 参数到下标方法 `subscript(keyPath:)` 来访问它的值。例如: + +```swift +struct SomeStructure { + var someValue: Int +} + +let s = SomeStructure(someValue: 12) +let pathToProperty = \SomeStructure.someValue + +let value = s[keyPath: pathToProperty] +// 值为 12 +``` + +在一些可以通过类型推断来确定所访问的具体类型的上下文中,可以省略 key-path 前的类型名字。下面的代码使用 `\.someProperty` 代替了 `SomeClass.someProperty` : + +```swift +class SomeClass: NSObject { + @objc var someProperty: Int + init(someProperty: Int) { + self.someProperty = someProperty + } +} + +let c = SomeClass(someProperty: 10) +c.observe(\.someProperty) { object, change in + // ... +} +``` + +使用 `self` 作为路径可以创建一个恒等 key path (`\.self`)。恒等 key path 可以作为整个实例的引用,因此你仅需一步操作便可以利用它来访问以及修改其存储的所有数据。例如: + +```swift +var compoundValue = (a: 1, b: 2) +// 等价于 compoundValue = (a: 10, b: 20) +compoundValue[keyPath: \.self] = (a: 10, b: 20) +``` + +通过点语法,可以让路径包含多个属性名称,以此来访问某实例的属性的属性。下面的代码使用 key-path 表达式 `\OuterStructure.outer.someValue` 来访问 `OuterStructure` 类型中 `outer` 属性的 `someValue` 属性。 + +```swift +struct OuterStructure { + var outer: SomeStructure + init(someValue: Int) { + self.outer = SomeStructure(someValue: someValue) + } +} + +let nested = OuterStructure(someValue: 24) +let nestedKeyPath = \OuterStructure.outer.someValue + +let nestedValue = nested[keyPath: nestedKeyPath] +// nestedValue 的值为 24 +``` + +路径中也可以包含使用中括号的下标访问,只要下标访问的参数类型满足 `Hashable` 协议即可。下面的例子在 key path 中使用了下标来访问数组的第二个元素。 + +```swift +let greetings = ["hello", "hola", "bonjour", "안녕"] +let myGreeting = greetings[keyPath: \[String].[1]] +// myGreeting 的值为 'hola' +``` + +下标访问中使用的值可以是一个变量或者字面量,并且 key-path 表达式会使用值语义来捕获此值。下面的代码在 key-path 表达式和闭包中都使用了 `index` 变量来访问 `greetings` 数组的第三个元素。当 `index` 被修改时,key-path 表达式仍旧引用数组第三个元素,而闭包则使用了新的索引值。 + +```swift +var index = 2 +let path = \[String].[index] +let fn: ([String]) -> String = { strings in strings[index] } + +print(greetings[keyPath: path]) +// 打印 "bonjour" +print(fn(greetings)) +// 打印 "bonjour" + +// 将 'index' 设置为一个新的值不会影响到 'path' +index += 1 +print(greetings[keyPath: path]) +// 打印 "bonjour" + +// 'fn' 闭包使用了新值。 +print(fn(greetings)) +// 打印 "안녕" +``` + +路径可以使用可选链和强制解包。下面的代码在 key path 中使用了可选链来访问可选字符串的属性。 + +```swift +let firstGreeting: String? = greetings.first +print(firstGreeting?.count as Any) +// 打印 "Optional(5)" + +// 使用 key path 实现同样的功能 +let count = greetings[keyPath: \[String].first?.count] +print(count as Any) +// 打印 "Optional(5)" +``` + +可以混合使用各种 key-path 组件来访问一些深度嵌套类型的值。下面的代码通过组合不同的组件,使用 key-path 表达式访问了一个字典数组中不同的值和属性。 + +```swift +let interestingNumbers = ["prime": [2, 3, 5, 7, 11, 13, 17], + "triangular": [1, 3, 6, 10, 15, 21, 28], + "hexagonal": [1, 6, 15, 28, 45, 66, 91]] +print(interestingNumbers[keyPath: \[String: [Int]].["prime"]] as Any) +// 打印 "Optional([2, 3, 5, 7, 11, 13, 17])" +print(interestingNumbers[keyPath: \[String: [Int]].["prime"]![0]]) +// 打印 "2" +print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count]) +// 打印 "7" +print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count.bitWidth]) +// 打印 "64" +``` + +关于更多如何使用 key path 与 Objective-C APIs 交互的信息,请参阅 [在 Swift 中使用 Objective-C 运行时特性](https://developer.apple.com/documentation/swift/using_objective_c_runtime_features_in_swift)。关于更多 key-value 编程和 key-value 观察的信息,请参阅 [Key-Value 编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107i) 和 [Key-Value 观察编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i)。 + +> key-path 表达式语法 +> +> +> +#### key-path-expression {#key-path-expression} +> +> *key-path 表达式* → **\\** [类型](./03_Types.md#type)可选 **.** [多个 key-path 组件](#key-path-components) +> +> +#### key-path-components {#key-path-components} +> +> *多个 key-path 组件* → [key-path 组件](#key-path-component) | [key-path 组件](#key-path-component) **.** [多个 key-path 组件](#key-path-components) +> +> +#### key-path-component {#key-path-component} +> +> *key-path 组件* → [标识符](./02_Lexical_Structure.md#identifier) [多个 key-path 后缀](#key-path-postfixes)可选 | [多个 key-path 后缀](#key-path-postfixes) +> +> +#### key-path-postfixes {#key-path-postfixes} +> +> +#### *多个 key-path 后缀* → [key-path 后缀](#key-path-postfix) [多个 key-path 后缀](#key-path-postfixes)可选 key-path-postfixes {#key-path-postfixes} +> +> *key-path 后缀* → **?** | **!** | **self** | **\[** [函数调用参数表](#function-call-argument-list) **\]** +> + + + +### 选择器表达式 {#selector-expression} +*选择器表达式*可以让你通过选择器来引用在 Objective-C 中方法(method)和属性(property)的 setter 和 getter 方法。 + +> \#selector(方法名) +> +\#selector(getter: 属性名) +\#selector(setter: 属性名) + +方法名和属性名必须是存在于 Objective-C 运行时中的方法和属性的引用。选择器表达式的返回值是一个 Selector 类型的实例。例如: + +```swift +class SomeClass: NSObject { + let property: String + @objc(doSomethingWithInt:) + func doSomething(_ x: Int) { } + + init(property: String) { + self.property = property + } +} +let selectorForMethod = #selector(SomeClass.doSomething(_:)) +let selectorForPropertyGetter = #selector(getter: SomeClass.property) +``` + +当为属性的 getter 创建选择器时,属性名可以是变量属性或者常量属性的引用。但是当为属性的 setter 创建选择器时,属性名只可以是对变量属性的引用。 + +方法名称可以包含圆括号来进行分组,并使用 as 操作符来区分具有相同方法名但类型不同的方法,例如: + +```swift +extension SomeClass { + @objc(doSomethingWithString:) + func doSomething(_ x: String) { } +} +let anotherSelector = #selector(SomeClass.doSomething(_:) as (SomeClass) -> (String) -> Void) +``` + +由于选择器是在编译时创建的,因此编译器可以检查方法或者属性是否存在,以及是否在运行时暴露给了 Objective-C 。 + +> 注意 +> +> 虽然方法名或者属性名是个表达式,但是它不会被求值。 +> + +更多关于如何在 Swift 代码中使用选择器来与 Objective-C API 进行交互的信息,请参阅 [在 Swift 中使用 Objective-C 运行时特性](https://developer.apple.com/documentation/swift/using_objective_c_runtime_features_in_swift)。 + +> 选择器表达式语法 +> + +#### selector-expression {#selector-expression} +> *选择器表达式* → __#selector__ **(** [*表达式*](#expression) **)** +> +> *选择器表达式* → __#selector__ **(** [*getter:表达式*](#expression) **)** +> +> *选择器表达式* → __#selector__ **(** [*setter:表达式*](#expression) **)** +> + +## Key-path 字符串表达式 {#key-path-string-expressions} +key-path 字符串表达式可以访问一个引用 Objective-C 属性的字符串,通常在 key-value 编程和 key-value 观察 APIs 中使用。其格式如下: + +> `#keyPath` ( `属性名` ) +> + +属性名必须是一个可以在 Objective-C 运行时使用的属性的引用。在编译期,key-path 字符串表达式会被一个字符串字面量替换。例如: + +```swift +class SomeClass: NSObject { + @objc var someProperty: Int + init(someProperty: Int) { + self.someProperty = someProperty + } +} + +let c = SomeClass(someProperty: 12) +let keyPath = #keyPath(SomeClass.someProperty) + +if let value = c.value(forKey: keyPath) { + print(value) +} +// 打印 "12" +``` + +当在一个类中使用 key-path 字符串表达式时,可以省略类名,直接使用属性名来访问这个类的某个属性。 + +```swift +extension SomeClass { + func getSomeKeyPath() -> String { +> + return #keyPath(someProperty) + } +} +print(keyPath == c.getSomeKeyPath()) +// 打印 "true" +``` + +由于 key-path 字符串表达式在编译期才创建,编译期可以检查属性是否存在,以及属性是否暴露给 Objective-C 运行时。 + +关于更多如何使用 key path 与 Objective-C APIs 交互的信息,请参阅 [在 Swift 中使用 Objective-C 运行时特性](./https://developer.apple.com/documentation/swift/using_objective_c_runtime_features_in_swift)。关于更多 key-value 编程和 key-value 观察的信息,请参阅 [Key-Value 编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.md#//apple_ref/doc/uid/10000107i) 和 [Key-Value 观察编程](./https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.md#//apple_ref/doc/uid/10000177i)。 + +> 注意 +> +> +> 尽管*属性名*是一个表达式,但它永远不会被求值 +> + +> key-path 字符串表达式语法 +> +> +#### key-path-string-expression {#key-path-string-expression} +> +> *key-path 字符串表达式* → **#keyPath (** [表达式](#expression) **)** +> + +## 后缀表达式 {#postfix-expressions} +*后缀表达式*就是在某个表达式的后面运用后缀运算符或其他后缀语法。从语法构成上来看,基本表达式也是后缀表达式。 + +关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md)。 + +关于 Swift 标准库提供的运算符的更多信息,请参阅 [运算符定义](https://developer.apple.com/documentation/swift/operator_declarations)。 + +> 后缀表达式语法 +> + +#### postfix-expression {#postfix-expression} +> *后缀表达式* → [*基本表达式*](#primary-expression) +> +> *后缀表达式* → [*后缀表达式*](#postfix-expression) [*后缀运算符*](02_Lexical_Structure.md#postfix-operator) +> +> *后缀表达式* → [*函数调用表达式*](#function-call-expression) +> +> *后缀表达式* → [*构造器表达式*](#initializer-expression) +> +> *后缀表达式* → [*显式成员表达式*](#explicit-member-expression) +> +> *后缀表达式* → [*后缀 self 表达式*](#postfix-self-expression) +> +> *后缀表达式* → [*dynamicType 表达式*](#dynamic-type-expression) +> +> *后缀表达式* → [*下标表达式*](#subscript-expression) +> +> *后缀表达式* → [*强制取值表达式*](#forced-value-expression) +> +> *后缀表达式* → [*可选链表达式*](#optional-chaining-expression) +> + +### 函数调用表达式 {#function-call-expression} +*函数调用表达式*由函数名和参数列表组成,形式如下: + +> `函数名`(`参数 1`, `参数 2`) +> + +函数名可以是值为函数类型的任意表达式。 + +如果函数声明中指定了参数的名字,那么在调用的时候也必须得写出来。这种函数调用表达式具有以下形式: + +> `函数名`(`参数名 1`: `参数 1`, `参数名 2`: `参数 2`) +> + +如果函数的最后一个参数是函数类型,可以在函数调用表达式的尾部(右圆括号之后)加上一个闭包,该闭包会作为函数的最后一个参数。如下两种写法是等价的: + +```swift +// someFunction 接受整数和闭包参数 +someFunction(x, f: {$0 == 13}) +someFunction(x) {$0 == 13} +``` + +如果闭包是该函数的唯一参数,那么圆括号可以省略。 + +```swift +// someFunction 只接受一个闭包参数 +myData.someMethod() {$0 == 13} +myData.someMethod {$0 == 13} +``` + +> 函数调用表达式语法 +> +> +#### function-call-expression {#function-call-expression} +> +> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*函数调用参数子句*](#function-call-argument-clause) +> +> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*函数调用参数子句*](#function-call-argument-clause)可选 [*尾随闭包*](#trailing-closure) +> +> +> +#### function-call-argument-clause {#function-call-argument-clause} +> +> *函数调用参数子句* → **(** **)** | **(** [*函数调用参数表*](#function-call-argument-list) **)** +> +> +#### function-call-argument-list {#function-call-argument-list} +> +> *函数调用参数表* → [函数调用参数](#function-call-argument) | [函数调用参数](#function-call-argument) **,** [*函数调用参数表*](#function-call-argument-list) +> +> +#### function-call-argument {#function-call-argument} +> +> *函数调用参数* → [表达式](#expression) | [标识符](02_Lexical_Structure.md#identifier) **:** [*表达式*](#expression) +> +> *函数调用参数* → [运算符](./02_Lexical_Structure.md#operator) | [标识符](./02_Lexical_Structure.md#identifier) **:** [*运算符*](./02_Lexical_Structure.md#operator) +> +> +> +#### trailing-closure {#trailing-closure} +> +> *尾随闭包* → [*闭包表达式*](#closure-expression) +> + +### 构造器表达式 {#initializer-expression} +*构造器表达式*用于访问某个类型的构造器,形式如下: + +> `表达式`.init(`构造器参数`) +> + +你可以在函数调用表达式中使用构造器表达式来初始化某个类型的新实例。也可以使用构造器表达式来代理给父类构造器。 + +```swift +class SomeSubClass: SomeSuperClass { + override init() { + // 此处为子类构造过程 + super.init() + } +} +``` + +和函数类似,构造器表达式可以作为一个值。 例如: + +```swift +// 类型注解是必须的,因为 String 类型有多种构造器 +let initializer: Int -> String = String.init +let oneTwoThree = [1, 2, 3].map(initializer).reduce("", combine: +) +print(oneTwoThree) +// 打印“123” +``` + +如果通过名字来指定某个类型,可以不用构造器表达式而直接使用类型的构造器。在其他情况下,你必须使用构造器表达式。 + +```swift +let s1 = SomeType.init(data: 3) // 有效 +let s2 = SomeType(data: 1) // 有效 + +let s4 = someValue.dynamicType(data: 5) // 错误 +let s3 = someValue.dynamicType.init(data: 7) // 有效 +``` + +> 构造器表达式语法 +> + +#### initializer-expression {#initializer-expression} +> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init** +> +> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init** **(** [*参数名称*](#argument-names) **)** +> + +### 显式成员表达式 {#explicit-member-expression} +*显式成员表达式*允许我们访问命名类型、元组或者模块的成员,其形式如下: + +> `表达式`.`成员名` +> + +命名类型的某个成员在原始实现或者扩展中定义,例如: + +```swift +class SomeClass { + var someProperty = 42 +} +let c = SomeClass() +let y = c.someProperty // 访问成员 +``` + +元组的成员会隐式地根据表示它们出现顺序的整数来命名,以 0 开始,例如: + +```swift +var t = (10, 20, 30) +t.0 = t.1 +// 现在元组 t 为 (20, 20, 30) +``` + +对于模块的成员来说,只能直接访问顶级声明中的成员。 + +使用 `dynamicMemberLookup` 属性声明的类型包含可以在运行时查找的成员,具体请参阅 [属性](./07_Attributes.md)。 + +为了区分只有参数名有所不同的方法或构造器,在圆括号中写出参数名,参数名后紧跟一个冒号,对于没有参数名的参数,使用下划线代替参数名。而对于重载方法,则需使用类型注解进行区分。例如: + +```swift +class SomeClass { + func someMethod(x: Int, y: Int) {} + func someMethod(x: Int, z: Int) {} + func overloadedMethod(x: Int, y: Int) {} + func overloadedMethod(x: Int, y: Bool) {} +} +let instance = SomeClass() + +let a = instance.someMethod // 有歧义 +let b = instance.someMethod(_:y:) // 无歧义 + +let d = instance.overloadedMethod // 有歧义 +let d = instance.overloadedMethod(_:y:) // 有歧义 +let d: (Int, Bool) -> Void = instance.overloadedMethod(_:y:) // 无歧义 +``` + +如果点号(`.`)出现在行首,它会被视为显式成员表达式的一部分,而不是隐式成员表达式的一部分。例如如下代码所展示的被分为多行的链式方法调用: + +```swift +let x = [10, 3, 20, 15, 4] + .sort() + .filter { $0 > 5 } + .map { $0 * 100 } +``` + +> 显式成员表达式语法 +> + +#### explicit-member-expression {#explicit-member-expression} +> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*十进制数字*] (02_Lexical_Structure.md#decimal-digit) +> +> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*](02_Lexical_Structure.md#identifier) [*泛型实参子句*](./09_Generic_Parameters_and_Arguments.md#generic-argument-clause)可选
+> +> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*] (02_Lexical_Structure.md#identifier) **(** [*参数名称*](#argument-names) **)** +> + +#### argument-names {#argument-names} +> *参数名称* → [*参数名*](#argument-name) [*参数名称*](#argument-names)可选
+> + +#### argument-name {#argument-name} +> *参数名* → [*标识符*](./02_Lexical_Structure.md#identifier) **:** +> + +### 后缀 self 表达式 {#postfix-self-expression} +后缀 `self` 表达式由某个表达式或类型名紧跟 `.self` 组成,其形式如下: + +> `表达式`.self +> +> `类型`.self +> + +第一种形式返回表达式的值。例如:`x.self` 返回 `x`。 + +第二种形式返回相应的类型。我们可以用它来获取某个实例的类型作为一个值来使用。例如,`SomeClass.self` 会返回 `SomeClass` 类型本身,你可以将其传递给相应函数或者方法作为参数。 + +> 后缀 self 表达式语法 +> + +#### postfix-self-expression {#postfix-self-expression} +> *后缀 self 表达式* → [*后缀表达式*](#postfix-expression) **.** **self** +> + + +### 下标表达式 {#subscript-expression} +可通过*下标表达式*访问相应的下标,形式如下: + +> `表达式`[`索引表达式`] +> + +要获取下标表达式的值,可将索引表达式作为下标表达式的参数来调用下标 getter。下标 setter 的调用方式与之一样。 + +关于下标的声明,请参阅 [协议下标声明](./06_Declarations.md#protocol_subscript_declaration)。 + +> 下标表达式语法 +> + +#### subscript-expression {#subscript-expression} +> *下标表达式* → [*后缀表达式*](#postfix-expression) **[** [*表达式列表*](#expression-list) **]** +> + +### 强制取值表达式 {#forced-Value-expression} +当你确定可选值不是 `nil` 时,可以使用*强制取值表达式*来强制解包,形式如下: + +> `表达式`! +> + +如果该表达式的值不是 `nil`,则返回解包后的值。否则,抛出运行时错误。 + +返回的值可以被修改,无论是修改值本身,还是修改值的成员。例如: + +```swift +var x: Int? = 0 +x!++ +// x 现在是 1 + +var someDictionary = ["a": [1, 2, 3], "b": [10, 20]] +someDictionary["a"]![0] = 100 +// someDictionary 现在是 [b: [10, 20], a: [100, 2, 3]] +``` + +> 强制取值语法 +> + +#### forced-value-expression {#forced-value-expression} +> *强制取值表达式* → [*后缀表达式*](#postfix-expression) **!** +> + +### 可选链表达式 {#optional-chaining-expression} +*可选链表达式*提供了一种使用可选值的便捷方法,形式如下: + +> `表达式`? +> + +后缀 `?` 运算符会根据表达式生成可选链表达式而不会改变表达式的值。 + +如果某个后缀表达式包含可选链表达式,那么它的执行过程会比较特殊。如果该可选链表达式的值是 `nil`,整个后缀表达式会直接返回 `nil`。如果该可选链表达式的值不是 `nil`,则返回可选链表达式解包后的值,并将该值用于后缀表达式中剩余的表达式。在这两种情况下,整个后缀表达式的值都会是可选类型。 + +如果某个后缀表达式中包含了可选链表达式,那么只有最外层的表达式会返回一个可选类型。例如,在下面的例子中,如果 `c` 不是 `nil`,那么它的值会被解包,然后通过 `.property` 访问它的属性,接着进一步通过 `.performAction()` 调用相应方法。整个 `c?.property.performAction()` 表达式返回一个可选类型的值,而不是多重可选类型。 + +```swift +var c: SomeClass? +var result: Bool? = c?.property.performAction() +``` + +上面的例子跟下面的不使用可选链表达式的例子等价: + +```swift +var result: Bool? = nil +if let unwrappedC = c { + result = unwrappedC.property.performAction() +} +``` + +可选链表达式解包后的值可以被修改,无论是修改值本身,还是修改值的成员。如果可选链表达式的值为 `nil`,则表达式右侧的赋值操作不会被执行。例如: + +```swift +func someFunctionWithSideEffects() -> Int { + // 译者注:为了能看出此函数是否被执行,加上了一句打印 + print("someFunctionWithSideEffects") + return 42 +} +var someDictionary = ["a": [1, 2, 3], "b": [10, 20]] + +someDictionary["not here"]?[0] = someFunctionWithSideEffects() +// someFunctionWithSideEffects 不会被执行 +// someDictionary 依然是 ["b": [10, 20], "a": [1, 2, 3]] + +someDictionary["a"]?[0] = someFunctionWithSideEffects() +// someFunctionWithSideEffects 被执行并返回 42 +// someDictionary 现在是 ["b": [10, 20], "a": [42, 2, 3]] +``` + +> 可选链表达式语法 +> + +#### optional-chaining-expression {#optional-chaining-expression} +> *可选链表达式* → [*后缀表达式*](#postfix-expression) **?** +> diff --git a/source/chapter3/05_Statements.md b/source/chapter3/05_Statements.md index 2576b1a6..79ecb3a7 100755 --- a/source/chapter3/05_Statements.md +++ b/source/chapter3/05_Statements.md @@ -1,908 +1,908 @@ -# 语句(Statements){#statement-statements} - -在 Swift 中,有三种类型的语句:简单语句、编译器控制语句和控制流语句。简单语句是最常见的,用于构造表达式或者声明。编译器控制语句允许程序改变编译器的行为,包含编译配置语句和行控制语句。 - -控制流语句则用于控制程序执行的流程,Swift 中有多种类型的控制流语句:循环语句、分支语句和控制转移语句。循环语句用于重复执行代码块;分支语句用于执行满足特定条件的代码块;控制转移语句则用于改变代码的执行顺序。另外,Swift 提供了 `do` 语句,用于构建局部作用域,还用于错误的捕获和处理;还提供了 `defer` 语句,用于退出当前作用域之前执行清理操作。 - -是否将分号(`;`)添加到语句的末尾是可选的。但若要在同一行内写多条独立语句,则必须使用分号。 - -> 语句语法 -> -> *语句* → [*表达式*](./04_Expressions.md#expression) **;**可选 -> -> *语句* → [*声明*](./06_Declarations.md#declaration) **;**可选 -> -> *语句* → [*循环语句*](#loop-statement) **;**可选 -> -> *语句* → [*分支语句*](#branch-statement) **;**可选 -> -> *语句* → [*带标签的语句*](#labeled-statement) **;**可选 -> -> *语句* → [*控制转移语句*](#control-transfer-statement) **;**可选 -> -> *语句* → [*defer 语句*](#defer-statement) **;**可选 -> -> *语句* → [*do 语句*](#do-statement) **:**可选 -> -> *语句* → [*编译器控制语句*](#compiler-control-statement) -> -> *多条语句* → [*语句*](#statement) [*多条语句*](#statements)可选 -> - -## 循环语句 {#loop-statements} -循环语句会根据特定的循环条件来重复执行代码块。Swift 提供三种类型的循环语句:`for-in` 语句、`while` 语句和 `repeat-while` 语句。 - -通过 `break` 语句和 `continue` 语句可以改变循环语句的控制流。有关这两条语句,详情参见 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。 - -> 循环语句语法 -> -> - -#### loop-statement {#loop-statement} -> *循环语句* → [*for-in 语句*](#for-in-statement) -> -> *循环语句* → [*while 语句*](#while-statement) -> -> *循环语句* → [*repeat-while 语句*](#repeat-while-statement) -> - -### For-In 语句 {#for-in-statements} - -`for-in` 语句会为集合(或实现了 [Sequence](https://developer.apple.com/documentation/swift/sequence) 协议的任意类型)中的每一项执行一次代码块。 - -`for-in` 语句的形式如下: - -```swift -for item in collection { - statements -} -``` - -`for-in` 语句在循环开始前会调用集合表达式(`collection expression`)的 `makeIterator()` 方法来获取一个实现了 [IteratorProtocol](https://developer.apple.com/documentation/swift/iteratorprotocol) 协议的迭代器类型。接下来循环开始,反复调用该迭代器的 `next()` 方法。如果其返回值不是 `nil`,它将会被赋给 `item`,然后执行循环体语句,执行完毕后回到循环开始处,继续重复这一过程;否则,既不会赋值也不会执行循环体语句,`for-in` 语句至此执行完毕。 - -> for-in 语句语法 -> -> - -#### for-in-statement {#for-in-statement} -> *for-in 语句* → **for** **case**可选 [*模式*](./08_Patterns.md#pattern) **in** [*表达式*](./04_Expressions.md#expression) [*where 子句*](#where-clause)可选 [*代码块*](05_Declarations.md#code-block) -> - -### While 语句 {#while-statements} -只要循环条件为真,`while` 语句就会重复执行代码块。 - -`while` 语句的形式如下: - -```swift -while condition { - statements -} -``` - -`while` 语句的执行流程如下: - -1. 判断条件(`condition`)的值。如果为 `true`,转到第 2 步;如果为 `false`,`while` 语句至此执行完毕。 -2. 执行循环体中的语句,然后重复第 1 步。 - -由于会在执行循环体中的语句前判断条件的值,因此循环体中的语句可能会被执行若干次,也可能一次也不会被执行。 - -条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 - -> while 语句语法 -> -> - -#### while-statement {#while-statement} -> *while 语句* → **while** [*条件子句*](#condition-clause) [*代码块*](./05_Declarations.md#code-block) -> - - -#### condition-clause {#condition-clause} -> *条件子句* → [*表达式*](./04_Expressions.md#expression) | [*表达式*](./04_Expressions.md#expression) **,** [*条件列表*](#condition-list) -> - -#### condition {#condition} -> *条件* → [*表达式*](./04_Expressions.md#expression) |[*可用性条件*](#availability-condition) | [*case 条件*](#case-condition) | [*可选绑定条件*](#optional-binding-condition) -> -> - -#### case-condition {#case-condition} -> *case 条件* → **case** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer) -> - -#### optional-binding-condition {#optional-binding-condition} -> *可选绑定条件* → **let** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer) | **var** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer) -> - -### Repeat-While 语句 {#repeat-while-statements} -`repeat-while` 语句至少执行一次代码块,之后只要循环条件为真,就会重复执行代码块。 - -`repeat-while` 语句的形式如下: - -```swift -repeat { - statements -} while condition -``` - -`repeat-while` 语句的执行流程如下: - -1. 执行循环体中的语句,然后转到第 2 步。 -2. 判断条件的值。如果为 `true`,重复第 1 步;如果为 `false`,`repeat-while` 语句至此执行完毕。 - -由于条件的值是在循环体中的语句执行后才进行判断,因此循环体中的语句至少会被执行一次。 - -条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 - -> repeat-while 语句语法 -> -> - -#### repeat-while-statement {#repeat-while-statement} -> *repeat-while 语句* → **repeat** [*代码块*](./06_Declarations.md#code-block) **while** [*表达式*](./04_Expressions.md#expression) -> - -## 分支语句 {#branch-statements} -分支语句会根据一个或者多个条件来执行指定部分的代码。分支语句中的条件将会决定程序如何分支以及执行哪部分代码。Swift 提供三种类型的分支语句:`if` 语句、 `guard` 语句和 `switch` 语句。 - -`if` 语句和 `switch` 语句中的控制流可以用 `break` 语句改变,请参阅 [Break 语句](#break_statement)。 - -> 分支语句语法 -> -> - -#### branch-statement {#branch-statement} -> *分支语句* → [*if 语句*](#if-statement) -> -> *分支语句* → [*guard 语句*](#guard-statement) -> -> *分支语句* → [*switch 语句*](#switch-statement) -> - -### If 语句 {#if-statements} -`if` 语句会根据一个或多个条件来决定执行哪一块代码。 - -`if` 语句有两种基本形式,无论哪种形式,都必须有花括号。 - -第一种形式是当且仅当条件为真时执行代码,像下面这样: - -```swift -if condition { - statements -} -``` - -第二种形式是在第一种形式的基础上添加 `else` 语句(通过引入 `else` 关键字),并且用于:当条件为真时执行一部分代码,当这同一个条件为假的时候执行另一部分代码。当只有一个 `else` 语句时,`if` 语句具有以下的形式: - -```swift -if condition { - statements to execute if condition is true -} else { - statements to execute if condition is false -} -``` - -`if` 语句的 `else` 语句也可包含另一个 `if` 语句,从而形成一条链来测试更多的条件,像下面这样: - -```swift -if condition 1 { - statements to execute if condition 1 is true -} else if condition 2 { - statements to execute if condition 2 is true -} else { - statements to execute if both conditions are false -} -``` - -`if` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 - -> if 语句语法 -> -> - -#### if-statement {#if-statement} -> *if 语句* → **if** [*条件子句*](#condition-clause) [*代码块*](05_Declarations.md#code-block) [*else 子句*](#else-clause)可选 -> - -#### else-clause {#else-clause} -> *else 子句* → **else** [*代码块*](./06_Declarations.md#code-block) | **else** [*if 语句*](#if-statement) -> - -### Guard 语句 {#guard-statements} -如果一个或者多个条件不成立,可用 `guard` 语句来退出当前作用域。 - -`guard` 语句的格式如下: - -```swift -guard condition else { - statements -} -``` - -`guard` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件也可以是一条可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 - -在 `guard` 语句中进行可选绑定的任何常量或者变量,其可用范围从声明开始直到作用域结束。 - -`guard` 语句必须有 `else` 子句,而且必须在该子句中调用返回类型是 `Never` 的函数,或者使用下面的语句退出当前作用域: - - * `return` - * `break` - * `continue` - * `throw` - -关于控制转移语句,请参阅 [控制转移语句](#control_transfer_statements)。关于 `Never` 返回类型的函数,请参阅 [永不返回的函数](05_Declarations.md#rethrowing_functions_and_methods)。 - -> guard 语句语法 -> -> - -#### guard-statement {#guard-statement} -> *guard 语句* → **guard** [*条件子句*](#condition-clause) **else** [*代码块*] (05_Declarations.md#code-block) -> - -### Switch 语句 {#switch-statements} -`switch` 语句会根据控制表达式的值来决定执行哪部分代码。 - -`switch` 语句的形式如下: - -```swift -switch control expression { -case pattern 1: - statements -case pattern 2 where condition: - statements -case pattern 3 where condition, - pattern 4 where condition: - statements -default: - statements -} -``` - -`switch` 语句会先计算*控制表达式*的值,然后与每一个 `case` 的模式进行匹配。如果匹配成功,程序将会执行对应的 `case` 中的语句。另外,每一个 `case` 的作用域都不能为空,也就是说在每一个 `case` 的冒号(`:`)后面必须至少有一条语句。如果你不想在匹配到的 `case` 中执行代码,只需在该 `case` 中写一条 `break` 语句即可。 - -可以用作控制表达式的值是十分灵活的。除了标量类型外,如 `Int`、`Character`,你可以使用任何类型的值,包括浮点数、字符串、元组、自定义类型的实例和可选类型。控制表达式的值还可以用来匹配枚举类型中的成员值或是检查该值是否包含在指定的 `Range` 中。关于如何在 `switch` 语句中使用这些类型,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [Switch](../chapter2/05_Control_Flow.md#switch)。 - -每个 `case` 的模式后面可以有一个 `where` 子句。`where` 子句由 `where` 关键字紧跟一个提供额外条件的表达式组成。因此,当且仅当控制表达式匹配一个 `case` 的模式且 `where` 子句的表达式为真时,`case` 中的语句才会被执行。在下面的例子中,控制表达式只会匹配包含两个相等元素的元组,例如 `(1, 1)`: - -```swift -case let (x, y) where x == y: -``` - -正如上面这个例子,也可以在模式中使用 `let`(或 `var`)语句来绑定常量(或变量)。这些常量(或变量)可以在对应的 `where` 子句以及 `case` 中的代码中使用。但是,如果一个 `case` 中含有多个模式,所有的模式必须包含相同的常量(或变量)绑定,并且每一个绑定的常量(或变量)必须在所有的条件模式中都有相同的类型。 - -`switch` 语句也可以包含默认分支,使用 `default` 关键字表示。只有所有 `case` 都无法匹配控制表达式时,默认分支中的代码才会被执行。一个 `switch` 语句只能有一个默认分支,而且必须在 `switch` 语句的最后面。 - -`switch` 语句中 `case` 的匹配顺序和源代码中的书写顺序保持一致。因此,当多个模式都能匹配控制表达式时,只有第一个匹配的 `case` 中的代码会被执行。 - -#### Switch 语句必须是详尽的 - -在 Swift 中,`switch` 语句中控制表达式的每一个可能的值都必须至少有一个 `case` 与之对应。在某些无法面面俱到的情况下(例如,表达式的类型是 `Int`),你可以使用 `default` 分支满足该要求。 - -#### 对未来枚举的 `case` 进行 `switch` {#future-case} -非冻结枚举(`nonfronzen enumeration`)是一种特殊的枚举类型,它可能在未来会增加新的枚举 `case`,即使这时候你已经编译并且发布了你的应用,所以在 switch 非冻结枚举前需要深思熟虑。当一个库的作者们把一个枚举标记为非冻结的,这意味着他们保留了增加新的枚举 `case` 的权利,并且任何和这个枚举交互的代码都要在不需要重新编译的条件下能够处理那些未来可能新加入的 `case` 。只有那些标准库,比如用 Swift 实现的苹果的一些框架,C 以及 Objective-C 代码才能够声明非冻结枚举。你在 Swift 中声明的枚举不能是非冻结的。 - -当你对未来枚举进行 switch 时,你总是需要有一个 `default case`,即使每种枚举类型都已经有对应的 `case` 了。你可以在 default 前标注 `@unknown`,意思是这个 `case` 应该只匹配未来加入的枚举 `case`。如果你的 `default case` 中匹配了任何在编译时就能确定的枚举 `case`,Swift 会抛出一个警告。这可以很好地提醒你库的作者已经新增了一种 `case`,并且你还没有去处理。 - -以下就是一个例子,我们对标准库的 [Mirror.AncestorRepresentation](https://developer.apple.com/documentation/swift/mirror/ancestorrepresentation) 枚举进行 switch 操作。每当有新的 `case` 加入,我们会得到一个警告,提示我们要去处理它。 - -```swift -let representation: Mirror.AncestorRepresentation = .generated -switch representation { -case .customized: - print("Use the nearest ancestor’s implementation.") -case .generated: - print("Generate a default mirror for all ancestor classes.") -case .suppressed: - print("Suppress the representation of all ancestor classes.") -@unknown default: - print("Use a representation that was unknown when this code was compiled.") -} -// Prints "Generate a default mirror for all ancestor classes." -``` - -#### 不存在隐式落入 - -当匹配到的 `case` 中的代码执行完毕后,`switch` 语句会直接退出,而不会继续执行下一个 `case` 。这就意味着,如果你想执行下一个 `case`,需要显式地在当前 `case` 中使用 `fallthrough` 语句。关于 `fallthrough` 语句的更多信息,请参阅 [Fallthrough 语句](#fallthrough_statements)。 - -> switch 语句语法 -> -> - -#### switch-statement {#switch-statement} -> *switch 语句* → **switch** [*表达式*](./04_Expressions.md#expression) **{** [*switch-case 列表*](#switch-cases)可选 **}** -> - -#### switch-cases {#switch-cases} -> *switch case 列表* → [*switch-case*](#switch-case) [*switch-case 列表*](#switch-cases)可选 -> - -#### switch-case {#switch-case} -> *switch case* → [*case 标签*](#case-label) [*多条语句*](#statements) | [*default 标签*](#default-label) [*多条语句*](#statements) | [*conditional-switch-case*](#conditional-switch-case-label) -> - - -#### case-label {#case-label} -> *case 标签* → [*属性*](#switch-case-attributes-label)可选 **case** [*case 项列表*](#case-item-list) **:** -> - -#### case-item-list {#case-item-list} -> *case 项列表* → [*模式*](./08_Patterns.md#pattern) [*where 子句*](#where-clause)可选 | [*模式*](07_Patterns.md#pattern) [*where 子句*](#where-clause)可选 **,** [*case 项列表*](#case-item-list) -> - -#### default-label {#default-label} -> *default 标签* → [*属性*](#switch-case-attributes-label)可选 **default** **:** -> -> - -#### where-clause {#where-clause} -> *where-clause* → **where** [*where 表达式*](#where-expression) -> - -#### where-expression {#where-expression} -> *where-expression* → [*表达式*](./04_Expressions.md#expression) -> -> - -#### grammar_conditional-switch-case {#grammar-conditional-switch-case} -> *conditional-switch-case* → [*switch-if-directive-clause*](#switch-case-attributes-label) [*switch-elseif-directive-clauses*](#switch-case-attributes-label) 可选 [*switch-else-directive-clause*](#switch-case-attributes-label) 可选 [*endif-directive*](#switch-case-attributes-label) -> - -#### grammar_switch-if-directive-clause {#grammar-switch-if-directive-clause} -> *switch-if-directive 语句* → [*if-directive*](#switch-case-attributes-label) [*compilation-condition*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label) 可选 -> - -#### grammar_switch-elseif-directive-clauses {#grammar-switch-elseif-directive-clauses} -> *switch-elseif-directive 语句(复数)* → [*elseif-directive-clause*](#switch-case-attributes-label) [*switch-elseif-directive-clauses*](#switch-case-attributes-label)可选 -> - -#### grammar_switch-elseif-directive-clause {#grammar-switch-elseif-directive-clause} -> *switch-elseif-directive 语句* → [*elseif-directive*](#switch-case-attributes-label) [*compilation-condition*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label)可选 -> - -#### grammar_switch-else-directive-clause {#grammar-switch-else-directive-clause} -> *switch-else-directive 语句* → [*else-directive*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label) 可选 -> - -## 带标签的语句 {#labeled-statements} -你可以在循环语句或 `switch` 语句前面加上标签,它由标签名和紧随其后的冒号(`:`)组成。在 `break` 和 `continue` 后面跟上标签名可以显式地在循环语句或 `switch` 语句中改变相应的控制流。关于这两条语句用法,请参阅 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。 - -标签的作用域在该标签所标记的语句内。可以嵌套使用带标签的语句,但标签名必须唯一。 - -关于使用带标签的语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 - -> 带标签的语句语法 -> -> - -#### labeled-statement {#labeled-statement} -> *带标签的语句* → [*语句标签*](#statement-label) [*循环语句*](#grammar_loop-statement) -> -> *带标签的语句* → [*语句标签*](#statement-label) [*if 语句*](#if-statement) -> -> *带标签的语句* → [*语句标签*](#statement-label) [*switch 语句*](#switch-statement) -> -> > *带标签的语句* → [*语句标签*](#statement-label) [*do 语句*](#sdo-statement) -> - -#### statement-label {#statement-label} -> *语句标签* → [*标签名称*](#label-name) **:** -> - -#### label-name {#label-name} -> *标签名称* → [*标识符*](./02_Lexical_Structure.md#identifier) -> - -## 控制转移语句 {#control-transfer-statements} -控制转移语句能够无条件地把控制权从一片代码转移到另一片代码,从而改变代码执行的顺序。Swift 提供五种类型的控制转移语句:`break` 语句、`continue` 语句、`fallthrough` 语句、`return` 语句和 `throw` 语句。 - -> 控制转移语句语法 -> -> - -#### control-transfer-statement {#control-transfer-statement} -> *控制转移语句* → [*break 语句*](#break-statement) -> -> *控制转移语句* → [*continue 语句*](#continue-statement) -> -> *控制转移语句* → [*fallthrough 语句*](#fallthrough-statement) -> -> *控制转移语句* → [*return 语句*](#return-statement) -> -> *控制转移语句* → [*throw 语句*](#throw-statement) -> - -### Break 语句 {#break-statement} -`break` 语句用于终止循环语句、`if` 语句或 `switch` 语句的执行。使用 `break` 语句时,可以只写 `break` 这个关键词,也可以在 `break` 后面跟上标签名,像下面这样: - -> break -> -> break `label name` -> - -当 `break` 语句后面带标签名时,可用于终止由这个标签标记的循环语句、`if` 语句或 `switch` 语句的执行。 - -而只写 `break` 时,则会终止 `switch` 语句或 `break` 语句所属的最内层循环语句的执行。不能使用 `break` 语句来终止未使用标签的 `if` 语句。 - -无论哪种情况,控制权都会被转移给被终止的控制流语句后面的第一行语句。 - -关于使用 `break` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Break](../chapter2/05_Control_Flow.md#break) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 - -> break 语句语法 -> -> - -#### break-statement {#break-statement} -> *break 语句* → **break** [*标签名称*](#label-name)可选 -> - -### Continue 语句 {#continue-statement} -`continue` 语句用于终止循环中当前迭代的执行,但不会终止该循环的执行。使用 `continue` 语句时,可以只写 `continue` 这个关键词,也可以在 `continue` 后面跟上标签名,像下面这样: - -> continue -> -> continue `label name` -> - -当 `continue` 语句后面带标签名时,可用于终止由这个标签标记的循环中当前迭代的执行。 - -而当只写 `continue` 时,可用于终止 `continue` 语句所属的最内层循环中当前迭代的执行。 - -在这两种情况下,控制权都会被转移给循环语句的条件语句。 - -在 `for` 语句中,`continue` 语句执行后,增量表达式还是会被计算,这是因为每次循环体执行完毕后,增量表达式都会被计算。 - -关于使用 `continue` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Continue](../chapter2/05_Control_Flow.md#continue) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 - -> continue 语句语法 -> -> - -#### continue-statement {#continue-statement} -> *continue 语句* → **continue** [*标签名称*](#label-name)可选 -> - -### Fallthrough 语句 {#fallthrough-statements} -`fallthrough` 语句用于在 `switch` 语句中转移控制权。`fallthrough` 语句会把控制权从 `switch` 语句中的一个 `case` 转移到下一个 `case`。这种控制权转移是无条件的,即使下一个 `case` 的模式与 `switch` 语句的控制表达式的值不匹配。 - -`fallthrough` 语句可出现在 `switch` 语句中的任意 `case` 中,但不能出现在最后一个 `case` 中。同时,`fallthrough` 语句也不能把控制权转移到使用了值绑定的 `case`。 - -关于在 `switch` 语句中使用 `fallthrough` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [控制转移语句](../chapter2/05_Control_Flow.md#control_transfer_statements)。 - -> fallthrough 语句语法 -> -> - -#### fallthrough-statement {#fallthrough-statement} -> *fallthrough 语句* → **fallthrough** -> - -### Return 语句 {#return-statements} -`return` 语句用于在函数或方法的实现中将控制权转移到调用函数或方法,接着程序将会从调用位置继续向下执行。 - -使用 `return` 语句时,可以只写 `return` 这个关键词,也可以在 `return` 后面跟上表达式,像下面这样: - -> return -> -> return `expression` -> - -当 `return` 语句后面带表达式时,表达式的值将会返回给调用函数或方法。如果表达式的值的类型与函数或者方法声明的返回类型不匹配,Swift 则会在返回表达式的值之前将表达式的值的类型转换为返回类型。 - -> 注意 -> -> -> 正如 [可失败构造器](./06_Declarations.md#failable_initializers) 中所描述的,`return nil` 在可失败构造器中用于表明构造失败。 -> - -而只写 `return` 时,仅仅是从该函数或方法中返回,而不返回任何值(也就是说,函数或方法的返回类型为 `Void` 或者说 `()`)。 - -> return 语句语法 -> -> - -#### return-statement {#return-statement} -> *return 语句* → **return** [*表达式*](./04_Expressions.html#expression)可选 - -### Throw 语句 {#throw-statements} - -### Throw 语句 {#throw-statements} -`throw` 语句出现在抛出函数或者抛出方法体内,或者类型被 `throws` 关键字标记的闭包表达式体内。 - -`throw` 语句使程序在当前作用域结束执行,并向外围作用域传播错误。抛出的错误会一直传递,直到被 `do` 语句的 `catch` 子句处理掉。 - -`throw` 语句由 `throw` 关键字紧跟一个表达式组成,如下所示: - -> throw `expression` -> - -表达式的结果必须符合 `ErrorType` 协议。 - -关于如何使用 `throw` 语句的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md) 一章的 [用 throwing 函数传递错误](../chapter2/17_Error_Handling.md#propagating_errors_using_throwing_functions)。 - -> throw 语句语法 -> -> - -#### throw-statement {#throw-statement} -> *throw 语句* → **throw** [*表达式*](./04_Expressions.md#expression) -> - -## Defer 语句 {#defer-statements} -`defer` 语句用于在退出当前作用域之前执行代码。 - -`defer` 语句形式如下: - -```swift -defer { - statements -} -``` - -在 `defer` 语句中的语句无论程序控制如何转移都会被执行。在某些情况下,例如,手动管理资源时,比如关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 `defer` 语句。 - -如果多个 `defer` 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个 `defer` 语句,会在最后执行,这意味着代码中最靠后的 `defer` 语句中引用的资源可以被其他 `defer` 语句清理掉。 - -```swift -func f() { - defer { print("First") } - defer { print("Second") } - defer { print("Third") } -} -f() -// 打印“Third” -// 打印“Second” -// 打印“First” -``` - -`defer` 语句中的语句无法将控制权转移到 `defer` 语句外部。 - -> defer 语句语法 -> -> - -#### defer-statement {#defer-statement} -> *延迟语句* → **defer** [*代码块*](./06_Declarations.md#code-block) -> - -## Do 语句 {#do-statements} -`do` 语句用于引入一个新的作用域,该作用域中可以含有一个或多个 `catch` 子句,`catch` 子句中定义了一些匹配错误条件的模式。`do` 语句作用域内定义的常量和变量只能在 `do` 语句作用域内使用。 - -Swift 中的 `do` 语句与 C 中限定代码块界限的大括号(`{}`)很相似,也并不会降低程序运行时的性能。 - -`do` 语句的形式如下: - -```swift -do { - try expression - statements -} catch pattern 1 { - statements -} catch pattern 2 where condition { - statements -} -``` - -如同 `switch` 语句,编译器会判断 `catch` 子句是否有遗漏。如果 `catch` 子句没有遗漏,则认为错误已被处理。否则,错误会自动传递到外围作用域,被某个 `catch` 子句处理掉或者被用 `throws` 关键字声明的抛出函数继续向外抛出。 - -为了确保错误已经被处理,可以让 `catch` 子句使用匹配所有错误的模式,如通配符模式(`_`)。如果一个 `catch` 子句不指定一种具体模式,`catch` 子句会匹配任何错误,并绑定到名为 `error` 的局部常量。有关在 `catch` 子句中使用模式的更多信息,请参阅 [模式](./08_Patterns.md)。 - -关于如何在 `do` 语句中使用一系列 `catch` 子句的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md#handling_errors)。 - -> do 语句语法 -> -> - -#### do-statement {#do-statement} -> *do 语句* → **do** [*代码块*](./06_Declarations.md#code-block) [*多条 catch 子句*](#catch-clauses)可选 -> - -#### catch-clauses {#catch-clauses} -> *多条 catch 子句* → [*catch 子句*](#catch-clause) [*多条 catch 子句*](#catch-clauses)可选 -> - -#### catch-clause {#catch-clause} -> *catch 子句* → **catch** [*模式*](./08_Patterns.md#pattern)可选 [*where 子句*](#where-clause)可选 [*代码块*](05_Declarations.md#code-block) -> - -## 编译器控制语句 {#compiler-control-statements} -编译器控制语句允许程序改变编译器的行为。Swift 有三种编译器控制语句:条件编译语句、线路控制语句和编译时诊断语句。 - -> 编译器控制语句语法 -> -> - -#### compiler-control-statement {#compiler-control-statement} -> *编译器控制语句* → [*条件编译语句*](#grammar_conditional-compilation-block) -> -> *编译器控制语句* → [*线路控制语句*](#line-control-statement) -> -> *编译器控制语句* → [*诊断语句*](#grammar_diagnostic-statement) -> - -### 条件编译代码块 {#Conditional-Compilation-Block} -条件编译代码块可以根据一个或多个配置来有条件地编译代码。 - -每一个条件编译代码块都以 `#if` 开始,`#endif` 结束。如下: - -```swift -#if compilation condition -statements -#endif -``` - -和 `if` 语句的条件不同,编译配置的条件是在编译时进行判断的。只有编译配置在编译时判断为 `true` 的情况下,相应的语句才会被编译和执行。 - -编译配置可以是 `true` 和 `false` 的字面量,也可以是使用 `-D` 命令行标志的标识符,或者是下列表格中的任意一个平台检测函数。 - -| 函数 | 可用参数 | -| --- | --- | -| `os()` | `OSX`, `iOS`, `watchOS`, `tvOS`, `Linux` | -| `arch()` | `i386`, `x86_64`, `arm`, `arm64` | -| `swift()` | `>=` 或 `<` 后跟版本号 | -| `compiler()` | `>=` 或 `<` 后跟版本号 | -| `canImport()` | 模块名 | -| `targetEnvironment()` | 模拟器 | - -在 `swift()` 和 `compiler()` 之后的版本号包含有主版本号,可选副版本号,可选补丁版本号类似,并且用(`.`)来分隔。在比较符和版本号之间不能有空格,版本号与前面的函数相对应,比如 `compiler()` 对应的就是这个编译器的版本号,`swift()` 对应的就是你要编译的 `Swift` 语言的版本号。举个简单的例子,如果你在使用 `Swift 5` 的编译器,想编译 `Swift 4.2` ,可以看下面的例子: - -```swift -#if compiler(>=5) -print("Compiled with the Swift 5 compiler or later") -#endif -#if swift(>=4.2) -print("Compiled in Swift 4.2 mode or later") -#endif -#if compiler(>=5) && swift(<5) -print("Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5") -#endif -// 打印 "Compiled with the Swift 5 compiler or later" -// 打印 "Compiled in Swift 4.2 mode or later" -// 打印 "Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5" -``` - -`canImport()` 后面跟的变量是模块的名字,这里这个模块可能并不是每个平台上都存在的。使用它来检测是否可以导入这个模块,如果模块存在就返回 `true` 否则返回 `false` 。 - -`targetEnvironment()` 当为模拟器编译时返回 `true`,否则返回 `false` 。 - -> 注意 -> -> -> `arch(arm)` 平台检测函数在 ARM 64 位设备上不会返回 `true`。如果代码在 32 位的 iOS 模拟器上编译,`arch(i386)` 平台检测函数会返回 `true`。 -> - -你可以使用逻辑操作符 `&&`、`||` 和 `!` 来组合多个编译配置,还可以使用圆括号来进行分组。 - -就像 `if` 语句一样,你可以使用 `#elseif` 子句来添加任意多个条件分支来测试不同的编译配置。你也可以使用 `#else` 子句来添加最终的条件分支。包含多个分支的编译配置语句例子如下: - -```swift -#if compilation condition 1 -statements to compile if compilation condition 1 is true -#elseif compilation condition 2 -statements to compile if compilation condition 2 is true -#else -statements to compile if both compilation conditions are false -#endif -``` - -> 注意 -> -> -> 即使没有被编译,编译配置中的语句仍然会被解析。然而,唯一的例外是编译配置语句中包含语言版本检测函数:仅当 `Swift` 编译器版本和语言版本检测函数中指定的版本号匹配时,语句才会被解析。这种设定能确保旧的编译器不会尝试去解析新 Swift 版本的语法。 -> - - -#### build-config-statement {#build-config-statement} -> 条件编译代码块语法 -> -> - -#### grammar_conditional-compilation-block {#grammar-conditional-compilation-block} -> *条件编译代码块* → [*if-directive 语句*](#grammar_if-directive-clause) [*elseif-directive 语句(复数)*](#grammar_elseif-directive-clauses)可选 [*else-directive 语句*](#grammar_else-directive-clause)可选 [*endif-directive*](#grammar_endif-directive) -> - -#### grammar_if-directive-clause {#grammar-if-directive-clause} -> *if-directive 语句* → [*if-directive*](#grammar_if-directive) [*编译条件*](#compilation-condition) [*语句(复数)*](#statements)可选 -> - -#### grammar_elseif-directive-clauses {#grammar-elseif-directive-clauses} -> *elseif-directive 语句(复数)* → [*elseif-directive 语句*](#grammar_elseif-directive-clause) [*elseif-directive 语句(复数)*](#grammar_elseif-directive-clauses) -> - -#### grammar_elseif-directive-clauses {#grammar-elseif-directive-clauses} -> *elseif-directive 语句* → [*elseif-directive*](#grammar_elseif-directive) [*编译条件*](#compilation-condition) [*语句(复数)*](#statements)可选 -> - -#### grammar_else-directive-clause {#grammar-else-directive-clause} -> *else-directive 语句* → [*else-directive*](#grammar_else-directive) [*语句(复数)*](#statements)可选 -> - - -> *if-directive* → **#if** -> -> *elseif-directive* → **#elseif** -> -> *else-directive* → **#else** -> -> *endif-directive* → **#endif** -> - - -#### compilation-condition {#compilation-condition} -> *编译条件* → [*平台条件*](#grammar_platform-condition) -> -> *编译条件* → [*标识符*](./02_Lexical_Structure.md#identifier) -> -> *编译条件* → [*布尔值字面量*](./02_Lexical_Structure.md#boolean-literal) -> -> *编译条件* → **(** [*编译条件*](#compilation-condition) **)** -> -> *编译条件* → **!** [*编译条件*](#compilation-condition) -> -> *编译条件* → [*编译条件*](#compilation-condition) **&&** [*编译条件*](#compilation-condition) -> -> *编译条件* → [*编译条件*](#compilation-condition) **||** [*编译条件*](#compilation-condition) -> - - -#### grammar_platform-condition {#grammar-platform-condition} - -#### grammar_platform-condition-os {#grammar-platform-condition-os} -> *平台条件* → **os ( [*操作系统*](#operating-system) )** -> - -#### grammar_platform-condition-arch {#grammar-platform-condition-arch} -> *平台条件* → **arch ( [*架构*](#architecture) )** -> - -#### grammar_platform-condition-swift {#grammar-platform-condition-swift} -> *平台条件* → **swift ( >= [*swift 版本*](#swift-version) )** | **swift ( < [*swift 版本*](#swift-version) )** -> - -#### grammar_platform-condition-compiler {#grammar-platform-condition-compiler} -> *平台条件* → **compiler ( >= [*swift 版本*](#swift-version) )** | **compiler ( < [*swift 版本*](#swift-version) )** -> - -#### grammar_platform-condition-canImport {#grammar-platform-condition-canImport} -> *平台条件* → **canImport ( [*模块名*](#grammar_module-name) )** -> - -#### grammar_platform-condition-targetEnvironment {#grammar-platform-condition-targetEnvironment} -> *平台条件* → **targetEnvironment ( [*环境*](#grammar_environment) )** -> - -#### operating-system {#operating-system} -> *操作系统* → **macOS** | **iOS** | **watchOS** | **tvOS** -> - -#### architecture {#architecture} -> *架构* → **i386** | **x86_64** | **arm** | **arm64** -> - -#### swift-version {#swift-version} -> *swift 版本* → [*十进制数字*](./02_Lexical_Structure.md#decimal-digit) ­**.** ­[*swift 版本延续*](#grammar_swift-version-continuation) 可选 -> - -#### grammar_swift-version-continuation {#grammar-swift-version-continuation} -> *swift 版本延续* → **.** [*十进制数字*](./02_Lexical_Structure.md#decimal-digit) [*swift 版本延续*](#grammar_swift-version-continuation) 可选 -> - -#### grammar_module-name {#grammar-module-name} -> *模块名* → [*identifier*](./02_Lexical_Structure.md#identifier) -> - -#### grammar_environment {#grammar-environment} -> *环境* → **模拟器** -> - -### 行控制语句 {#line-control-statements} -行控制语句可以为被编译的源代码指定行号和文件名,从而改变源代码的定位信息,以便进行分析和调试。 - -行控制语句形式如下: - -> \#sourceLocation(file: `filename` , line:`line number`) -> - -> \#sourceLocation() -> - -第一种的行控制语句会改变该语句之后的代码中的字面量表达式 `#line` 和 `#file` 所表示的值。`行号` 是一个大于 0 的整形字面量,会改变 `#line` 表达式的值。`文件名` 是一个字符串字面量,会改变 `#file` 表达式的值。 - -第二种的行控制语句,`#sourceLocation()`,会将源代码的定位信息重置回默认的行号和文件名。 - - -#### line-control-statement {#line-control-statement} -> 行控制语句语法 -> -> -> *行控制语句* → **#sourceLocation(file:[*文件名*](#file-name),line:[*行号*](#line-number))** -> -> *行控制语句* → **#sourceLocation()** -> - -#### line-number {#line-number} -> *行号* → 大于 0 的十进制整数 -> - -#### file-name {#file-name} -> *文件名* → [*静态字符串字面量*](./02_Lexical_Structure.md#static-string-literal) -> - -### 编译时诊断语句 {#compile-time-diagnostic-statement} - -编译时诊断语句允许编译器在编译的时候可以发出错误或者警告。语句形式如下: - -```swift -#error("error message") -#warning("warning message") -``` - -第一句会抛出错误信息并终止编译,第二句会发出警告信息但是编译会继续进行。你可以通过静态字符串字面量来书写诊断信息,静态字符串字面量不能使用字符串 `interpolation` 或者 `concatenation`,但可以使用多行的形式。 - -> 编译时诊断语句语法 -> -> - -#### grammar_compile-time-diagnostic-statement {#grammar-compile-time-diagnostic-statement} -> *诊断语句* → **#error** **(** [*diagnostic-message*](#grammar_diagnostic-message) **)** -> -> *诊断语句* → **#warning** **(** [*diagnostic-message*](#grammar_diagnostic-message) **)** -> -> *诊断语句* → [*静态字符串字面量*](./02_Lexical_Structure.md#static-string-literal) -> - -## 可用性条件 {#availability-condition} -可用性条件可作为 `if`,`while`,`guard` 语句的条件,可以在运行时基于特定的平台参数来查询 API 的可用性。 - -可用性条件的形式如下: - -```swift -if #available(platform name version, ..., *) { - statements to execute if the APIs are available -} else { - fallback statements to execute if the APIs are unavailable -} -``` - -使用可用性条件来执行一个代码块时,取决于使用的 API 在运行时是否可用,编译器会根据可用性条件提供的信息来决定是否执行相应的代码块。 - -可用性条件使用一系列逗号分隔的平台名称和版本。使用 `iOS`,`OSX`,以及 `watchOS` 等作为平台名称,并写上相应的版本号。`*` 参数是必须写的,用于处理未来的潜在平台。可用性条件确保了运行时的平台不低于条件中指定的平台版本时才执行代码块。 - -与布尔类型的条件不同,不能用逻辑运算符 `&&` 和 `||` 组合可用性条件。 - -> 可用性条件语法 -> -> - -#### availability-condition {#availability-condition} -> *可用性条件* → **#available** **(** [*可用性参数列表*](#availability-arguments) **)** -> - -#### availability-arguments {#availability-arguments} -> *可用性参数列表* → [*可用性参数*](#availability-argument) | [*可用性参数*](#availability-argument) **,** [*可用性参数列表*](#availability-arguments) -> - -#### availability-argument {#availability-argument} -> *可用性参数* → [平台名称](#platform-name) [平台版本](#platform-version) -> -> *可用性条件* → __*__ -> -> - -#### platform-name {#platform-name} -> *平台名称* → **iOS** | **iOSApplicationExtension** -> -> *平台名称* → **OSX** | **macOSApplicationExtension** -> -> *平台名称* → **watchOS** -> -> *平台名称* → **tvOS** -> - -#### platform-version {#platform-version} -> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) -> -> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) -> -> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) -> +# 语句(Statements){#statement-statements} + +在 Swift 中,有三种类型的语句:简单语句、编译器控制语句和控制流语句。简单语句是最常见的,用于构造表达式或者声明。编译器控制语句允许程序改变编译器的行为,包含编译配置语句和行控制语句。 + +控制流语句则用于控制程序执行的流程,Swift 中有多种类型的控制流语句:循环语句、分支语句和控制转移语句。循环语句用于重复执行代码块;分支语句用于执行满足特定条件的代码块;控制转移语句则用于改变代码的执行顺序。另外,Swift 提供了 `do` 语句,用于构建局部作用域,还用于错误的捕获和处理;还提供了 `defer` 语句,用于退出当前作用域之前执行清理操作。 + +是否将分号(`;`)添加到语句的末尾是可选的。但若要在同一行内写多条独立语句,则必须使用分号。 + +> 语句语法 +> +> *语句* → [*表达式*](./04_Expressions.md#expression) **;**可选 +> +> *语句* → [*声明*](./06_Declarations.md#declaration) **;**可选 +> +> *语句* → [*循环语句*](#loop-statement) **;**可选 +> +> *语句* → [*分支语句*](#branch-statement) **;**可选 +> +> *语句* → [*带标签的语句*](#labeled-statement) **;**可选 +> +> *语句* → [*控制转移语句*](#control-transfer-statement) **;**可选 +> +> *语句* → [*defer 语句*](#defer-statement) **;**可选 +> +> *语句* → [*do 语句*](#do-statement) **:**可选 +> +> *语句* → [*编译器控制语句*](#compiler-control-statement) +> +> *多条语句* → [*语句*](#statement) [*多条语句*](#statements)可选 +> + +## 循环语句 {#loop-statements} +循环语句会根据特定的循环条件来重复执行代码块。Swift 提供三种类型的循环语句:`for-in` 语句、`while` 语句和 `repeat-while` 语句。 + +通过 `break` 语句和 `continue` 语句可以改变循环语句的控制流。有关这两条语句,详情参见 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。 + +> 循环语句语法 +> +> + +#### loop-statement {#loop-statement} +> *循环语句* → [*for-in 语句*](#for-in-statement) +> +> *循环语句* → [*while 语句*](#while-statement) +> +> *循环语句* → [*repeat-while 语句*](#repeat-while-statement) +> + +### For-In 语句 {#for-in-statements} + +`for-in` 语句会为集合(或实现了 [Sequence](https://developer.apple.com/documentation/swift/sequence) 协议的任意类型)中的每一项执行一次代码块。 + +`for-in` 语句的形式如下: + +```swift +for item in collection { + statements +} +``` + +`for-in` 语句在循环开始前会调用集合表达式(`collection expression`)的 `makeIterator()` 方法来获取一个实现了 [IteratorProtocol](https://developer.apple.com/documentation/swift/iteratorprotocol) 协议的迭代器类型。接下来循环开始,反复调用该迭代器的 `next()` 方法。如果其返回值不是 `nil`,它将会被赋给 `item`,然后执行循环体语句,执行完毕后回到循环开始处,继续重复这一过程;否则,既不会赋值也不会执行循环体语句,`for-in` 语句至此执行完毕。 + +> for-in 语句语法 +> +> + +#### for-in-statement {#for-in-statement} +> *for-in 语句* → **for** **case**可选 [*模式*](./08_Patterns.md#pattern) **in** [*表达式*](./04_Expressions.md#expression) [*where 子句*](#where-clause)可选 [*代码块*](05_Declarations.md#code-block) +> + +### While 语句 {#while-statements} +只要循环条件为真,`while` 语句就会重复执行代码块。 + +`while` 语句的形式如下: + +```swift +while condition { + statements +} +``` + +`while` 语句的执行流程如下: + +1. 判断条件(`condition`)的值。如果为 `true`,转到第 2 步;如果为 `false`,`while` 语句至此执行完毕。 +2. 执行循环体中的语句,然后重复第 1 步。 + +由于会在执行循环体中的语句前判断条件的值,因此循环体中的语句可能会被执行若干次,也可能一次也不会被执行。 + +条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 + +> while 语句语法 +> +> + +#### while-statement {#while-statement} +> *while 语句* → **while** [*条件子句*](#condition-clause) [*代码块*](./05_Declarations.md#code-block) +> + + +#### condition-clause {#condition-clause} +> *条件子句* → [*表达式*](./04_Expressions.md#expression) | [*表达式*](./04_Expressions.md#expression) **,** [*条件列表*](#condition-list) +> + +#### condition {#condition} +> *条件* → [*表达式*](./04_Expressions.md#expression) |[*可用性条件*](#availability-condition) | [*case 条件*](#case-condition) | [*可选绑定条件*](#optional-binding-condition) +> +> + +#### case-condition {#case-condition} +> *case 条件* → **case** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer) +> + +#### optional-binding-condition {#optional-binding-condition} +> *可选绑定条件* → **let** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer) | **var** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer) +> + +### Repeat-While 语句 {#repeat-while-statements} +`repeat-while` 语句至少执行一次代码块,之后只要循环条件为真,就会重复执行代码块。 + +`repeat-while` 语句的形式如下: + +```swift +repeat { + statements +} while condition +``` + +`repeat-while` 语句的执行流程如下: + +1. 执行循环体中的语句,然后转到第 2 步。 +2. 判断条件的值。如果为 `true`,重复第 1 步;如果为 `false`,`repeat-while` 语句至此执行完毕。 + +由于条件的值是在循环体中的语句执行后才进行判断,因此循环体中的语句至少会被执行一次。 + +条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 + +> repeat-while 语句语法 +> +> + +#### repeat-while-statement {#repeat-while-statement} +> *repeat-while 语句* → **repeat** [*代码块*](./06_Declarations.md#code-block) **while** [*表达式*](./04_Expressions.md#expression) +> + +## 分支语句 {#branch-statements} +分支语句会根据一个或者多个条件来执行指定部分的代码。分支语句中的条件将会决定程序如何分支以及执行哪部分代码。Swift 提供三种类型的分支语句:`if` 语句、 `guard` 语句和 `switch` 语句。 + +`if` 语句和 `switch` 语句中的控制流可以用 `break` 语句改变,请参阅 [Break 语句](#break_statement)。 + +> 分支语句语法 +> +> + +#### branch-statement {#branch-statement} +> *分支语句* → [*if 语句*](#if-statement) +> +> *分支语句* → [*guard 语句*](#guard-statement) +> +> *分支语句* → [*switch 语句*](#switch-statement) +> + +### If 语句 {#if-statements} +`if` 语句会根据一个或多个条件来决定执行哪一块代码。 + +`if` 语句有两种基本形式,无论哪种形式,都必须有花括号。 + +第一种形式是当且仅当条件为真时执行代码,像下面这样: + +```swift +if condition { + statements +} +``` + +第二种形式是在第一种形式的基础上添加 `else` 语句(通过引入 `else` 关键字),并且用于:当条件为真时执行一部分代码,当这同一个条件为假的时候执行另一部分代码。当只有一个 `else` 语句时,`if` 语句具有以下的形式: + +```swift +if condition { + statements to execute if condition is true +} else { + statements to execute if condition is false +} +``` + +`if` 语句的 `else` 语句也可包含另一个 `if` 语句,从而形成一条链来测试更多的条件,像下面这样: + +```swift +if condition 1 { + statements to execute if condition 1 is true +} else if condition 2 { + statements to execute if condition 2 is true +} else { + statements to execute if both conditions are false +} +``` + +`if` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 + +> if 语句语法 +> +> + +#### if-statement {#if-statement} +> *if 语句* → **if** [*条件子句*](#condition-clause) [*代码块*](05_Declarations.md#code-block) [*else 子句*](#else-clause)可选 +> + +#### else-clause {#else-clause} +> *else 子句* → **else** [*代码块*](./06_Declarations.md#code-block) | **else** [*if 语句*](#if-statement) +> + +### Guard 语句 {#guard-statements} +如果一个或者多个条件不成立,可用 `guard` 语句来退出当前作用域。 + +`guard` 语句的格式如下: + +```swift +guard condition else { + statements +} +``` + +`guard` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件也可以是一条可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 + +在 `guard` 语句中进行可选绑定的任何常量或者变量,其可用范围从声明开始直到作用域结束。 + +`guard` 语句必须有 `else` 子句,而且必须在该子句中调用返回类型是 `Never` 的函数,或者使用下面的语句退出当前作用域: + + * `return` + * `break` + * `continue` + * `throw` + +关于控制转移语句,请参阅 [控制转移语句](#control_transfer_statements)。关于 `Never` 返回类型的函数,请参阅 [永不返回的函数](05_Declarations.md#rethrowing_functions_and_methods)。 + +> guard 语句语法 +> +> + +#### guard-statement {#guard-statement} +> *guard 语句* → **guard** [*条件子句*](#condition-clause) **else** [*代码块*] (05_Declarations.md#code-block) +> + +### Switch 语句 {#switch-statements} +`switch` 语句会根据控制表达式的值来决定执行哪部分代码。 + +`switch` 语句的形式如下: + +```swift +switch control expression { +case pattern 1: + statements +case pattern 2 where condition: + statements +case pattern 3 where condition, + pattern 4 where condition: + statements +default: + statements +} +``` + +`switch` 语句会先计算*控制表达式*的值,然后与每一个 `case` 的模式进行匹配。如果匹配成功,程序将会执行对应的 `case` 中的语句。另外,每一个 `case` 的作用域都不能为空,也就是说在每一个 `case` 的冒号(`:`)后面必须至少有一条语句。如果你不想在匹配到的 `case` 中执行代码,只需在该 `case` 中写一条 `break` 语句即可。 + +可以用作控制表达式的值是十分灵活的。除了标量类型外,如 `Int`、`Character`,你可以使用任何类型的值,包括浮点数、字符串、元组、自定义类型的实例和可选类型。控制表达式的值还可以用来匹配枚举类型中的成员值或是检查该值是否包含在指定的 `Range` 中。关于如何在 `switch` 语句中使用这些类型,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [Switch](../chapter2/05_Control_Flow.md#switch)。 + +每个 `case` 的模式后面可以有一个 `where` 子句。`where` 子句由 `where` 关键字紧跟一个提供额外条件的表达式组成。因此,当且仅当控制表达式匹配一个 `case` 的模式且 `where` 子句的表达式为真时,`case` 中的语句才会被执行。在下面的例子中,控制表达式只会匹配包含两个相等元素的元组,例如 `(1, 1)`: + +```swift +case let (x, y) where x == y: +``` + +正如上面这个例子,也可以在模式中使用 `let`(或 `var`)语句来绑定常量(或变量)。这些常量(或变量)可以在对应的 `where` 子句以及 `case` 中的代码中使用。但是,如果一个 `case` 中含有多个模式,所有的模式必须包含相同的常量(或变量)绑定,并且每一个绑定的常量(或变量)必须在所有的条件模式中都有相同的类型。 + +`switch` 语句也可以包含默认分支,使用 `default` 关键字表示。只有所有 `case` 都无法匹配控制表达式时,默认分支中的代码才会被执行。一个 `switch` 语句只能有一个默认分支,而且必须在 `switch` 语句的最后面。 + +`switch` 语句中 `case` 的匹配顺序和源代码中的书写顺序保持一致。因此,当多个模式都能匹配控制表达式时,只有第一个匹配的 `case` 中的代码会被执行。 + +#### Switch 语句必须是详尽的 + +在 Swift 中,`switch` 语句中控制表达式的每一个可能的值都必须至少有一个 `case` 与之对应。在某些无法面面俱到的情况下(例如,表达式的类型是 `Int`),你可以使用 `default` 分支满足该要求。 + +#### 对未来枚举的 `case` 进行 `switch` {#future-case} +非冻结枚举(`nonfronzen enumeration`)是一种特殊的枚举类型,它可能在未来会增加新的枚举 `case`,即使这时候你已经编译并且发布了你的应用,所以在 switch 非冻结枚举前需要深思熟虑。当一个库的作者们把一个枚举标记为非冻结的,这意味着他们保留了增加新的枚举 `case` 的权利,并且任何和这个枚举交互的代码都要在不需要重新编译的条件下能够处理那些未来可能新加入的 `case` 。只有那些标准库,比如用 Swift 实现的苹果的一些框架,C 以及 Objective-C 代码才能够声明非冻结枚举。你在 Swift 中声明的枚举不能是非冻结的。 + +当你对未来枚举进行 switch 时,你总是需要有一个 `default case`,即使每种枚举类型都已经有对应的 `case` 了。你可以在 default 前标注 `@unknown`,意思是这个 `case` 应该只匹配未来加入的枚举 `case`。如果你的 `default case` 中匹配了任何在编译时就能确定的枚举 `case`,Swift 会抛出一个警告。这可以很好地提醒你库的作者已经新增了一种 `case`,并且你还没有去处理。 + +以下就是一个例子,我们对标准库的 [Mirror.AncestorRepresentation](https://developer.apple.com/documentation/swift/mirror/ancestorrepresentation) 枚举进行 switch 操作。每当有新的 `case` 加入,我们会得到一个警告,提示我们要去处理它。 + +```swift +let representation: Mirror.AncestorRepresentation = .generated +switch representation { +case .customized: + print("Use the nearest ancestor’s implementation.") +case .generated: + print("Generate a default mirror for all ancestor classes.") +case .suppressed: + print("Suppress the representation of all ancestor classes.") +@unknown default: + print("Use a representation that was unknown when this code was compiled.") +} +// Prints "Generate a default mirror for all ancestor classes." +``` + +#### 不存在隐式落入 + +当匹配到的 `case` 中的代码执行完毕后,`switch` 语句会直接退出,而不会继续执行下一个 `case` 。这就意味着,如果你想执行下一个 `case`,需要显式地在当前 `case` 中使用 `fallthrough` 语句。关于 `fallthrough` 语句的更多信息,请参阅 [Fallthrough 语句](#fallthrough_statements)。 + +> switch 语句语法 +> +> + +#### switch-statement {#switch-statement} +> *switch 语句* → **switch** [*表达式*](./04_Expressions.md#expression) **{** [*switch-case 列表*](#switch-cases)可选 **}** +> + +#### switch-cases {#switch-cases} +> *switch case 列表* → [*switch-case*](#switch-case) [*switch-case 列表*](#switch-cases)可选 +> + +#### switch-case {#switch-case} +> *switch case* → [*case 标签*](#case-label) [*多条语句*](#statements) | [*default 标签*](#default-label) [*多条语句*](#statements) | [*conditional-switch-case*](#conditional-switch-case-label) +> + + +#### case-label {#case-label} +> *case 标签* → [*属性*](#switch-case-attributes-label)可选 **case** [*case 项列表*](#case-item-list) **:** +> + +#### case-item-list {#case-item-list} +> *case 项列表* → [*模式*](./08_Patterns.md#pattern) [*where 子句*](#where-clause)可选 | [*模式*](07_Patterns.md#pattern) [*where 子句*](#where-clause)可选 **,** [*case 项列表*](#case-item-list) +> + +#### default-label {#default-label} +> *default 标签* → [*属性*](#switch-case-attributes-label)可选 **default** **:** +> +> + +#### where-clause {#where-clause} +> *where-clause* → **where** [*where 表达式*](#where-expression) +> + +#### where-expression {#where-expression} +> *where-expression* → [*表达式*](./04_Expressions.md#expression) +> +> + +#### grammar_conditional-switch-case {#grammar-conditional-switch-case} +> *conditional-switch-case* → [*switch-if-directive-clause*](#switch-case-attributes-label) [*switch-elseif-directive-clauses*](#switch-case-attributes-label) 可选 [*switch-else-directive-clause*](#switch-case-attributes-label) 可选 [*endif-directive*](#switch-case-attributes-label) +> + +#### grammar_switch-if-directive-clause {#grammar-switch-if-directive-clause} +> *switch-if-directive 语句* → [*if-directive*](#switch-case-attributes-label) [*compilation-condition*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label) 可选 +> + +#### grammar_switch-elseif-directive-clauses {#grammar-switch-elseif-directive-clauses} +> *switch-elseif-directive 语句(复数)* → [*elseif-directive-clause*](#switch-case-attributes-label) [*switch-elseif-directive-clauses*](#switch-case-attributes-label)可选 +> + +#### grammar_switch-elseif-directive-clause {#grammar-switch-elseif-directive-clause} +> *switch-elseif-directive 语句* → [*elseif-directive*](#switch-case-attributes-label) [*compilation-condition*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label)可选 +> + +#### grammar_switch-else-directive-clause {#grammar-switch-else-directive-clause} +> *switch-else-directive 语句* → [*else-directive*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label) 可选 +> + +## 带标签的语句 {#labeled-statements} +你可以在循环语句或 `switch` 语句前面加上标签,它由标签名和紧随其后的冒号(`:`)组成。在 `break` 和 `continue` 后面跟上标签名可以显式地在循环语句或 `switch` 语句中改变相应的控制流。关于这两条语句用法,请参阅 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。 + +标签的作用域在该标签所标记的语句内。可以嵌套使用带标签的语句,但标签名必须唯一。 + +关于使用带标签的语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 + +> 带标签的语句语法 +> +> + +#### labeled-statement {#labeled-statement} +> *带标签的语句* → [*语句标签*](#statement-label) [*循环语句*](#grammar_loop-statement) +> +> *带标签的语句* → [*语句标签*](#statement-label) [*if 语句*](#if-statement) +> +> *带标签的语句* → [*语句标签*](#statement-label) [*switch 语句*](#switch-statement) +> +> > *带标签的语句* → [*语句标签*](#statement-label) [*do 语句*](#sdo-statement) +> + +#### statement-label {#statement-label} +> *语句标签* → [*标签名称*](#label-name) **:** +> + +#### label-name {#label-name} +> *标签名称* → [*标识符*](./02_Lexical_Structure.md#identifier) +> + +## 控制转移语句 {#control-transfer-statements} +控制转移语句能够无条件地把控制权从一片代码转移到另一片代码,从而改变代码执行的顺序。Swift 提供五种类型的控制转移语句:`break` 语句、`continue` 语句、`fallthrough` 语句、`return` 语句和 `throw` 语句。 + +> 控制转移语句语法 +> +> + +#### control-transfer-statement {#control-transfer-statement} +> *控制转移语句* → [*break 语句*](#break-statement) +> +> *控制转移语句* → [*continue 语句*](#continue-statement) +> +> *控制转移语句* → [*fallthrough 语句*](#fallthrough-statement) +> +> *控制转移语句* → [*return 语句*](#return-statement) +> +> *控制转移语句* → [*throw 语句*](#throw-statement) +> + +### Break 语句 {#break-statement} +`break` 语句用于终止循环语句、`if` 语句或 `switch` 语句的执行。使用 `break` 语句时,可以只写 `break` 这个关键词,也可以在 `break` 后面跟上标签名,像下面这样: + +> break +> +> break `label name` +> + +当 `break` 语句后面带标签名时,可用于终止由这个标签标记的循环语句、`if` 语句或 `switch` 语句的执行。 + +而只写 `break` 时,则会终止 `switch` 语句或 `break` 语句所属的最内层循环语句的执行。不能使用 `break` 语句来终止未使用标签的 `if` 语句。 + +无论哪种情况,控制权都会被转移给被终止的控制流语句后面的第一行语句。 + +关于使用 `break` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Break](../chapter2/05_Control_Flow.md#break) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 + +> break 语句语法 +> +> + +#### break-statement {#break-statement} +> *break 语句* → **break** [*标签名称*](#label-name)可选 +> + +### Continue 语句 {#continue-statement} +`continue` 语句用于终止循环中当前迭代的执行,但不会终止该循环的执行。使用 `continue` 语句时,可以只写 `continue` 这个关键词,也可以在 `continue` 后面跟上标签名,像下面这样: + +> continue +> +> continue `label name` +> + +当 `continue` 语句后面带标签名时,可用于终止由这个标签标记的循环中当前迭代的执行。 + +而当只写 `continue` 时,可用于终止 `continue` 语句所属的最内层循环中当前迭代的执行。 + +在这两种情况下,控制权都会被转移给循环语句的条件语句。 + +在 `for` 语句中,`continue` 语句执行后,增量表达式还是会被计算,这是因为每次循环体执行完毕后,增量表达式都会被计算。 + +关于使用 `continue` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Continue](../chapter2/05_Control_Flow.md#continue) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 + +> continue 语句语法 +> +> + +#### continue-statement {#continue-statement} +> *continue 语句* → **continue** [*标签名称*](#label-name)可选 +> + +### Fallthrough 语句 {#fallthrough-statements} +`fallthrough` 语句用于在 `switch` 语句中转移控制权。`fallthrough` 语句会把控制权从 `switch` 语句中的一个 `case` 转移到下一个 `case`。这种控制权转移是无条件的,即使下一个 `case` 的模式与 `switch` 语句的控制表达式的值不匹配。 + +`fallthrough` 语句可出现在 `switch` 语句中的任意 `case` 中,但不能出现在最后一个 `case` 中。同时,`fallthrough` 语句也不能把控制权转移到使用了值绑定的 `case`。 + +关于在 `switch` 语句中使用 `fallthrough` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [控制转移语句](../chapter2/05_Control_Flow.md#control_transfer_statements)。 + +> fallthrough 语句语法 +> +> + +#### fallthrough-statement {#fallthrough-statement} +> *fallthrough 语句* → **fallthrough** +> + +### Return 语句 {#return-statements} +`return` 语句用于在函数或方法的实现中将控制权转移到调用函数或方法,接着程序将会从调用位置继续向下执行。 + +使用 `return` 语句时,可以只写 `return` 这个关键词,也可以在 `return` 后面跟上表达式,像下面这样: + +> return +> +> return `expression` +> + +当 `return` 语句后面带表达式时,表达式的值将会返回给调用函数或方法。如果表达式的值的类型与函数或者方法声明的返回类型不匹配,Swift 则会在返回表达式的值之前将表达式的值的类型转换为返回类型。 + +> 注意 +> +> +> 正如 [可失败构造器](./06_Declarations.md#failable_initializers) 中所描述的,`return nil` 在可失败构造器中用于表明构造失败。 +> + +而只写 `return` 时,仅仅是从该函数或方法中返回,而不返回任何值(也就是说,函数或方法的返回类型为 `Void` 或者说 `()`)。 + +> return 语句语法 +> +> + +#### return-statement {#return-statement} +> *return 语句* → **return** [*表达式*](./04_Expressions.html#expression)可选 + +### Throw 语句 {#throw-statements} + +### Throw 语句 {#throw-statements} +`throw` 语句出现在抛出函数或者抛出方法体内,或者类型被 `throws` 关键字标记的闭包表达式体内。 + +`throw` 语句使程序在当前作用域结束执行,并向外围作用域传播错误。抛出的错误会一直传递,直到被 `do` 语句的 `catch` 子句处理掉。 + +`throw` 语句由 `throw` 关键字紧跟一个表达式组成,如下所示: + +> throw `expression` +> + +表达式的结果必须符合 `ErrorType` 协议。 + +关于如何使用 `throw` 语句的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md) 一章的 [用 throwing 函数传递错误](../chapter2/17_Error_Handling.md#propagating_errors_using_throwing_functions)。 + +> throw 语句语法 +> +> + +#### throw-statement {#throw-statement} +> *throw 语句* → **throw** [*表达式*](./04_Expressions.md#expression) +> + +## Defer 语句 {#defer-statements} +`defer` 语句用于在退出当前作用域之前执行代码。 + +`defer` 语句形式如下: + +```swift +defer { + statements +} +``` + +在 `defer` 语句中的语句无论程序控制如何转移都会被执行。在某些情况下,例如,手动管理资源时,比如关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 `defer` 语句。 + +如果多个 `defer` 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个 `defer` 语句,会在最后执行,这意味着代码中最靠后的 `defer` 语句中引用的资源可以被其他 `defer` 语句清理掉。 + +```swift +func f() { + defer { print("First") } + defer { print("Second") } + defer { print("Third") } +} +f() +// 打印“Third” +// 打印“Second” +// 打印“First” +``` + +`defer` 语句中的语句无法将控制权转移到 `defer` 语句外部。 + +> defer 语句语法 +> +> + +#### defer-statement {#defer-statement} +> *延迟语句* → **defer** [*代码块*](./06_Declarations.md#code-block) +> + +## Do 语句 {#do-statements} +`do` 语句用于引入一个新的作用域,该作用域中可以含有一个或多个 `catch` 子句,`catch` 子句中定义了一些匹配错误条件的模式。`do` 语句作用域内定义的常量和变量只能在 `do` 语句作用域内使用。 + +Swift 中的 `do` 语句与 C 中限定代码块界限的大括号(`{}`)很相似,也并不会降低程序运行时的性能。 + +`do` 语句的形式如下: + +```swift +do { + try expression + statements +} catch pattern 1 { + statements +} catch pattern 2 where condition { + statements +} +``` + +如同 `switch` 语句,编译器会判断 `catch` 子句是否有遗漏。如果 `catch` 子句没有遗漏,则认为错误已被处理。否则,错误会自动传递到外围作用域,被某个 `catch` 子句处理掉或者被用 `throws` 关键字声明的抛出函数继续向外抛出。 + +为了确保错误已经被处理,可以让 `catch` 子句使用匹配所有错误的模式,如通配符模式(`_`)。如果一个 `catch` 子句不指定一种具体模式,`catch` 子句会匹配任何错误,并绑定到名为 `error` 的局部常量。有关在 `catch` 子句中使用模式的更多信息,请参阅 [模式](./08_Patterns.md)。 + +关于如何在 `do` 语句中使用一系列 `catch` 子句的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md#handling_errors)。 + +> do 语句语法 +> +> + +#### do-statement {#do-statement} +> *do 语句* → **do** [*代码块*](./06_Declarations.md#code-block) [*多条 catch 子句*](#catch-clauses)可选 +> + +#### catch-clauses {#catch-clauses} +> *多条 catch 子句* → [*catch 子句*](#catch-clause) [*多条 catch 子句*](#catch-clauses)可选 +> + +#### catch-clause {#catch-clause} +> *catch 子句* → **catch** [*模式*](./08_Patterns.md#pattern)可选 [*where 子句*](#where-clause)可选 [*代码块*](05_Declarations.md#code-block) +> + +## 编译器控制语句 {#compiler-control-statements} +编译器控制语句允许程序改变编译器的行为。Swift 有三种编译器控制语句:条件编译语句、线路控制语句和编译时诊断语句。 + +> 编译器控制语句语法 +> +> + +#### compiler-control-statement {#compiler-control-statement} +> *编译器控制语句* → [*条件编译语句*](#grammar_conditional-compilation-block) +> +> *编译器控制语句* → [*线路控制语句*](#line-control-statement) +> +> *编译器控制语句* → [*诊断语句*](#grammar_diagnostic-statement) +> + +### 条件编译代码块 {#Conditional-Compilation-Block} +条件编译代码块可以根据一个或多个配置来有条件地编译代码。 + +每一个条件编译代码块都以 `#if` 开始,`#endif` 结束。如下: + +```swift +#if compilation condition +statements +#endif +``` + +和 `if` 语句的条件不同,编译配置的条件是在编译时进行判断的。只有编译配置在编译时判断为 `true` 的情况下,相应的语句才会被编译和执行。 + +编译配置可以是 `true` 和 `false` 的字面量,也可以是使用 `-D` 命令行标志的标识符,或者是下列表格中的任意一个平台检测函数。 + +| 函数 | 可用参数 | +| --- | --- | +| `os()` | `OSX`, `iOS`, `watchOS`, `tvOS`, `Linux` | +| `arch()` | `i386`, `x86_64`, `arm`, `arm64` | +| `swift()` | `>=` 或 `<` 后跟版本号 | +| `compiler()` | `>=` 或 `<` 后跟版本号 | +| `canImport()` | 模块名 | +| `targetEnvironment()` | 模拟器 | + +在 `swift()` 和 `compiler()` 之后的版本号包含有主版本号,可选副版本号,可选补丁版本号类似,并且用(`.`)来分隔。在比较符和版本号之间不能有空格,版本号与前面的函数相对应,比如 `compiler()` 对应的就是这个编译器的版本号,`swift()` 对应的就是你要编译的 `Swift` 语言的版本号。举个简单的例子,如果你在使用 `Swift 5` 的编译器,想编译 `Swift 4.2` ,可以看下面的例子: + +```swift +#if compiler(>=5) +print("Compiled with the Swift 5 compiler or later") +#endif +#if swift(>=4.2) +print("Compiled in Swift 4.2 mode or later") +#endif +#if compiler(>=5) && swift(<5) +print("Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5") +#endif +// 打印 "Compiled with the Swift 5 compiler or later" +// 打印 "Compiled in Swift 4.2 mode or later" +// 打印 "Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5" +``` + +`canImport()` 后面跟的变量是模块的名字,这里这个模块可能并不是每个平台上都存在的。使用它来检测是否可以导入这个模块,如果模块存在就返回 `true` 否则返回 `false` 。 + +`targetEnvironment()` 当为模拟器编译时返回 `true`,否则返回 `false` 。 + +> 注意 +> +> +> `arch(arm)` 平台检测函数在 ARM 64 位设备上不会返回 `true`。如果代码在 32 位的 iOS 模拟器上编译,`arch(i386)` 平台检测函数会返回 `true`。 +> + +你可以使用逻辑操作符 `&&`、`||` 和 `!` 来组合多个编译配置,还可以使用圆括号来进行分组。 + +就像 `if` 语句一样,你可以使用 `#elseif` 子句来添加任意多个条件分支来测试不同的编译配置。你也可以使用 `#else` 子句来添加最终的条件分支。包含多个分支的编译配置语句例子如下: + +```swift +#if compilation condition 1 +statements to compile if compilation condition 1 is true +#elseif compilation condition 2 +statements to compile if compilation condition 2 is true +#else +statements to compile if both compilation conditions are false +#endif +``` + +> 注意 +> +> +> 即使没有被编译,编译配置中的语句仍然会被解析。然而,唯一的例外是编译配置语句中包含语言版本检测函数:仅当 `Swift` 编译器版本和语言版本检测函数中指定的版本号匹配时,语句才会被解析。这种设定能确保旧的编译器不会尝试去解析新 Swift 版本的语法。 +> + + +#### build-config-statement {#build-config-statement} +> 条件编译代码块语法 +> +> + +#### grammar_conditional-compilation-block {#grammar-conditional-compilation-block} +> *条件编译代码块* → [*if-directive 语句*](#grammar_if-directive-clause) [*elseif-directive 语句(复数)*](#grammar_elseif-directive-clauses)可选 [*else-directive 语句*](#grammar_else-directive-clause)可选 [*endif-directive*](#grammar_endif-directive) +> + +#### grammar_if-directive-clause {#grammar-if-directive-clause} +> *if-directive 语句* → [*if-directive*](#grammar_if-directive) [*编译条件*](#compilation-condition) [*语句(复数)*](#statements)可选 +> + +#### grammar_elseif-directive-clauses {#grammar-elseif-directive-clauses} +> *elseif-directive 语句(复数)* → [*elseif-directive 语句*](#grammar_elseif-directive-clause) [*elseif-directive 语句(复数)*](#grammar_elseif-directive-clauses) +> + +#### grammar_elseif-directive-clauses {#grammar-elseif-directive-clauses} +> *elseif-directive 语句* → [*elseif-directive*](#grammar_elseif-directive) [*编译条件*](#compilation-condition) [*语句(复数)*](#statements)可选 +> + +#### grammar_else-directive-clause {#grammar-else-directive-clause} +> *else-directive 语句* → [*else-directive*](#grammar_else-directive) [*语句(复数)*](#statements)可选 +> + + +> *if-directive* → **#if** +> +> *elseif-directive* → **#elseif** +> +> *else-directive* → **#else** +> +> *endif-directive* → **#endif** +> + + +#### compilation-condition {#compilation-condition} +> *编译条件* → [*平台条件*](#grammar_platform-condition) +> +> *编译条件* → [*标识符*](./02_Lexical_Structure.md#identifier) +> +> *编译条件* → [*布尔值字面量*](./02_Lexical_Structure.md#boolean-literal) +> +> *编译条件* → **(** [*编译条件*](#compilation-condition) **)** +> +> *编译条件* → **!** [*编译条件*](#compilation-condition) +> +> *编译条件* → [*编译条件*](#compilation-condition) **&&** [*编译条件*](#compilation-condition) +> +> *编译条件* → [*编译条件*](#compilation-condition) **||** [*编译条件*](#compilation-condition) +> + + +#### grammar_platform-condition {#grammar-platform-condition} + +#### grammar_platform-condition-os {#grammar-platform-condition-os} +> *平台条件* → **os ( [*操作系统*](#operating-system) )** +> + +#### grammar_platform-condition-arch {#grammar-platform-condition-arch} +> *平台条件* → **arch ( [*架构*](#architecture) )** +> + +#### grammar_platform-condition-swift {#grammar-platform-condition-swift} +> *平台条件* → **swift ( >= [*swift 版本*](#swift-version) )** | **swift ( < [*swift 版本*](#swift-version) )** +> + +#### grammar_platform-condition-compiler {#grammar-platform-condition-compiler} +> *平台条件* → **compiler ( >= [*swift 版本*](#swift-version) )** | **compiler ( < [*swift 版本*](#swift-version) )** +> + +#### grammar_platform-condition-canImport {#grammar-platform-condition-canImport} +> *平台条件* → **canImport ( [*模块名*](#grammar_module-name) )** +> + +#### grammar_platform-condition-targetEnvironment {#grammar-platform-condition-targetEnvironment} +> *平台条件* → **targetEnvironment ( [*环境*](#grammar_environment) )** +> + +#### operating-system {#operating-system} +> *操作系统* → **macOS** | **iOS** | **watchOS** | **tvOS** +> + +#### architecture {#architecture} +> *架构* → **i386** | **x86_64** | **arm** | **arm64** +> + +#### swift-version {#swift-version} +> *swift 版本* → [*十进制数字*](./02_Lexical_Structure.md#decimal-digit) ­**.** ­[*swift 版本延续*](#grammar_swift-version-continuation) 可选 +> + +#### grammar_swift-version-continuation {#grammar-swift-version-continuation} +> *swift 版本延续* → **.** [*十进制数字*](./02_Lexical_Structure.md#decimal-digit) [*swift 版本延续*](#grammar_swift-version-continuation) 可选 +> + +#### grammar_module-name {#grammar-module-name} +> *模块名* → [*identifier*](./02_Lexical_Structure.md#identifier) +> + +#### grammar_environment {#grammar-environment} +> *环境* → **模拟器** +> + +### 行控制语句 {#line-control-statements} +行控制语句可以为被编译的源代码指定行号和文件名,从而改变源代码的定位信息,以便进行分析和调试。 + +行控制语句形式如下: + +> \#sourceLocation(file: `filename` , line:`line number`) +> + +> \#sourceLocation() +> + +第一种的行控制语句会改变该语句之后的代码中的字面量表达式 `#line` 和 `#file` 所表示的值。`行号` 是一个大于 0 的整形字面量,会改变 `#line` 表达式的值。`文件名` 是一个字符串字面量,会改变 `#file` 表达式的值。 + +第二种的行控制语句,`#sourceLocation()`,会将源代码的定位信息重置回默认的行号和文件名。 + + +#### line-control-statement {#line-control-statement} +> 行控制语句语法 +> +> +> *行控制语句* → **#sourceLocation(file:[*文件名*](#file-name),line:[*行号*](#line-number))** +> +> *行控制语句* → **#sourceLocation()** +> + +#### line-number {#line-number} +> *行号* → 大于 0 的十进制整数 +> + +#### file-name {#file-name} +> *文件名* → [*静态字符串字面量*](./02_Lexical_Structure.md#static-string-literal) +> + +### 编译时诊断语句 {#compile-time-diagnostic-statement} + +编译时诊断语句允许编译器在编译的时候可以发出错误或者警告。语句形式如下: + +```swift +#error("error message") +#warning("warning message") +``` + +第一句会抛出错误信息并终止编译,第二句会发出警告信息但是编译会继续进行。你可以通过静态字符串字面量来书写诊断信息,静态字符串字面量不能使用字符串 `interpolation` 或者 `concatenation`,但可以使用多行的形式。 + +> 编译时诊断语句语法 +> +> + +#### grammar_compile-time-diagnostic-statement {#grammar-compile-time-diagnostic-statement} +> *诊断语句* → **#error** **(** [*diagnostic-message*](#grammar_diagnostic-message) **)** +> +> *诊断语句* → **#warning** **(** [*diagnostic-message*](#grammar_diagnostic-message) **)** +> +> *诊断语句* → [*静态字符串字面量*](./02_Lexical_Structure.md#static-string-literal) +> + +## 可用性条件 {#availability-condition} +可用性条件可作为 `if`,`while`,`guard` 语句的条件,可以在运行时基于特定的平台参数来查询 API 的可用性。 + +可用性条件的形式如下: + +```swift +if #available(platform name version, ..., *) { + statements to execute if the APIs are available +} else { + fallback statements to execute if the APIs are unavailable +} +``` + +使用可用性条件来执行一个代码块时,取决于使用的 API 在运行时是否可用,编译器会根据可用性条件提供的信息来决定是否执行相应的代码块。 + +可用性条件使用一系列逗号分隔的平台名称和版本。使用 `iOS`,`OSX`,以及 `watchOS` 等作为平台名称,并写上相应的版本号。`*` 参数是必须写的,用于处理未来的潜在平台。可用性条件确保了运行时的平台不低于条件中指定的平台版本时才执行代码块。 + +与布尔类型的条件不同,不能用逻辑运算符 `&&` 和 `||` 组合可用性条件。 + +> 可用性条件语法 +> +> + +#### availability-condition {#availability-condition} +> *可用性条件* → **#available** **(** [*可用性参数列表*](#availability-arguments) **)** +> + +#### availability-arguments {#availability-arguments} +> *可用性参数列表* → [*可用性参数*](#availability-argument) | [*可用性参数*](#availability-argument) **,** [*可用性参数列表*](#availability-arguments) +> + +#### availability-argument {#availability-argument} +> *可用性参数* → [平台名称](#platform-name) [平台版本](#platform-version) +> +> *可用性条件* → __*__ +> +> + +#### platform-name {#platform-name} +> *平台名称* → **iOS** | **iOSApplicationExtension** +> +> *平台名称* → **OSX** | **macOSApplicationExtension** +> +> *平台名称* → **watchOS** +> +> *平台名称* → **tvOS** +> + +#### platform-version {#platform-version} +> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) +> +> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) +> +> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) +> diff --git a/source/chapter3/06_Declarations.md b/source/chapter3/06_Declarations.md index e501579e..d672ab1a 100755 --- a/source/chapter3/06_Declarations.md +++ b/source/chapter3/06_Declarations.md @@ -584,7 +584,7 @@ func someFunction(callback: () throws -> Void) rethrows { ### 永不返回的函数 {#functions-that-never-return} Swift 定义了 `Never` 类型,它表示函数或者方法不会返回给它的调用者。`Never` 返回类型的函数或方法可以称为不归,不归函数、方法要么引发不可恢复的错误,要么永远不停地运作,这会使调用后本应执行得代码就不再执行了。但即使是不归函数、方法,抛错函数和重抛出函数也可以将程序控制转移到合适的 `catch` 代码块。 -不归函数、方法可以在 guard 语句的 else 字句中调用,具体讨论在[*Guard 语句*](./05_Statements.md#guard_statements)。 +不归函数、方法可以在 guard 语句的 else 字句中调用,具体讨论在 [*Guard 语句*](./05_Statements.md#guard_statements)。 你可以重写一个不归方法,但是新的方法必须保持原有的返回类型和没有返回的行为。 @@ -877,7 +877,7 @@ struct 结构体名称: 采纳的协议 { 结构体实例的属性可以用点语法(`.`)来访问,正如 [访问属性](../chapter2/09_Structures_And_Classes.md#accessing_properties) 所述。 -结构体是值类型。结构体的实例在被赋予变量或常量,或传递给函数作为参数时会被复制。关于值类型的更多信息,请参阅 +结构体是值类型。结构体的实例在被赋予变量或常量,或传递给函数作为参数时会被复制。关于值类型的更多信息,请参阅 [结构体和枚举是值类型](../chapter2/09_Structures_And_Classes.md#structures_and_enumerations_are_value_types)。 可以使用扩展声明来扩展结构体类型的行为,请参阅 [扩展声明](#extension_declaration)。 @@ -998,7 +998,7 @@ protocol 协议名称: 继承的协议 { 可以通过类型的扩展声明来采纳协议,从而为之前声明的类型添加协议一致性。在扩展中,必须实现所有采纳协议的要求。如果该类型已经实现了所有的要求,可以让这个扩展声明的主体留空。 -默认地,符合某个协议的类型必须实现所有在协议中声明的属性、方法和下标。即便如此,可以用 `optional` 声明修饰符标注协议成员声明,以指定它们的实现是可选的。`optional` 修饰符仅仅可以用于使用 `objc` 特性标记过的协议。因此,仅仅类类型可以采用并符合包含可选成员要求的协议。更多关于如何使用 `optional` 声明修饰符的信息,以及如何访问可选协议成员的指导——例如不能确定采纳协议的类型是否实现了它们时——请参阅 [可选协议要求](../chapter2/21_Protocols.md#optional_protocol_requirements) +默认地,符合某个协议的类型必须实现所有在协议中声明的属性、方法和下标。即便如此,可以用 `optional` 声明修饰符标注协议成员声明,以指定它们的实现是可选的。`optional` 修饰符仅仅可以用于使用 `objc` 特性标记过的协议。因此,仅仅类类型可以采用并符合包含可选成员要求的协议。更多关于如何使用 `optional` 声明修饰符的信息,以及如何访问可选协议成员的指导——例如不能确定采纳协议的类型是否实现了它们时——请参阅 [可选协议要求](../chapter2/21_Protocols.md#optional_protocol_requirements)。 为了限制协议只能被类类型采纳,需要使用 `AnyObject` 关键字来标记协议,将 `AnyObject` 关键在写在冒号后面的继承的协议列表的首位。例如,下面的协议只能被类类型采纳: @@ -1660,7 +1660,7 @@ precedencegroup 优先级组名称{ > 使用较低和较高优先级组相互联系的优先级组必须保持单一层次关系,但它们不必是线性关系。这意味着优先级组也许会有未定义的相关优先级。这些优先级组的运算符在没有用圆括号分组的情况下是不能紧邻着使用的。 > -Swift 定义了大量的优先级组来与标准库的运算符配合使用,例如相加(`+`)和相减(`-`)属于 `AdditionPrecedence` 组,相乘(`*`)和相除(`/`)属于 `MultiplicationPrecedence` 组,详细关于 Swift 标准库中一系列运算符和优先级组内容,参阅[Swift 标准库操作符参考](https://developer.apple.com/documentation/swift/operator_declarations)。 +Swift 定义了大量的优先级组来与标准库的运算符配合使用,例如相加(`+`)和相减(`-`)属于 `AdditionPrecedence` 组,相乘(`*`)和相除(`/`)属于 `MultiplicationPrecedence` 组,详细关于 Swift 标准库中一系列运算符和优先级组内容,参阅 [Swift 标准库操作符参考](https://developer.apple.com/documentation/swift/operator_declarations)。 运算符的结合性表示在没有圆括号分组的情况下,同样优先级的一系列运算符是如何被分组的。你可以指定运算符的结合性通过上下文关键字 `left`、`right` 或者 `none`,如果没有指定结合性,默认是 `none` 关键字。左关联性的运算符是从左至右分组的,例如,相减操作符(-)是左关联性的,所以表达式 `4 - 5 - 6` 被分组为 `(4 - 5) - 6`,得出结果-7。右关联性的运算符是从右往左分组的,指定为 `none` 结合性的运算符就没有结合性。同样优先级没有结合性的运算符不能相邻出现,例如 `<` 运算符是 `none` 结合性,那表示 `1 < 2 < 3` 就不是一个有效表达式。 @@ -1760,7 +1760,7 @@ Swift 定义了大量的优先级组来与标准库的运算符配合使用, `unowned(unsafe)` -该修饰符用于修饰存储型变量、常量或者存储型变量属性,表示该变量或属性持有其存储对象的无主引用。如果在此存储对象释放后尝试访问该对象,会直接访问该对象释放前存储的内存地址,因此这是非内存安全的操作。如同弱引用一样,该引用类型的变量或属性必须是类类型。与弱引用不同的是,这种类型的变量或属性是非可选的。关于 `unowned` 更多的信息和例子,请参阅 [无主引用]( +该修饰符用于修饰存储型变量、常量或者存储型变量属性,表示该变量或属性持有其存储对象的无主引用。如果在此存储对象释放后尝试访问该对象,会直接访问该对象释放前存储的内存地址,因此这是非内存安全的操作。如同弱引用一样,该引用类型的变量或属性必须是类类型。与弱引用不同的是,这种类型的变量或属性是非可选的。关于 `unowned` 更多的信息和例子,请参阅 [无主引用](../chapter2/23_Automatic_Reference_Counting.md#resolving_strong_reference_cycles_between_class_instances)。 `weak` diff --git a/source/chapter3/09_Generic_Parameters_and_Arguments.md b/source/chapter3/09_Generic_Parameters_and_Arguments.md index 48959852..98b487ba 100755 --- a/source/chapter3/09_Generic_Parameters_and_Arguments.md +++ b/source/chapter3/09_Generic_Parameters_and_Arguments.md @@ -1,136 +1,136 @@ -# 泛型参数(Generic Parameters and Arguments) - -本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之。 - -关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/22_Generics.md)。 - -## 泛型形参子句 {#generic-parameter} -*泛型形参子句*指定泛型类型或函数的类型形参,以及这些参数相关的约束和要求。泛型形参子句用尖括号(`<>`)包住,形式如下: - -> <`泛型形参列表`> -> - -泛型形参列表中泛型形参用逗号分开,其中每一个采用以下形式: - -> `类型形参` : `约束` -> - -泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 `T`,`U`,`V`,`Key`,`Value` 等)的名字而已。你可以在泛型类型、函数的其余部分或者构造器声明,包括函数或构造器的签名中使用它(以及它的关联类型)。 - -约束用于指明该类型形参继承自某个类或者符合某个协议或协议组合。例如,在下面的泛型函数中,泛型形参 `T: Comparable` 表示任何用于替代类型形参 `T` 的类型实参必须满足 `Comparable` 协议。 - -```swift -func simpleMax(_ x: T, _ y: T) -> T { - if x < y { - return y - } - return x -} -``` - -例如,因为 `Int` 和 `Double` 均满足 `Comparable` 协议,所以该函数可以接受这两种类型。与泛型类型相反,调用泛型函数或构造器时不需要指定泛型实参子句。类型实参由传递给函数或构造器的实参推断而出。 - -```swift -simpleMax(17, 42) // T 被推断为 Int 类型 -simpleMax(3.14159, 2.71828) // T 被推断为 Double 类型 -``` - -### Where 子句 {#where-clauses} -要想对类型形参及其关联类型指定额外要求,可以在函数体或者类型的大括号之前添加 `where` 子句。`where` 子句由关键字 `where` 及其后的用逗号分隔的一个或多个要求组成。 - -> `where` : `类型要求` -> - -`where` 子句中的要求用于指明该类型形参继承自某个类或符合某个协议或协议组合。尽管 `where` 子句提供了语法糖使其有助于表达类型形参上的简单约束(如 `` 等同于 ` where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束,例如你可以强制形参的关联类型遵守协议,如,` where S.Iterator.Element: Equatable` 表示泛型类型 `S` 遵守 `Sequence` 协议并且关联类型 `S.Iterator.Element` 遵守 `Equatable` 协议,这个约束确保队列的每一个元素都是符合 `Equatable` 协议的。 -> - -也可以用操作符 `==` 来指定两个类型必须相同。例如,泛型形参子句 ` where S1.Iterator.Element == S2.Iterator.Element` 表示 `S1` 和 `S2` 必须都符合 `SequenceType` 协议,而且两个序列中的元素类型必须相同。 -> - -当然,替代类型形参的类型实参必须满足所有的约束和要求。 - -泛型函数或构造器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或要求,抑或二者皆不同。当调用重载的泛型函数或构造器时,编译器会根据这些约束来决定调用哪个重载函数或构造器。 - -更多关于泛型 where 从句的信息和关于泛型函数声明的例子,可以看一看 [泛型 where 子句](../chapter2/22_Generics.md#where_clauses) - -> 泛型形参子句语法 -> - -#### generic-parameter-clause {#generic-parameter-clause} -> *泛型形参子句* → **<** [*泛型形参列表*](#generic-parameter-list) [*约束子句*](#requirement-clause)可选 **>** -> - -#### generic-parameter-list {#generic-parameter-list} -> *泛型形参列表* → [*泛形形参*](#generic-parameter) | [*泛形形参*](#generic-parameter) **,** [*泛型形参列表*](#generic-parameter-list) -> - -#### generic-parameter {#generic-parameter} -> *泛形形参* → [*类型名称*](./03_Types.md#type-name) -> -> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*类型标识符*](./03_Types.md#type-identifier) -> -> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type) -> -> -#### requirement-clause {#requirement-clause} -> -> *约束子句* → **where** [*约束列表*](#requirement-list) -> - -#### requirement-list {#requirement-list} -> *约束列表* → [*约束*](#requirement) | [*约束*](#requirement) **,** [*约束列表*](#requirement-list) -> - -#### requirement {#requirement} -> *约束* → [*一致性约束*](#conformance-requirement) | [*同类型约束*](#same-type-requirement) -> -> -#### conformance-requirement {#conformance-requirement} -> -> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*类型标识符*](./03_Types.md#type-identifier) -> -> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type) -> - -#### same-type-requirement {#same-type-requirement} -> *同类型约束* → [*类型标识符*](./03_Types.md#type-identifier) **==** [*类型*](./03_Types.md#type) -> - -## 泛型实参子句 {#generic-argument} -*泛型实参子句*指定泛型类型的类型实参。泛型实参子句用尖括号(`<>`)包住,形式如下: - -> <`泛型实参列表`> -> - -泛型实参列表中类型实参用逗号分开。类型实参是实际具体类型的名字,用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。例如,Swift 标准库中的泛型字典类型的的简化定义如下: - -```swift -struct Dictionary: CollectionType, DictionaryLiteralConvertible { - /* ... */ -} -``` - -泛型 `Dictionary` 类型的特化版本,`Dictionary` 就是用具体的 `String` 和 `Int` 类型替代泛型类型 `Key: Hashable` 和 `Value` 产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何 `where` 子句所指定的额外的关联类型要求。上面的例子中,类型形参 `Key` 的类型必须符合 `Hashable` 协议,因此 `String` 也必须满足 `Hashable` 协议。 - -可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本 `Array` 替代泛型类型 `Array` 的类型形参 `T` 来实现。 - -```swift -let arrayOfArrays: Array> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -``` - -如 [泛型形参子句](#generic_parameter) 所述,不能用泛型实参子句来指定泛型函数或构造器的类型实参。 - -> 泛型实参子句语法 -> - -#### generic-argument-clause {#generic-argument-clause} -> *泛型实参子句* → **<** [*泛型实参列表*](#generic-argument-list) **>** -> - -#### generic-argument-list {#generic-argument-list} -> *泛型实参列表* → [*泛型实参*](#generic-argument) | [*泛型实参*](#generic-argument) **,** [*泛型实参列表*](#generic-argument-list) -> - -#### generic-argument {#generic-argument} -> *泛型实参* → [*类型*](./03_Types.md#type) -> +# 泛型参数(Generic Parameters and Arguments) + +本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之。 + +关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/22_Generics.md)。 + +## 泛型形参子句 {#generic-parameter} +*泛型形参子句*指定泛型类型或函数的类型形参,以及这些参数相关的约束和要求。泛型形参子句用尖括号(`<>`)包住,形式如下: + +> <`泛型形参列表`> +> + +泛型形参列表中泛型形参用逗号分开,其中每一个采用以下形式: + +> `类型形参` : `约束` +> + +泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 `T`,`U`,`V`,`Key`,`Value` 等)的名字而已。你可以在泛型类型、函数的其余部分或者构造器声明,包括函数或构造器的签名中使用它(以及它的关联类型)。 + +约束用于指明该类型形参继承自某个类或者符合某个协议或协议组合。例如,在下面的泛型函数中,泛型形参 `T: Comparable` 表示任何用于替代类型形参 `T` 的类型实参必须满足 `Comparable` 协议。 + +```swift +func simpleMax(_ x: T, _ y: T) -> T { + if x < y { + return y + } + return x +} +``` + +例如,因为 `Int` 和 `Double` 均满足 `Comparable` 协议,所以该函数可以接受这两种类型。与泛型类型相反,调用泛型函数或构造器时不需要指定泛型实参子句。类型实参由传递给函数或构造器的实参推断而出。 + +```swift +simpleMax(17, 42) // T 被推断为 Int 类型 +simpleMax(3.14159, 2.71828) // T 被推断为 Double 类型 +``` + +### Where 子句 {#where-clauses} +要想对类型形参及其关联类型指定额外要求,可以在函数体或者类型的大括号之前添加 `where` 子句。`where` 子句由关键字 `where` 及其后的用逗号分隔的一个或多个要求组成。 + +> `where` : `类型要求` +> + +`where` 子句中的要求用于指明该类型形参继承自某个类或符合某个协议或协议组合。尽管 `where` 子句提供了语法糖使其有助于表达类型形参上的简单约束(如 `` 等同于 ` where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束,例如你可以强制形参的关联类型遵守协议,如,` where S.Iterator.Element: Equatable` 表示泛型类型 `S` 遵守 `Sequence` 协议并且关联类型 `S.Iterator.Element` 遵守 `Equatable` 协议,这个约束确保队列的每一个元素都是符合 `Equatable` 协议的。 +> + +也可以用操作符 `==` 来指定两个类型必须相同。例如,泛型形参子句 ` where S1.Iterator.Element == S2.Iterator.Element` 表示 `S1` 和 `S2` 必须都符合 `SequenceType` 协议,而且两个序列中的元素类型必须相同。 +> + +当然,替代类型形参的类型实参必须满足所有的约束和要求。 + +泛型函数或构造器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或要求,抑或二者皆不同。当调用重载的泛型函数或构造器时,编译器会根据这些约束来决定调用哪个重载函数或构造器。 + +更多关于泛型 where 从句的信息和关于泛型函数声明的例子,可以看一看 [泛型 where 子句](../chapter2/22_Generics.md#where_clauses)。 + +> 泛型形参子句语法 +> + +#### generic-parameter-clause {#generic-parameter-clause} +> *泛型形参子句* → **<** [*泛型形参列表*](#generic-parameter-list) [*约束子句*](#requirement-clause)可选 **>** +> + +#### generic-parameter-list {#generic-parameter-list} +> *泛型形参列表* → [*泛形形参*](#generic-parameter) | [*泛形形参*](#generic-parameter) **,** [*泛型形参列表*](#generic-parameter-list) +> + +#### generic-parameter {#generic-parameter} +> *泛形形参* → [*类型名称*](./03_Types.md#type-name) +> +> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*类型标识符*](./03_Types.md#type-identifier) +> +> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type) +> +> +#### requirement-clause {#requirement-clause} +> +> *约束子句* → **where** [*约束列表*](#requirement-list) +> + +#### requirement-list {#requirement-list} +> *约束列表* → [*约束*](#requirement) | [*约束*](#requirement) **,** [*约束列表*](#requirement-list) +> + +#### requirement {#requirement} +> *约束* → [*一致性约束*](#conformance-requirement) | [*同类型约束*](#same-type-requirement) +> +> +#### conformance-requirement {#conformance-requirement} +> +> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*类型标识符*](./03_Types.md#type-identifier) +> +> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type) +> + +#### same-type-requirement {#same-type-requirement} +> *同类型约束* → [*类型标识符*](./03_Types.md#type-identifier) **==** [*类型*](./03_Types.md#type) +> + +## 泛型实参子句 {#generic-argument} +*泛型实参子句*指定泛型类型的类型实参。泛型实参子句用尖括号(`<>`)包住,形式如下: + +> <`泛型实参列表`> +> + +泛型实参列表中类型实参用逗号分开。类型实参是实际具体类型的名字,用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。例如,Swift 标准库中的泛型字典类型的的简化定义如下: + +```swift +struct Dictionary: CollectionType, DictionaryLiteralConvertible { + /* ... */ +} +``` + +泛型 `Dictionary` 类型的特化版本,`Dictionary` 就是用具体的 `String` 和 `Int` 类型替代泛型类型 `Key: Hashable` 和 `Value` 产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何 `where` 子句所指定的额外的关联类型要求。上面的例子中,类型形参 `Key` 的类型必须符合 `Hashable` 协议,因此 `String` 也必须满足 `Hashable` 协议。 + +可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本 `Array` 替代泛型类型 `Array` 的类型形参 `T` 来实现。 + +```swift +let arrayOfArrays: Array> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +``` + +如 [泛型形参子句](#generic_parameter) 所述,不能用泛型实参子句来指定泛型函数或构造器的类型实参。 + +> 泛型实参子句语法 +> + +#### generic-argument-clause {#generic-argument-clause} +> *泛型实参子句* → **<** [*泛型实参列表*](#generic-argument-list) **>** +> + +#### generic-argument-list {#generic-argument-list} +> *泛型实参列表* → [*泛型实参*](#generic-argument) | [*泛型实参*](#generic-argument) **,** [*泛型实参列表*](#generic-argument-list) +> + +#### generic-argument {#generic-argument} +> *泛型实参* → [*类型*](./03_Types.md#type) +>