This commit is contained in:
梁杰
2015-07-10 23:04:56 +08:00
parent b7f61209fa
commit bea57c8ffb
2 changed files with 1117 additions and 1050 deletions

View File

@ -1,5 +1,5 @@
> 翻译:[lifedim](https://github.com/lifedim) > 翻译:[lifedim](https://github.com/lifedim)
> 校对:[lifedim](https://github.com/lifedim) > 校对:[lifedim](https://github.com/lifedim)[chenmingbiao](https://github.com/chenmingbiao)
# 构造过程Initialization # 构造过程Initialization
@ -8,7 +8,7 @@
本页包含内容: 本页包含内容:
- [存储型属性的初始赋值](#setting_initial_values_for_stored_properties) - [存储型属性的初始赋值](#setting_initial_values_for_stored_properties)
- [定制化构造过程](#customizing_initialization) - [自定义构造过程](#customizing_initialization)
- [默认构造器](#default_initializers) - [默认构造器](#default_initializers)
- [值类型的构造器代理](#initializer_delegation_for_value_types) - [值类型的构造器代理](#initializer_delegation_for_value_types)
- [类的继承和构造过程](#class_inheritance_and_initialization) - [类的继承和构造过程](#class_inheritance_and_initialization)
@ -17,11 +17,11 @@
- [通过闭包和函数来设置属性的默认值](#setting_a_default_property_value_with_a_closure_or_function) - [通过闭包和函数来设置属性的默认值](#setting_a_default_property_value_with_a_closure_or_function)
构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。 构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个存储型属性设置初始值和为其执行必要的准备和初始化任务。
构造过程是通过定义构造器(`Initializers`)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。 构造过程是通过定义构造器(`Initializers`)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类实例也可以通过定义析构器(`deinitializer`)在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](../chapter2/15_Deinitialization.html)。 实例也可以通过定义析构器(`deinitializer`)在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](15_Deinitialization.html)。
<a name="setting_initial_values_for_stored_properties"></a> <a name="setting_initial_values_for_stored_properties"></a>
## 存储型属性的初始赋值 ## 存储型属性的初始赋值
@ -37,6 +37,12 @@
构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字`init`命名。 构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字`init`命名。
```swift
init() {
// 在此处执行构造过程
}
```
下面例子中定义了一个用来保存华氏温度的结构体`Fahrenheit`,它拥有一个`Double`类型的存储型属性`temperature` 下面例子中定义了一个用来保存华氏温度的结构体`Fahrenheit`,它拥有一个`Double`类型的存储型属性`temperature`
```swift ```swift
@ -46,9 +52,6 @@ struct Fahrenheit {
temperature = 32.0 temperature = 32.0
} }
} }
```
```swift
var f = Fahrenheit() var f = Fahrenheit()
println("The default temperature is \(f.temperature)° Fahrenheit") println("The default temperature is \(f.temperature)° Fahrenheit")
// 输出 "The default temperature is 32.0° Fahrenheit” // 输出 "The default temperature is 32.0° Fahrenheit”
@ -58,7 +61,7 @@ println("The default temperature is \(f.temperature)° Fahrenheit")
### 默认属性值 ### 默认属性值
如前所述,你可以在构造器中为存储型属性设置初始值同样,你也可以在属性声明时为其设置默认值。 如前所述,你可以在构造器中为存储型属性设置初始值同样,你也可以在属性声明时为其设置默认值。
>注意: >注意:
如果一个属性总是使用同一个初始值,可以为其设置一个默认值。无论定义默认值还是在构造器中赋值,最终它们实现的效果是一样的,只不过默认值将属性的初始化和属性的声明结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承(后续章节将讲到)等特性。 如果一个属性总是使用同一个初始值,可以为其设置一个默认值。无论定义默认值还是在构造器中赋值,最终它们实现的效果是一样的,只不过默认值将属性的初始化和属性的声明结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承(后续章节将讲到)等特性。
@ -72,13 +75,13 @@ struct Fahrenheit {
``` ```
<a name="customizing_initialization"></a> <a name="customizing_initialization"></a>
## 定制化构造过程 ## 自定义构造过程
你可以通过输入参数和可选属性类型来定构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。 你可以通过输入参数和可选属性类型来定构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。
### 构造参数 ### 构造参数
你可以在定义构造器时提供构造参数,为其提供定制化构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。 你可以在定义构造器时提供构造参数,为其提供自定义构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。
下面例子中定义了一个包含摄氏度温度的结构体`Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)``init(fromKelvin:)`,二者分别通过接受不同刻度表示的温度值来创建新的实例: 下面例子中定义了一个包含摄氏度温度的结构体`Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)``init(fromKelvin:)`,二者分别通过接受不同刻度表示的温度值来创建新的实例:
@ -92,9 +95,6 @@ struct Celsius {
temperatureInCelsius = kelvin - 273.15 temperatureInCelsius = kelvin - 273.15
} }
} }
```
```swift
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0) let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0 // boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15) let freezingPointOfWater = Celsius(fromKelvin: 273.15)
@ -103,18 +103,15 @@ let freezingPointOfWater = Celsius(fromKelvin: 273.15)
第一个构造器拥有一个构造参数,其外部名字为`fromFahrenheit`,内部名字为`fahrenheit`;第二个构造器也拥有一个构造参数,其外部名字为`fromKelvin`,内部名字为`kelvin`。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性`temperatureInCelsius`中。 第一个构造器拥有一个构造参数,其外部名字为`fromFahrenheit`,内部名字为`fahrenheit`;第二个构造器也拥有一个构造参数,其外部名字为`fromKelvin`,内部名字为`kelvin`。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性`temperatureInCelsius`中。
### 内部和外部参数名 ### 参数的内部名称和外部名称
跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。 跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
然而构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时主要通过构造器中的参数名和类型来确定需要调用的构造器。正因为参数如此重要如果你在定义构造器时没有提供参数的外部名字Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每个构造参数之前加了一个哈希符号。 然而构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时主要通过构造器中的参数名和类型来确定需要调用的构造器。正因为参数如此重要如果你在定义构造器时没有提供参数的外部名字Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每个构造参数之前加了一个哈希符号。
> 注意:
如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线`_`来显示描述它的外部名,以此覆盖上面所说的默认行为。
以下例子中定义了一个结构体`Color`,它包含了三个常量:`red``green``blue`。这些属性可以存储0.0到1.0之间的值,用来指示颜色中红、绿、蓝成分的含量。 以下例子中定义了一个结构体`Color`,它包含了三个常量:`red``green``blue`。这些属性可以存储0.0到1.0之间的值,用来指示颜色中红、绿、蓝成分的含量。
`Color`提供了一个构造器,其中包含三个`Double`类型的构造参数 `Color`提供了一个构造器,其中包含三个`Double`类型的构造参数`Color`也可以提供第二个构造器,它只包含`Double`类型名叫`white`的参数,它被用于给上述三个构造参数赋予同样的值。
```swift ```swift
struct Color { struct Color {
@ -132,7 +129,7 @@ struct Color {
} }
``` ```
每当你创建一个新的`Color`实例,你需要通过三种颜色的外部参数名来传值,并调用构造器 两种构造器都能用于创建一个新的`Color`实例,你需要为构造器每个外部参数传值
```swift ```swift
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0) let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
@ -146,6 +143,31 @@ let veryGreen = Color(0.0, 1.0, 0.0)
// 报编译时错误,需要外部名称 // 报编译时错误,需要外部名称
``` ```
### 不带外部名的构造器参数
如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(_)来显示描述它的外部名,以此覆盖上面所说的默认行为。
下面是之前`Celsius`例子的扩展,跟之前相比添加了一个带有`Double`类型参数名为`celsius`的构造器,其外部名用`_`代替。
```swift
struct Celsius {I
var temperatureInCelsius: Double = 0.0
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
init(_ celsius: Double){
temperatureInCelsius = celsius
}
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0
```
调用这种不需要外部参数名称的`Celsius(37.0)`构造器看起来十分简明的。因此适当使用这种`init(_ celsius: Double)`构造器可以提供`Double`类型的参数值而不需要加上外部名。
### 可选属性类型 ### 可选属性类型
如果你定制的类型包含一个逻辑上允许取值为空的存储型属性--不管是因为它无法在初始化时赋值,还是因为它可以在之后某个时间点可以赋值为空--你都需要将它定义为可选类型`optional type`。可选类型的属性将自动初始化为空`nil`,表示这个属性是故意在初始化时设置为空的。 如果你定制的类型包含一个逻辑上允许取值为空的存储型属性--不管是因为它无法在初始化时赋值,还是因为它可以在之后某个时间点可以赋值为空--你都需要将它定义为可选类型`optional type`。可选类型的属性将自动初始化为空`nil`,表示这个属性是故意在初始化时设置为空的。
@ -237,14 +259,14 @@ let twoByTwo = Size(width: 2.0, height: 2.0)
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。 构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。类则不同,它可以继承自其它类(请参考[继承](../chapter2/13_Inheritance.html)),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。 构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。类则不同,它可以继承自其它类(请参考[继承](13_Inheritance.html)),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
对于值类型,你可以使用`self.init`在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用`self.init` 对于值类型,你可以使用`self.init`在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用`self.init`
注意,如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。 如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。
>注意: >注意:
假如你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(`extension`)中,而不是跟值类型定义混在一起。想查看更多内容,请查看[扩展](../chapter2/20_Extensions.html)章节。 假如你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(`extension`)中,而不是跟值类型定义混在一起。想查看更多内容,请查看[扩展](20_Extensions.html)章节。
下面例子将定义一个结构体`Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体`Size``Point`,它们各自为其所有的属性提供了初始值`0.0` 下面例子将定义一个结构体`Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体`Size``Point`,它们各自为其所有的属性提供了初始值`0.0`
@ -302,7 +324,7 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
构造器`init(center:size:)`可以自己将`origin``size`的新值赋值到对应的属性中。然而尽量利用现有的构造器和它所提供的功能来实现`init(center:size:)`的功能,是更方便、更清晰和更直观的方法。 构造器`init(center:size:)`可以自己将`origin``size`的新值赋值到对应的属性中。然而尽量利用现有的构造器和它所提供的功能来实现`init(center:size:)`的功能,是更方便、更清晰和更直观的方法。
>注意: >注意:
如果你想用另外一种不需要自己定义`init()``init(origin:size:)`的方式来实现这个例子,请参考[扩展](../chapter2/20_Extensions.html)。 如果你想用另外一种不需要自己定义`init()``init(origin:size:)`的方式来实现这个例子,请参考[扩展](20_Extensions.html)。
<a name="class_inheritance_and_initialization"></a> <a name="class_inheritance_and_initialization"></a>
## 类的继承和构造过程 ## 类的继承和构造过程
@ -321,6 +343,24 @@ Swift 提供了两种类型的类构造器来确保所有类实例中存储型
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。 你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
### 指定构造器和便利构造器的语法
类的指定构造器的写法跟值类型简单构造器一样:
```swift
init(parameters) {
statements
}
```
便利构造器也采用相同样式的写法,但需要在`init`关键字之前放置`convenience`关键字,并使用空格将它们俩分开:
```swift
convenience init(parameters) {
statements
}
```
<a name="initialization_chain"></a> <a name="initialization_chain"></a>
### 构造器链 ### 构造器链
@ -404,7 +444,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
- 最终,任意构造器链中的便利构造器可以有机会定制实例和使用`self` - 最终,任意构造器链中的便利构造器可以有机会定制实例和使用`self`
下图展示了在假定的子类和父类之间构造的阶段1 下图展示了在假定的子类和父类之间构造的阶段1
·
![构造过程阶段1](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/twoPhaseInitialization01_2x.png) ![构造过程阶段1](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/twoPhaseInitialization01_2x.png)
在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。 在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。
@ -429,14 +469,64 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
跟 Objective-C 中的子类不同Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。 跟 Objective-C 中的子类不同Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。
假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器--也许是为了完成一些定制的构造过程--你可以在你定制的子类中提供和重载与父类相同的构造器。 >注意:
父类的构造器仅在确定和安全的情况下被继承。具体内容请参考后续章节[自动构造器的继承](#automatic_initializer_inheritance)。
如果你重载的构造器是一个指定构造器,你可以在子类里重载它的实现,并在自定义版本的构造器中调用父类版本的构造器。 假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器,也许是为了完成一些定制的构造过程,你可以在你定制的子类中提供和重载与父类相同的构造器。
如果你重载的构造器是一个便利构造器,你的重载过程必须通过调用同一类中提供的其它指定构造器来实现。这一规则的详细内容请参考[构造器](#initialization_chain)。 当你写一个父类中带有指定构造器的子类构造器,你需要重载这个指定的构造器。因此,你必须在定义子类构造器时带上`override`修饰符。即使你重载系统提供的默认构造器也需要带上`override`修饰符,具体内容请参考[默认构造器](#default_initializers)。
无论是重载属性,方法或者是下标脚本,只要含有`override`修饰符就会去检查父类是否有相匹配的重载指定构造器和验证重载构造器参数。
>注意: >注意:
与方法、属性和下标不同,在重载构造器时你没有必要使用关键字`override` 当你重载一个父类指定构造器时,你需要写`override`修饰符,甚至你的子类构造器继承的是父类的便利构造器
相反地,如果你写了一个和父类便利构造器相匹配的子类构造器,子类都不能直接调用父类的便利构造器,每个规则都在上文[构造器链](#initialization_chain)有所描述。
在下面的例子中定义了一个基础类叫`Vehicle`。基础类中声明了一个存储型属性`numberOfWheels`,它是值为`0``Int`类型属性。`numberOfWheels`属性用于创建名为`descrpiption`类型为`String`的计算型属性。
```swift
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
```
`Vehicle`类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动生成一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器通常在类中是指定构造器,它可以用于创建属性叫`numberOfWheels`值为`0``Vehicle`实例。
```swift
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
```
下面例子中定义了一个`Vehicle`的子类`Bicycle`
```swift
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
```
子类`Bicycle`定义了一个自定义指定构造器`init()`。这个指定构造器和父类的指定构造器相匹配,所以`Bicycle`中的指定构造器需要带上`override`修饰符。
`Bicycle`的构造器`init()`一开始调用`super.init()`方法,这个方法的作用是调用`Bicycle`的父类`Vehicle`。这样可以确保`Bicycle`在修改属性之前它所继承的属性`numberOfWheels`能被`Vehicle`类初始化。在调用`super.init()`之后,原本的属性`numberOfWheels`被赋值为`2`
如果你创建一个`Bicycle`实例,你可以调用继承的`description`计算型属性去查看属性`numberOfWheels`是否有改变。
```swift
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
```
>注意
子类可以在初始化时修改继承变量属性,但是不能修改继承过来的常量属性。
<a name="automatic_initializer_inheritance"></a> <a name="automatic_initializer_inheritance"></a>
### 自动构造器的继承 ### 自动构造器的继承
@ -458,27 +548,9 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
>注意: >注意:
子类可以通过部分满足规则2的方式使用子类便利构造器来实现父类的指定构造器。 子类可以通过部分满足规则2的方式使用子类便利构造器来实现父类的指定构造器。
### 指定构造器和便利构造器的语法 ### 指定构造器和便利构造器操作
类的指定构造器的写法跟值类型简单构造器一样: 接下来的例子将在操作中展示指定构造器、便利构造器和自动构造器的继承。它定义了包含三个类`Food``RecipeIngredient`以及`ShoppingListItem`的类层次结构,并将演示它们的构造器是如何相互作用的。
```swift
init(parameters) {
statements
}
```
便利构造器也采用相同样式的写法,但需要在`init`关键字之前放置`convenience`关键字,并使用空格将它们俩分开:
```swift
convenience init(parameters) {
statements
}
```
### 指定构造器和便利构造器实战
接下来的例子将在实战中展示指定构造器、便利构造器和自动构造器的继承。它定义了包含三个类`Food``RecipeIngredient`以及`ShoppingListItem`的类层次结构,并将演示它们的构造器是如何相互作用的。
类层次中的基类是`Food`,它是一个简单的用来封装食物名字的类。`Food`类引入了一个叫做`name``String`类型属性,并且提供了两个构造器来创建`Food`实例: 类层次中的基类是`Food`,它是一个简单的用来封装食物名字的类。`Food`类引入了一个叫做`name``String`类型属性,并且提供了两个构造器来创建`Food`实例:
@ -601,14 +673,12 @@ for item in breakfastList {
为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在`init`关键字后面加添问号`(init?)` 为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在`init`关键字后面加添问号`(init?)`
> 注意: > 注意:
> 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其类型相同。
> 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其类型相同。
可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过`return nil` 语句,来表明可失败构造器在何种情况下“失败”。 可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过`return nil` 语句,来表明可失败构造器在何种情况下“失败”。
> 注意: > 注意:
> 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了能确保对象自身能被正确构建。所以即使你在表明可失败构造器,失败的这种情况下,用到了`return nil`。也不要在表明可失败构造器成功的这种情况下,使用关键字 `return`
> 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了能确保对象自身能被正确构建。所以即使你在表明可失败构造器,失败的这种情况下,用到了`return nil`。也不要在表明可失败构造器成功的这种情况下,使用关键字 `return`。
下例中,定义了一个名为`Animal`的结构体,其中有一个名为`species`的,`String`类型的常量属性。同时该结构体还定义了一个,带一个`String`类型参数`species`的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。 下例中,定义了一个名为`Animal`的结构体,其中有一个名为`species`的,`String`类型的常量属性。同时该结构体还定义了一个,带一个`String`类型参数`species`的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。
@ -647,8 +717,7 @@ if anonymousCreature == nil {
``` ```
> 注意: > 注意:
> 空字符串(```""```)和一个值为```nil```的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们只所以让`Animal`的可失败构造器,构建对象失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。
> 空字符串(```""```)和一个值为```nil```的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们只所以让`Animal`的可失败构造器,构建对象失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。
###枚举类型的可失败构造器 ###枚举类型的可失败构造器
@ -688,7 +757,6 @@ if unknownUnit == nil {
println("This is not a defined temperature unit, so initialization failed.") println("This is not a defined temperature unit, so initialization failed.")
} }
// 打印 "This is not a defined temperature unit, so initialization failed." // 打印 "This is not a defined temperature unit, so initialization failed."
``` ```
###带原始值的枚举类型的可失败构造器 ###带原始值的枚举类型的可失败构造器
@ -713,7 +781,6 @@ if unknownUnit == nil {
println("This is not a defined temperature unit, so initialization failed.") println("This is not a defined temperature unit, so initialization failed.")
} }
// prints "This is not a defined temperature unit, so initialization failed." // prints "This is not a defined temperature unit, so initialization failed."
``` ```
###类的可失败构造器 ###类的可失败构造器
@ -749,8 +816,7 @@ if let bowTie = Product(name: "bow tie") {
无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。 无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。
>注意: >注意:
> 可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构造失败的条件。
>可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构造失败的条件。
下面这个例子,定义了一个名为```CartItem```的```Product```类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为```quantity```的常量参数用来表示该物品的数量至少为1 下面这个例子,定义了一个名为```CartItem```的```Product```类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为```quantity```的常量参数用来表示该物品的数量至少为1
@ -764,6 +830,7 @@ class CartItem: Product {
} }
} }
``` ```
和```Product```类中的```name```属性相类似的,```CartItem```类中的```quantity```属性的类型也是一个隐式解析可选类型,只不过由(```String```)变为了(```Int!```。这样做都是为了确保在构造过程中该属性在被赋予特定的值之前能有一个默认的初始值nil。 和```Product```类中的```name```属性相类似的,```CartItem```类中的```quantity```属性的类型也是一个隐式解析可选类型,只不过由(```String```)变为了(```Int!```。这样做都是为了确保在构造过程中该属性在被赋予特定的值之前能有一个默认的初始值nil。
可失败构造器总是先向上代理调用基类,```Product```的构造器 ```init(name:)```。这满足了可失败构造器在触发构造失败这个行为前必须总是执行构造代理调用这个条件。 可失败构造器总是先向上代理调用基类,```Product```的构造器 ```init(name:)```。这满足了可失败构造器在触发构造失败这个行为前必须总是执行构造代理调用这个条件。
@ -800,15 +867,14 @@ if let oneUnnamed = CartItem(name: "", quantity: 1) {
// 打印 "Unable to initialize one unnamed product" // 打印 "Unable to initialize one unnamed product"
``` ```
###覆盖一个可失败构造器 ###重写一个可失败构造器
就如同其它构造器一样,你也可以用子类的可失败构造器覆盖基类的可失败构造器。或者你也可以用子类的非可失败构造器覆盖一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。 就如同其它构造器一样,你也可以用子类的可失败构造器重写基类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。
注意当你用一个子类的非可失败构造器覆盖了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败构造器。 注意当你用一个子类的非可失败构造器重写了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败构造器。
>注意: >注意:
> 你可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。
>你可以用一个非可失败构造器覆盖一个可失败构造器,但反过来却行不通。
下例定义了一个名为```Document```的类,这个类中的```name```属性允许为```nil```和一个非空字符串,但不能是一个空字符串: 下例定义了一个名为```Document```的类,这个类中的```name```属性允许为```nil```和一个非空字符串,但不能是一个空字符串:
@ -825,7 +891,7 @@ class Document {
} }
``` ```
下面这个例子,定义了一个名为```AutomaticallyNamedDocument```的```Document```类的子类。这个子类覆盖了基类的两个指定构造器。确保了不论在何种情况下```name```属性总是有一个非空字符串```[Untitled]```的值。 下面这个例子,定义了一个名为```AutomaticallyNamedDocument```的```Document```类的子类。这个子类重写了基类的两个指定构造器。确保了不论在何种情况下```name```属性总是有一个非空字符串```[Untitled]```的值。
```swift ```swift
class AutomaticallyNamedDocument: Document { class AutomaticallyNamedDocument: Document {
@ -843,14 +909,15 @@ class AutomaticallyNamedDocument: Document {
} }
} }
``` ```
```AutomaticallyNamedDocument```用一个非可失败构造器```init(name:)```,覆盖了基类的可失败构造器```init?(name:)```。因为子类用不同的方法处理了```name```属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失败的构造器。
```AutomaticallyNamedDocument```用一个非可失败构造器```init(name:)```,重写了基类的可失败构造器```init?(name:)```。因为子类用不同的方法处理了```name```属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失败的构造器。
###可失败构造器 init! ###可失败构造器 init!
通常来说我们通过在```init```关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在```init```后面添加惊叹号的方式来定义一个可失败构造器```init!```,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。 通常来说我们通过在```init```关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在```init```后面添加惊叹号的方式来定义一个可失败构造器```init!```,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。
你可以在 ```init```构造器中代理调用 ```init```构造器,反之亦然。 你可以在 ```init```构造器中代理调用 ```init```构造器,反之亦然。
你也可以用 ```init```覆盖 ```init```,反之亦然。 你也可以用 ```init```重写 ```init```,反之亦然。
你还可以用 ```init```代理调用```init```,但这会触发一个断言:是否 ```init```构造器会触发构造失败? 你还可以用 ```init```代理调用```init```,但这会触发一个断言:是否 ```init```构造器会触发构造失败?
<a name="required_initializers"></a> <a name="required_initializers"></a>
@ -865,7 +932,7 @@ class SomeClass {
} }
} }
``` ```
当子类覆盖基类的必要构造器时,必须在子类的构造器前同样添加```required```修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在覆盖基类的必要构造器时,不需要添加```override```修饰符: 当子类重写基类的必要构造器时,必须在子类的构造器前同样添加```required```修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加```override```修饰符:
```swift ```swift
class SomeSubclass: SomeClass { class SomeSubclass: SomeClass {
@ -876,8 +943,7 @@ class SomeSubclass: SomeClass {
``` ```
>注意: >注意:
> 如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。
>如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。
<a name="setting_a_default_property_value_with_a_closure_or_function"></a> <a name="setting_a_default_property_value_with_a_closure_or_function"></a>
## 通过闭包和函数来设置属性的默认值 ## 通过闭包和函数来设置属性的默认值
@ -940,3 +1006,4 @@ println(board.squareIsBlackAtRow(0, column: 1))
println(board.squareIsBlackAtRow(9, column: 9)) println(board.squareIsBlackAtRow(9, column: 9))
// 输出 "false" // 输出 "false"
``` ```

View File

@ -1,5 +1,5 @@
> 翻译:[bruce0505](https://github.com/bruce0505) > 翻译:[bruce0505](https://github.com/bruce0505)
> 校对:[fd5788](https://github.com/fd5788) > 校对:[fd5788](https://github.com/fd5788)[chenmingbiao](https://github.com/chenmingbiao)
# 析构过程Deinitialization # 析构过程Deinitialization
--------------------------- ---------------------------
@ -7,16 +7,16 @@
本页包含内容: 本页包含内容:
- [析构过程原理](#how_deinitialization_works) - [析构过程原理](#how_deinitialization_works)
- [析构函数操作](#deinitializers_in_action) - [析构操作](#deinitializers_in_action)
一个类的实例被释放之前,析构函数被立即调用。用关键字`deinit`来标示析构函数,类似于初始化函数`init`来标示。析构函数只适用于类类型。 析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字`deinit`来标示,类似于构造器要`init`来标示。
<a name="how_deinitialization_works"></a> <a name="how_deinitialization_works"></a>
##析构过程原理 ##析构过程原理
Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](16_Automatic_Reference_Counting.html)那一章描Swift 通过_自动引用计数_ARC处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是当使用自己的资源时你可能需要进行一些额外的清理。例如如果创建了一个自定义的类来打开一个文件并写入一些数据你可能需要在类实例被释放之前关闭该文件。 Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](16_Automatic_Reference_Counting.html)章节中所讲Swift 通过`自动引用计数ARC`处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号 在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数,如下所示
```swift ```swift
deinit { deinit {
@ -24,14 +24,14 @@ deinit {
} }
``` ```
析构函数是在实例释放发生前一步被自动调用。不允许主动调用自己的析构函数。子类继承了父类的析构函数,并且在子类析构函数实现的最后,父类的析构函数被自动调用。即使子类没有提供自己的析构函数,父类的析构函数也总是被调用。 析构是在实例释放发生前被自动调用。析构器是不允许主动调用。子类继承了父类的析构,并且在子类析构实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构,父类的析构器也同样会被调用。
因为直到实例的析构函数被调用时,实例才会被释放,所以析构函数可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件的名称)。 因为直到实例的析构被调用时,实例才会被释放,所以析构可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
<a name="deinitializers_in_action"></a> <a name="deinitializers_in_action"></a>
##析构函数操作 ##析构操作
是一个析构函数操作的例子。这个例子一个简单的游戏,定义了两种新类型,`Bank``Player``Bank`结构体管理一个虚拟货币的流通,在这个流通中`Bank`永远不可能拥有超过 10,000 的硬币。在这个游戏中有且只能有一个`Bank`存在,因此`Bank`带有静态属性和静态方法的结构体实现,从而存储和管理其当前的状态。 这是一个析构操作的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是`Bank``Player``Bank`结构体管理一个虚拟货币的流通,在这个流通中我们设定`Bank`永远不可能拥有超过 10,000 的硬币,而且在游戏中有且只能有一个`Bank`存在,因此`Bank`结构体在实现时会带有静态属性和静态方法存储和管理其当前的状态。
```swift ```swift
struct Bank { struct Bank {
@ -47,11 +47,11 @@ struct Bank {
} }
``` ```
`Bank`根据它的`coinsInBank`属性来跟踪当前它拥有的硬币数量。银行还提供两个方法——`vendCoins``receiveCoins`——用来处理硬币的分发和收集。 `Bank`根据它的`coinsInBank`属性来跟踪当前它拥有的硬币数量。`Bank`还提供两个方法——`vendCoins(_:)``receiveCoins(_:)`,分别用来处理硬币的分发和收集。
`vendCoins`方法在 bank 分发硬币之前检查是否有足够的硬币。如果没有足够多的硬币,`Bank`返回一个比请求时小的数字(如果没有硬币留在 bank 中就返回 0)。`vendCoins`方法声明`numberOfCoinsToVend`为一个变量参数,这样就可以在方法体的内部修改数字,而不需要定义一个新的变量。`vendCoins`方法返回一个整型值,表明了提供的硬币的实际数目。 `vendCoins(_:)`方法在bank对象分发硬币之前检查是否有足够的硬币。如果没有足够多的硬币,`Bank`返回一个比请求时小的数字(如果没有硬币留在bank对象中就返回 0)。`vendCoins`方法声明`numberOfCoinsToVend`为一个变量参数,这样就可以在方法体的内部修改数字,而不需要定义一个新的变量。`vendCoins`方法返回一个整型值,表明了提供的硬币的实际数目。
`receiveCoins`方法只是将 bank 的硬币存储和接收到的硬币数目相加,再保存回 bank。 `receiveCoins`方法只是将bank对象的硬币存储和接收到的硬币数目相加再保存回bank对象
`Player`类描述了游戏中的一个玩家。每一个 player 在任何时刻都有一定数量的硬币存储在他们的钱包中。这通过 player 的`coinsInPurse`属性来体现: `Player`类描述了游戏中的一个玩家。每一个 player 在任何时刻都有一定数量的硬币存储在他们的钱包中。这通过 player 的`coinsInPurse`属性来体现:
@ -71,9 +71,9 @@ class Player {
``` ```
每个`Player`实例都由一个指定数目硬币组成的启动额度初始化,这些硬币在 bank 初始化的过程中得到。如果没有足够的硬币可用,`Player`实例可能收到比指定数目少的硬币。 每个`Player`实例构造时都会设定由硬币组成的启动额度这些硬币在bank对象初始化的过程中得到。如果在bank对象中没有足够的硬币可用,`Player`实例可能收到比指定数目少的硬币。
`Player`类定义了一个`winCoins`方法,该方法从银行获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构函数,这个析构函数`Player`实例释放前一步被调用。这里析构函数只是将玩家的所有硬币都返回给银行 `Player`类定义了一个`winCoins(_:)`方法,该方法从bank对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构,这个析构`Player`实例释放前被调用。这里析构器的作用只是将玩家的所有硬币都返回给bank对象
```swift ```swift
var playerOne: Player? = Player(coins: 100) var playerOne: Player? = Player(coins: 100)
@ -83,9 +83,9 @@ println("There are now \(Bank.coinsInBank) coins left in the bank")
// 输出 "There are now 9900 coins left in the bank" // 输出 "There are now 9900 coins left in the bank"
``` ```
一个新的`Player`实例随着一个 100 个硬币(如果有)的请求而被创建。这`个Player`实例存储在一个名为`playerOne`的可选`Player`变量中。这里使用一个可选变量,是因为玩家可以随时离开游戏。设置为可选使得你可以跟踪当前是否有玩家在游戏中。 一个新的`Player`实例被创建时会设定有 100 个硬币(如果bank对象中硬币的数目足够。这`个Player`实例存储在一个名为`playerOne`的可选`Player`变量中。这里使用一个可选变量,是因为玩家可以随时离开游戏。设置为可选使得你可以跟踪当前是否有玩家在游戏中。
因为`playerOne`是可选的,所以一个感叹号(`!`修饰,每当其`winCoins`方法被调用时,`coinsInPurse`属性被访问并打印出它的默认硬币数目。 因为`playerOne`是可选的,所以一个感叹号(`!`作为修饰,每当其`winCoins(_:)`方法被调用时,`coinsInPurse`属性就会被访问并打印出它的默认硬币数目。
```swift ```swift
playerOne!.winCoins(2_000) playerOne!.winCoins(2_000)
@ -95,7 +95,7 @@ println("The bank now only has \(Bank.coinsInBank) coins left")
// 输出 "The bank now only has 7900 coins left" // 输出 "The bank now only has 7900 coins left"
``` ```
这里player 已经赢得了 2,000 硬币player 的钱包现在有 2,100 硬币bank 只剩余 7,900 硬币。 这里player 已经赢得了 2,000 硬币,所以player 的钱包现在有 2,100 硬币,bank对象只剩余 7,900 硬币。
```swift ```swift
playerOne = nil playerOne = nil
@ -105,4 +105,4 @@ println("The bank now has \(Bank.coinsInBank) coins")
// 输出 "The bank now has 10000 coins" // 输出 "The bank now has 10000 coins"
``` ```
玩家现在已经离开了游戏。这表明是要将可选的`playerOne`变量设置为`nil`,意思是“没有`Player`实例”。当这种情况发生的时候,`playerOne`变量对`Player`实例的引用被破坏了。没有其它属性或者变量引用`Player`实例,因此为了清空它占用的内存从而释放它。在这发生前一步,其析构函数被自动调用,其硬币被返回到银行 玩家现在已经离开了游戏。这表明是要将可选的`playerOne`变量设置为`nil`,意思是“不存在`Player`实例”。当这种情况发生的时候,`playerOne`变量对`Player`实例的引用被破坏了。没有其它属性或者变量引用`Player`实例,因此为了清空它占用的内存从而释放它。在这发生前,其析构器会被自动调用,从而使其硬币被返回到bank对象中