From cef9d9aa6f1a6148e3fcb75cfc229102075af87a Mon Sep 17 00:00:00 2001 From: Channe Date: Fri, 9 Oct 2015 00:23:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?ARC=20=E6=8E=AA=E8=BE=9E=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/chapter2/16_Automatic_Reference_Counting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/chapter2/16_Automatic_Reference_Counting.md b/source/chapter2/16_Automatic_Reference_Counting.md index ca246151..307ed1b9 100755 --- a/source/chapter2/16_Automatic_Reference_Counting.md +++ b/source/chapter2/16_Automatic_Reference_Counting.md @@ -17,7 +17,7 @@ - [闭包引起的循环强引用](#strong_reference_cycles_for_closures) - [解决闭包引起的循环强引用](#resolving_strong_reference_cycles_for_closures) -Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 的内存管理机制会一直起着作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。 +Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。 然而,在少数情况下,ARC 为了能帮助你管理内存,需要更多的关于你的代码之间关系的信息。本章描述了这些情况,并且为你示范怎样启用 ARC 来管理你的应用程序的内存。 From 32d571b6aa165c4883189dc64d6fe1829e256440 Mon Sep 17 00:00:00 2001 From: Channe Date: Sat, 10 Oct 2015 20:06:34 +0800 Subject: [PATCH 2/3] =?UTF-8?q?Swift=202.0=202015.09.16=20-=20ARC=20?= =?UTF-8?q?=E6=9E=9A=E4=B8=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/chapter2/08_Enumerations.md | 34 +++++++++---------- .../16_Automatic_Reference_Counting.md | 27 +++++++++++++-- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/source/chapter2/08_Enumerations.md b/source/chapter2/08_Enumerations.md index 89160fc9..fc4fdae7 100755 --- a/source/chapter2/08_Enumerations.md +++ b/source/chapter2/08_Enumerations.md @@ -16,13 +16,13 @@ - [原始值(Raw Values)](#raw_values) - [递归枚举(Recursive Enumerations)](#recursive_enumerations) -*枚举*定义了一个通用类型的一组相关值,使你可以在你的代码中以一种安全的方式来使用这些值。 +**枚举**定义了一个通用类型的一组相关值,使你可以在你的代码中以一种安全的方式来使用这些值。 如果你熟悉 C 语言,你就会知道,在 C 语言中枚举将枚举名和一个整型值相对应。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。 此外,枚举成员可以指定任何类型的相关值存储到枚举成员值中,就像其他语言中的联合体(unions)和变体(variants)。你可以定义一组通用的相关成员作为枚举的一部分,每一组都有不同的一组与它相关的适当类型的数值。 -在 Swift 中,枚举类型是一等公民(first-class)。它们采用了很多传统上只被类(class)所支持的特征,例如计算型属性(computed properties),用于提供关于枚举当前值的附加信息, 实例方法(instance methods),用于提供和枚举所代表的值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始的实现基础上扩展它们的功能;可以遵守协议(protocols)来提供标准的功能。 +在 Swift 中,枚举类型是一等公民(first-class)。它们采用了很多传统上只被类(class)所支持的特征,例如计算型属性(computed properties),用于提供关于枚举当前值的附加信息,实例方法(instance methods),用于提供和枚举所代表的值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始的实现基础上扩展它们的功能;可以遵守协议(protocols)来提供标准的功能。 欲了解更多相关信息,请参见[属性(Properties)](./10_Properties.html),[方法(Methods)](./11_Methods.html),[构造过程(Initialization)](./14_Initialization.html),[扩展(Extensions)](./20_Extensions.html)和[协议(Protocols)](./21_Protocols.html)。 @@ -33,11 +33,11 @@ ```swift enum SomeEnumeration { - // enumeration definition goes here + // 枚举定义放在这里 } ``` -以下是指南针四个方向的一个例子: +下面是指南针四个方向的例子: ```swift enum CompassPoint { @@ -48,10 +48,10 @@ enum CompassPoint { } ``` -一个枚举中被定义的值(例如 `North`,`South`,`East`和`West`)是枚举的*成员值*(或者*成员*)。`case`关键词表明新的一行成员值将被定义。 +枚举中定义的值(如 `North`,`South`,`East`和`West`)是这个枚举的**成员值**(或**成员**)。`case`关键词表示一行新的成员值将被定义。 -> 注意: -> 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoints`例子中,`North`,`South`,`East`和`West`不会隐式地赋值为了`0`,`1`,`2`和`3`。相反的,这些不同的枚举成员在`CompassPoint`的一种显示定义中拥有各自不同的值。 +> 注意: +> 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoint`例子中,`North`,`South`,`East`和`West`不会隐式地赋值为`0`,`1`,`2`和`3`。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的`CompassPoint`类型。 多个成员值可以出现在同一行上,用逗号隔开: @@ -83,14 +83,14 @@ directionToHead = .East ```swift directionToHead = .South switch directionToHead { -case .North: - print("Lots of planets have a north") -case .South: - print("Watch out for penguins") -case .East: - print("Where the sun rises") -case .West: - print("Where the skies are blue") + case .North: + print("Lots of planets have a north") + case .South: + print("Watch out for penguins") + case .East: + print("Where the sun rises") + case .West: + print("Where the skies are blue") } // 输出 "Watch out for penguins” ``` @@ -131,7 +131,7 @@ default: -对于库存跟踪系统来说,能够把 UPC-A 码作为三个整型值的元组,和把 QR 码作为一个任何长度的字符串存储起来是方便的。 +对于库存跟踪系统来说,能够把 UPC-A 码作为四个整型值的元组,和把 QR 码作为一个任何长度的字符串存储起来是方便的。 在 Swift 中,使用如下方式定义两种商品条码的枚举: @@ -229,7 +229,7 @@ enum Planet: Int { 当使用字符串作为枚举类型的初值时,每个枚举成员的隐式初值则为该成员的名称。 -下面的例子是`CompassPoint`枚举类型的精简版,使用字符串作为初值类型,隐式初始化为咩个方向的名称: +下面的例子是`CompassPoint`枚举类型的精简版,使用字符串作为初值类型,隐式初始化为各个方向的名称: ```swift enum CompassPoint: String { diff --git a/source/chapter2/16_Automatic_Reference_Counting.md b/source/chapter2/16_Automatic_Reference_Counting.md index 307ed1b9..55b76166 100755 --- a/source/chapter2/16_Automatic_Reference_Counting.md +++ b/source/chapter2/16_Automatic_Reference_Counting.md @@ -260,7 +260,14 @@ unit4A = nil ``` 上面的两段代码展示了变量`john`和`unit4A`在被赋值为`nil`后,`Person`实例和`Apartment`实例的析构函数都打印出“销毁”的信息。这证明了引用循环被打破了。 - + + + >注意: + 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被删除,就会被立即销毁,这导致弱引用并不适合上面的用途。 + ### 无主引用 @@ -441,6 +448,20 @@ class HTMLElement { 默认情况下,闭包赋值给了`asHTML`属性,这个闭包返回一个代表 HTML 标签的字符串。如果`text`值存在,该标签就包含可选值`text`;如果`text`不存在,该标签就不包含文本。对于段落元素,根据`text`是`"some text"`还是`nil`,闭包会返回"`

some text

`"或者"`

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

some default text

" +``` > 注意: `asHTML`声明为`lazy`属性,因为只有当元素确实需要处理为HTML输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问`lazy`属性。 @@ -480,8 +501,8 @@ paragraph = nil 在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。 ->注意: -Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod`(而不只是`someProperty`或`someMethod`)。这提醒你可能会一不小心就捕获了`self`。 +>注意: +Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod()`(而不只是`someProperty`或`someMethod()`)。这提醒你可能会一不小心就捕获了`self`。 ###定义捕获列表 From 19c491cdfab208af6901e9a854c807775fa0a029 Mon Sep 17 00:00:00 2001 From: Channe Date: Wed, 14 Oct 2015 21:38:24 +0800 Subject: [PATCH 3/3] =?UTF-8?q?Swift=202.0=202015.09.16=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/chapter2/08_Enumerations.md | 21 ++++---- source/chapter2/14_Initialization.md | 72 +++++++++++++++++----------- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/source/chapter2/08_Enumerations.md b/source/chapter2/08_Enumerations.md index fc4fdae7..81565015 100755 --- a/source/chapter2/08_Enumerations.md +++ b/source/chapter2/08_Enumerations.md @@ -119,15 +119,15 @@ default: ## 相关值(Associated Values) -上一小节的例子演示了如何定义(分类)枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并且在赋值之后查看这个值。不管怎样,如果有时候能够把其他类型的*相关值*和成员值一起存储起来会很有用。这能让你存储成员值之外的自定义信息,并且当你每次在代码中使用该成员时允许这个信息产生变化。 +上一小节的例子演示了如何定义(分类)枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并且在赋值之后查看这个值。不管怎样,如果有时候能够把其他类型的**相关值**和成员值一起存储起来会很有用。这能让你存储成员值之外的自定义信息,并且当你每次在代码中使用该成员时允许这个信息产生变化。 你可以定义 Swift 的枚举存储任何类型的相关值,如果需要的话,每个成员的数据类型可以是各不相同的。枚举的这种特性跟其他语言中的可辨识联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。 -例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有 UPC-A 格式的一维t条形码,它使用数字 0 到 9。每一个条形码都有一个代表“数字系统”的数字,该数字后接 5 个代表“生产代码”的数字,接下来是5位“产品代码”。最后一个数字是“检查”位,用来验证代码是否被正确扫描: +例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有 UPC-A 格式的一维条形码,它使用数字 0 到 9。每一个条形码都有一个代表“数字系统”的数字,该数字后接 5 个代表“生产代码”的数字,接下来是5位“产品代码”。最后一个数字是“检查”位,用来验证代码是否被正确扫描: -其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO8859-1 字符,并且可以编码一个最多拥有 2,953 字符的字符串: +其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2953 个字符的字符串: @@ -248,11 +248,10 @@ let earthsOrder = Planet.Earth.rawValue let sunsetDirection = CompassPoint.West.rawValue // sunsetDirection 值为 "West" ``` -### 使用原始值来初始化(Initializing from a Raw Value) ### 使用原始值初始化枚举变量(Initializing from a Raw Value) -如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法将原始值类型作为参数,返回枚举成员或者`nil`。你可以使用这种初始化方法来创建一个新的枚举变量。 +如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法将原始值类型作为参数,返回值是枚举成员或`nil`。你可以使用这种初始化方法来创建一个新的枚举变量。 这个例子通过原始值`7`从而创建枚举成员: @@ -261,9 +260,9 @@ let possiblePlanet = Planet(rawValue: 7) // possiblePlanet 类型为 Planet? 值为 Planet.Uranus ``` -然而,并非所有可能的`Int`值都可以找到一个匹配的行星。正因为如此,构造函数可以返回一个*可选*的枚举成员。在上面的例子中,`possiblePlanet`是`Planet?`类型,或“可选的`Planet`”。 +然而,并非所有可能的`Int`值都可以找到一个匹配的行星。正因为如此,构造函数可以返回一个**可选**的枚举成员。在上面的例子中,`possiblePlanet`是`Planet?`类型,或“可选的`Planet`”。 ->注意: +>注意: >原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations#failable_initializers) 如果你试图寻找一个位置为9的行星,通过参数为`rawValue`构造函数返回的可选`Planet`值将是`nil`: @@ -288,9 +287,9 @@ if let somePlanet = Planet(rawValue: positionToFind) { ## 递归枚举(Recursive Enumerations) -在对操作符进行描述的时候,使用枚举类型来对数据建模很方便,因为需要考虑的情况固定可枚举。操作符可以将两个由数字组成的算数表达式连接起来,例如,将`5`连接成复杂一些的表达式`5+4`. +在对操作符进行描述的时候,使用枚举类型来对数据建模很方便,因为需要考虑的情况固定可枚举。操作符可以将两个由数字组成的算数表达式连接起来,例如,将`5`连接成复杂一些的表达式`5+4`。 -算数表达式的一个重要特性是,表达式可以嵌套使用。例如,表达式`(5 + 4) * 2`乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也许要支持这种嵌套————这表示枚举类型需要支持递归。 +算术表达式的一个重要特性是,表达式可以嵌套使用。例如,表达式`(5 + 4) * 2`乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也许要支持这种嵌套————这表示枚举类型需要支持递归。 `递归枚举(recursive enumeration)`是一种枚举类型,表示它的枚举中,有一个或多个枚举成员拥有该枚举的其他成员作为相关值。使用递归枚举时,编译器会插入一个中间层。你可以在枚举成员前加上`indirect`来表示这成员可递归。 @@ -317,7 +316,7 @@ indirect enum ArithmeticExpression { 上面定义的枚举类型可以存储三种算数表达式:纯数字、两个表达式的相加、两个表达式相乘。`Addition` 和 `Multiplication`成员的相关值也是算数表达式————这些相关值使得嵌套表达式成为可能。 -递归函数可以很直观地使用具有递归性质的数据结构。例如,下面是一个计算算数表达式的函数: +递归函数可以很直观地使用具有递归性质的数据结构。例如,下面是一个计算算术表达式的函数: ```swift func evaluate(expression: ArithmeticExpression) -> Int { @@ -340,4 +339,4 @@ print(evaluate(product)) // 输出 "18" ``` -该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法元算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。 +该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法运算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。 diff --git a/source/chapter2/14_Initialization.md b/source/chapter2/14_Initialization.md index 0d99214f..e79a04a9 100755 --- a/source/chapter2/14_Initialization.md +++ b/source/chapter2/14_Initialization.md @@ -20,21 +20,21 @@ - [通过闭包和函数来设置属性的默认值](#setting_a_default_property_value_with_a_closure_or_function) -构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个存储型属性设置初始值和为其执行必要的准备和初始化任务。 +构造过程是使用类、结构体或枚举类型一个实例的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。 -构造过程是通过定义构造器(`Initializers`)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。 +通过定义构造器(`Initializers`)来实现构造过程,这些构造器可以看做是用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。 类的实例也可以通过定义析构器(`deinitializer`)在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。 -## 存储型属性的初始赋值 +## 设置存储型属性的初始值 -类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。 +类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。 -你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下章节将详细介绍这两种方法。 +你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下小节将详细介绍这两种方法。 ->注意: -当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(`property observers`)。 +>注意: +当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者(`property observers`)。 ### 构造器 @@ -67,7 +67,7 @@ print("The default temperature is \(f.temperature)° Fahrenheit") 如前所述,你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。 >注意: -如果一个属性总是使用同一个初始值,可以为其设置一个默认值。无论定义默认值还是在构造器中赋值,最终它们实现的效果是一样的,只不过默认值将属性的初始化和属性的声明结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承(后续章节将讲到)等特性。 +如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性(后续章节将讲到)。 你可以使用更简单的方式在定义结构体`Fahrenheit`时为属性`temperature`设置默认值: @@ -80,17 +80,17 @@ struct Fahrenheit { ## 自定义构造过程 -你可以通过输入参数和可选属性类型来定义构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。 +你可以通过输入参数和可选属性类型来自定义构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。 ### 构造参数 -你可以在定义构造器时提供构造参数,为其提供自定义构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。 +自定义`构造过程`时,可以在定义中提供构造参数,指定所需值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。 下面例子中定义了一个包含摄氏度温度的结构体`Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)`和`init(fromKelvin:)`,二者分别通过接受不同刻度表示的温度值来创建新的实例: ```swift struct Celsius { - var temperatureInCelsius: Double = 0.0 + var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } @@ -199,12 +199,12 @@ cheeseQuestion.response = "Yes, I do like cheese." ### 构造过程中常量属性的修改 -只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。 +你可以在构造过程中的任意时间点修改常量属性的值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。 >注意: -对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。 +对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。 -你可以修改上面的`SurveyQuestion`示例,用常量属性替代变量属性`text`,指明问题内容`text`在其创建之后不会再被修改。尽管`text`属性现在是常量,我们仍然可以在其类的构造器中设置它的值: +你可以修改上面的`SurveyQuestion`示例,用常量属性替代变量属性`text`,表示问题内容`text`在`SurveyQuestion`的实例被创建之后不会再被修改。尽管`text`属性现在是常量,我们仍然可以在其类的构造器中设置它的值: ```swift class SurveyQuestion { @@ -226,7 +226,7 @@ beetsQuestion.response = "I also like beets. (But not with cheese.)" ## 默认构造器 -Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。 +如果结构体和类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体和类创建一个默认构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。 下面例子中创建了一个类`ShoppingListItem`,它封装了购物清单中的某一项的属性:名字(`name`)、数量(`quantity`)和购买状态 `purchase state`。 @@ -635,7 +635,7 @@ let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6) class ShoppingListItem: RecipeIngredient { var purchased = false var description: String { - var output = "\(quantity) x \(name.lowercaseString)" + var output = "\(quantity) x \(name)" output += purchased ? " ✔" : " ✘" return output } @@ -674,7 +674,7 @@ for item in breakfastList { ## 可失败构造器 -如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有必要的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 +如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有用的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在`init`关键字后面加添问号`(init?)`。 @@ -723,11 +723,11 @@ if anonymousCreature == nil { ``` > 注意: -空字符串(`""`)和一个值为`nil`的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们只所以让`Animal`的可失败构造器,构建对象失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。 +空字符串(如`""`,而不是`"Giraffe"`)和一个值为`nil`的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们只所以让`Animal`的可失败构造器,构建对象失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。 ###枚举类型的可失败构造器 -你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足你所期望的条件时,导致构造失败。 +你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足枚举成员期望的条件时,构造失败。 下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(`Kelvin`,`Celsius`,和 `Fahrenheit`)和一个被用来找到`Character`值所对应的枚举成员的可失败构造器: @@ -791,9 +791,11 @@ if unknownUnit == nil { ###类的可失败构造器 -值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前面的例子中,结构体`Animal`的可失败构造器触发失败的行为,甚至发生在`species`属性的值被初始化以前。而对类而言,就没有那么幸运了。类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。 +值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前面的例子中,结构体`Animal`的可失败构造器触发失败的行为,甚至发生在`species`属性的值被初始化以前。 -下例子中,定义了一个名为`Product`的类,其内部结构和结构体`Animal`很相似,内部也有一个名为`name`的`String`类型的属性。由于该属性的值同样不能为空字符串,所以我们加入了可失败构造器来确保该类满足上述条件。但由于`Product`类不是一个结构体,所以当想要在该类中添加可失败构造器触发失败条件时,必须确保`name`属性被初始化。因此我们把`name`属性的`String`类型做了一点点小小的修改,把其改为隐式解析可选类型(`String!`),来确保可失败构造器触发失败条件时,所有类属性都被初始化了。因为所有可选类型都有一个默认的初始值`nil`。因此最后`Product`类可写为: +而对类而言,就没有那么幸运了。类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。 + +下面例子展示了如何使用隐式解析可选类型来实现这个类的可失败构造器的要求: ```swift class Product { @@ -804,8 +806,13 @@ class Product { } } ``` +上面定义的`Product`类,其内部结构和之前`Animal`结构体很相似。`Product`类有一个不能为空字符串的`name`常量属性。为了强制满足这个要求,`Product`类使用了可失败构造器来确保这个属性的值在构造器成功时不为空。 -因为`name`属性是一个常量,所以一旦`Product`类构造成功,`name`属性肯定有一个非`nil`的值。因此完全可以放心大胆的直接访问`Product`类的`name`属性,而不用考虑去检查`name`属性是否有值。 +毕竟,`Product`是一个类而不是结构体,也就不能和`Animal`一样了。`Product`类的所有可失败构造器必须在自己失败前给`name`属性一个初始值。 + +上面的例子中,`Product`类的`name`属性被定义为隐式解析可选字符串类型(`String!`)。因为它是一个可选类型,所以在构造过程里的赋值前,`name`属性有个默认值`nil`。用默认值`nil`意味着`Product`类的所有属性都有一个合法的初始值。因而,在构造器中给`name`属性赋一个特定的值前,可失败构造器能够在传入一个空字符串时触发构造过程的失败。 + +因为`name`属性是一个常量,所以一旦`Product`类构造成功,`name`属性肯定有一个非`nil`的值。即使它被定义为隐式解析可选类型,也完全可以放心大胆地直接访问,而不用考虑`name`属性是否有值。 ```swift if let bowTie = Product(name: "bow tie") { @@ -897,7 +904,7 @@ class Document { } ``` -下面这个例子,定义了一个名为`AutomaticallyNamedDocument`的`Document`类的子类。这个子类重写了基类的两个指定构造器。确保了不论在何种情况下`name`属性总是有一个非空字符串`[Untitled]`的值。 +下面这个例子,定义了一个`Document`类的子类`AutomaticallyNamedDocument`。这个子类重写了父类的两个指定构造器,确保不论是通过没有 name 参数的构造器,还是通过传一个空字符串给`init(name:)`构造器,生成的实例中的`name`属性总有初始值`"[Untitled]"`。 ```swift class AutomaticallyNamedDocument: Document { @@ -916,15 +923,26 @@ class AutomaticallyNamedDocument: Document { } ``` -`AutomaticallyNamedDocument`用一个非可失败构造器`init(name:)`,重写了基类的可失败构造器`init?(name:)`。因为子类用不同的方法处理了`name`属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失败的构造器。 +`AutomaticallyNamedDocument`用一个非可失败构造器`init(name:)`,重写了父类的可失败构造器`init?(name:)`。因为子类用不同的方法处理了`name`属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失败的构造器,用一个非可失败版本代替了父类的版本。 + +你可以在构造器中调用父类的可失败构造器强制解包,以实现子类的非可失败构造器。比如,下面的`UntitledDocument `子类总有值为`"[Untitled]"`的 name 属性,它在构造过程中用了父类的可失败的构造器`init(name:)`。 + +```swift +class UntitledDocument: Document { + override init() { + super.init(name: "[Untitled]")! + } +} +``` +在这个例子中,如果在调用父类的构造器`init(name:)`时传给 name 的是空字符串,那么强制解绑操作会造成运行时错误。不过,因为这里是通过字符串常量来调用它,所以并不会发生运行时错误。 ###可失败构造器 init! -通常来说我们通过在`init`关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在`init`后面添加惊叹号的方式来定义一个可失败构造器`(init!)`,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。 +通常来说我们通过在`init`关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以使用通过在`init`后面添加惊叹号的方式来定义一个可失败构造器`(init!)`,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。 你可以在 `init? `构造器中代理调用 `init!`构造器,反之亦然。 你也可以用 `init?`重写 `init!`,反之亦然。 -你还可以用 `init`代理调用`init!`,但这会触发一个断言:是否 `init!` 构造器会触发构造失败? +你还可以用 `init`代理调用`init!`,但这会触发一个断言: `init!` 构造器是否会触发构造失败? ##必要构造器 @@ -938,7 +956,7 @@ class SomeClass { } } ``` -当子类重写基类的必要构造器时,必须在子类的构造器前同样添加`required`修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加`override`修饰符: +在子类重写父类的必要构造器时,必须在子类的构造器前也添加`required`修饰符,这是为了保证继承链上子类的构造器也是必要构造器。在重写父类的必要构造器时,不需要添加`override`修饰符: ```swift class SomeSubclass: SomeClass {