Merge pull request #755 from muhlenXi/develop
Finish initialization chapter translate
This commit is contained in:
20
source/chapter1/02_version_compatibility.md
Executable file
20
source/chapter1/02_version_compatibility.md
Executable file
@ -0,0 +1,20 @@
|
||||
# 版本兼容性
|
||||
-----------------
|
||||
|
||||
> 4.0
|
||||
>
|
||||
> 翻译:[muhlenXi](https://github.com/muhlenxi) 2017-09-25
|
||||
|
||||
本书描述的是 Swift 4.0,是 Xcode 9 中包含的默认版本。你可以用 Xcode 9 来构建用 Swift 4 或 Swift 3 写的项目。
|
||||
|
||||
> 注意
|
||||
> 当 Swift 4 编译器编译 Swift 3 版本的代码时,它识别的语言版本为 3.2 版本。因此,你可以使用像 `#if swift(>=3.2)` 条件编译块来编写多版本编译器可以并存的代码。
|
||||
|
||||
当你用 Xcode 编译 Swift 3 的代码,Swift 4 中大部分功能是可以使用的。也就是说,下面的功能仅仅是 Swift 4 的代码中可以使用:
|
||||
|
||||
|
||||
* 字符串的子串操作返回的实例是 `Substring` 类型,不再是 `String` 类型。
|
||||
* 在一些地方显式的添加 `@objc` 属性。
|
||||
* 同一文件中一个类型的 extension 可以访问这个类型的 private 属性。
|
||||
|
||||
用 Swift 4 写的项目可以依赖用 Swift 3 写的项目,反之亦然。这意味着,如果你将一个大的项目分解成多个框架(framework),你可以依次迁移每个框架到 Swift 4。
|
||||
@ -20,6 +20,9 @@
|
||||
> 3.1 校对: [SketchK](https://github.com/SketchK) 2017-04-08
|
||||
|
||||
|
||||
> 4.0
|
||||
> 翻译+校对:[muhlenxi](https://github.com/muhlenxi) 2017-09-26
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [简单值(Simple Values)](#simple_values)
|
||||
@ -55,7 +58,7 @@ myVariable = 50
|
||||
let myConstant = 42
|
||||
```
|
||||
|
||||
常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型,声明的同时赋值的话,编译器会自动推断类型。在上面的例子中,编译器推断出 `myVariable` 是一个整数类型(integer)因为它的初始值是整数。
|
||||
常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型。当你通过一个值来声明变量和常量时,编译器会自动推断其类型。在上面的例子中,编译器推断出 `myVariable` 是一个整数类型(integer)因为它的初始值是整数。
|
||||
|
||||
如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。
|
||||
|
||||
@ -78,7 +81,7 @@ let widthLabel = label + String(width)
|
||||
> 练习:
|
||||
> 删除最后一行中的 `String`,错误提示是什么?
|
||||
|
||||
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。例如:
|
||||
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠(\)。例如:
|
||||
|
||||
```swift
|
||||
let apples = 3
|
||||
@ -90,6 +93,19 @@ let fruitSummary = "I have \(apples + oranges) pieces of fruit."
|
||||
> 练习:
|
||||
> 使用 `\()` 来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。
|
||||
|
||||
使用一对三个单引号(`"""`)来包含多行字符串内容,字符串中的内容(包括引号、空格、换行符等)都会保留下来。举个例子:
|
||||
|
||||
```swift
|
||||
let quotation = """
|
||||
Even though there's whitespace to the left,
|
||||
the actual lines aren't indented.
|
||||
Except for this line.
|
||||
Double quotes (") can appear without being escaped.
|
||||
|
||||
I still have \(apples + oranges) pieces of fruit.
|
||||
"""
|
||||
```
|
||||
|
||||
使用方括号 `[]` 来创建数组和字典,并使用下标或者键(key)来访问元素。最后一个元素后面允许有个逗号。
|
||||
|
||||
```swift
|
||||
@ -103,7 +119,7 @@ var occupations = [
|
||||
occupations["Jayne"] = "Public Relations"
|
||||
```
|
||||
|
||||
要创建一个空数组或者字典,使用初始化语法。
|
||||
使用初始化语法来创建一个空数组或者空字典。
|
||||
|
||||
```swift
|
||||
let emptyArray = [String]()
|
||||
@ -120,7 +136,7 @@ occupations = [:]
|
||||
<a name="control_flow"></a>
|
||||
## 控制流
|
||||
|
||||
使用 `if` 和 `switch` 来进行条件操作,使用 `for-in`、 `for`、 `while` 和 `repeat-while` 来进行循环。包裹条件和循环变量括号可以省略,但是语句体的大括号是必须的。
|
||||
使用 `if` 和 `switch` 来进行条件操作,使用 `for-in`、 `while` 和 `repeat-while` 来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须的。
|
||||
|
||||
```swift
|
||||
let individualScores = [75, 43, 103, 87, 12]
|
||||
@ -137,7 +153,7 @@ print(teamScore)
|
||||
|
||||
在 `if` 语句中,条件必须是一个布尔表达式——这意味着像 `if score { ... }` 这样的代码将报错,而不会隐形地与 0 做对比。
|
||||
|
||||
你可以一起使用 `if` 和 `let` 来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 `nil` 以表示值缺失。在类型后面加一个问号来标记这个变量的值是可选的。
|
||||
你可以一起使用 `if` 和 `let` 一起来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 `nil` 以表示值缺失。在类型后面加一个问号(`?ß`)来标记这个变量的值是可选的。
|
||||
|
||||
```swift
|
||||
var optionalString: String? = "Hello"
|
||||
@ -183,9 +199,9 @@ default:
|
||||
|
||||
注意 `let` 在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量 `x`。
|
||||
|
||||
运行 `switch` 中匹配到的子句之后,程序会退出 `switch` 语句,并不会继续向下运行,所以不需要在每个子句结尾写 `break`。
|
||||
运行 `switch` 中匹配到的 `case` 语句之后,程序会退出 `switch` 语句,并不会继续向下运行,所以不需要在每个子句结尾写 `break`。
|
||||
|
||||
你可以使用 `for-in` 来遍历字典,需要两个变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。
|
||||
你可以使用 `for-in` 来遍历字典,需要一对儿变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。
|
||||
|
||||
```swift
|
||||
let interestingNumbers = [
|
||||
@ -207,23 +223,23 @@ print(largest)
|
||||
> 练习:
|
||||
> 添加另一个变量来记录最大数字的种类(kind),同时仍然记录这个最大数字的值。
|
||||
|
||||
使用 `while` 来重复运行一段代码直到不满足条件。循环条件也可以在结尾,保证能至少循环一次。
|
||||
使用 `while` 来重复运行一段代码直到条件改变。循环条件也可以在结尾,保证能至少循环一次。
|
||||
|
||||
```swift
|
||||
var n = 2
|
||||
while n < 100 {
|
||||
n = n * 2
|
||||
n *= 2
|
||||
}
|
||||
print(n)
|
||||
|
||||
var m = 2
|
||||
repeat {
|
||||
m = m * 2
|
||||
m *= 2
|
||||
} while m < 100
|
||||
print(m)
|
||||
```
|
||||
|
||||
你可以在循环中使用 `..<` 来表示范围。
|
||||
你可以在循环中使用 `..<` 来表示下标范围。
|
||||
|
||||
```swift
|
||||
var total = 0
|
||||
@ -259,7 +275,7 @@ func greet(_ person: String, on day: String) -> String {
|
||||
greet("John", on: "Wednesday")
|
||||
```
|
||||
|
||||
使用元组来让一个函数返回多个值。该元组的元素可以用名称或数字来表示。
|
||||
使用元组来生成复合值,比如让一个函数返回多个值。该元组的元素可以用名称或数字来获取。
|
||||
|
||||
```swift
|
||||
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
|
||||
@ -283,23 +299,6 @@ print(statistics.sum)
|
||||
print(statistics.2)
|
||||
```
|
||||
|
||||
函数可以带有可变个数的参数,这些参数在函数内表现为数组的形式:
|
||||
|
||||
```swift
|
||||
func sumOf(numbers: Int...) -> Int {
|
||||
var sum = 0
|
||||
for number in numbers {
|
||||
sum += number
|
||||
}
|
||||
return sum
|
||||
}
|
||||
sumOf()
|
||||
sumOf(numbers: 42, 597, 12)
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 写一个计算参数平均值的函数。
|
||||
|
||||
函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
|
||||
|
||||
```swift
|
||||
@ -416,7 +415,7 @@ class NamedShape {
|
||||
|
||||
注意 `self` 被用来区别实例变量 `name` 和构造器的参数 `name`。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像 `numberOfSides`)还是通过构造器(就像 `name`)。
|
||||
|
||||
如果你需要在删除对象之前进行一些清理工作,使用 `deinit` 创建一个析构函数。
|
||||
如果你需要在对象释放之前进行一些清理工作,使用 `deinit` 创建一个析构函数。
|
||||
|
||||
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。
|
||||
|
||||
@ -523,6 +522,7 @@ let sideLength = optionalSquare?.sideLength
|
||||
<a name="enumerations_and_structure"></a>
|
||||
## 枚举和结构体
|
||||
|
||||
|
||||
使用 `enum` 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
|
||||
|
||||
```swift
|
||||
@ -587,9 +587,9 @@ let heartsDescription = hearts.simpleDescription()
|
||||
> 练习:
|
||||
> 给 `Suit` 添加一个 `color()` 方法,对 `spades` 和 `clubs` 返回 “black” ,对 `hearts` 和 `diamonds` 返回 “red” 。
|
||||
|
||||
注意在上面的例子中用了两种方式引用 `hearts` 枚举成员:给 `hearts` 常量赋值时,枚举成员 `Suit.hearts` 需要用全名来引用,因为常量没有显式指定类型。在 `switch` 里,枚举成员使用缩写 `.hearts` 来引用,因为 `self` 已经是一个 `suit` 类型,在已知变量类型的情况下可以使用缩写。
|
||||
注意在上面的例子中用了两种方式引用 `hearts` 枚举成员:给 `hearts` 常量赋值时,枚举成员 `Suit.hearts` 需要用全名来引用,因为常量没有显式指定类型。在 `switch` 里,枚举成员使用缩写 `.hearts` 来引用,因为 `self` 的值已经是一个 `suit` 类型,在已知变量类型的情况下可以使用缩写。
|
||||
|
||||
如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着不同的枚举成员的关联值都可以不同。你可以把关联值想象成枚举成员的寄存属性。例如,考虑从服务器获取日出和日落的时间。服务器会返回正常结果或者错误信息。
|
||||
如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着不同的枚举成员的关联值都可以不同。你可以把关联值想象成枚举成员的寄存属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。
|
||||
|
||||
```swift
|
||||
enum ServerResponse {
|
||||
@ -613,7 +613,7 @@ case let .failure(message):
|
||||
|
||||
注意日升和日落时间是如何从 `ServerResponse` 中提取到并与 `switch` 的 `case` 相匹配的。
|
||||
|
||||
使用 `struct` 来创建一个结构体。结构体和类有很多相同的地方,比如方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
|
||||
使用 `struct` 来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
|
||||
|
||||
```swift
|
||||
struct Card {
|
||||
@ -643,7 +643,7 @@ protocol ExampleProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
类、枚举和结构体都可以实现协议。
|
||||
类、枚举和结构体都可以遵循协议。
|
||||
|
||||
```swift
|
||||
class SimpleClass: ExampleProtocol {
|
||||
@ -688,7 +688,7 @@ print(7.simpleDescription)
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 给 `Double` 类型写一个扩展,添加 `absoluteValue` 功能。
|
||||
> 给 `Double` 类型写一个扩展,添加 `absoluteValue` 属性。
|
||||
|
||||
你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。
|
||||
|
||||
@ -713,7 +713,7 @@ enum PrinterError: Error {
|
||||
}
|
||||
```
|
||||
|
||||
使用 `throw` 来抛出一个错误并使用 `throws` 来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
|
||||
使用 `throw` 来抛出一个错误和使用 `throws` 来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
|
||||
|
||||
```swift
|
||||
func send(job: Int, toPrinter printerName: String) throws -> String {
|
||||
@ -756,7 +756,7 @@ do {
|
||||
> 练习:
|
||||
> 在 `do` 代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个 `catch` 块进行接收?怎么使第二个和第三个 `catch` 进行接收呢?
|
||||
|
||||
另一种处理错误的方式使用 `try?` 将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 `nil`。否则的话,结果会是一个包含函数返回值的可选值。
|
||||
另一种处理错误的方式使用 `try?` 将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 `nil`。否则,结果会是一个包含函数返回值的可选值。
|
||||
|
||||
```swift
|
||||
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
|
||||
@ -788,7 +788,7 @@ print(fridgeIsOpen)
|
||||
在尖括号里写一个名字来创建一个泛型函数或者类型。
|
||||
|
||||
```swift
|
||||
func repeatItem<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
|
||||
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
|
||||
var result = [Item]()
|
||||
for _ in 0..<numberOfTimes {
|
||||
result.append(item)
|
||||
@ -803,14 +803,14 @@ repeatItem(repeating: "knock", numberOfTimes:4)
|
||||
```swift
|
||||
// 重新实现 Swift 标准库中的可选类型
|
||||
enum OptionalValue<Wrapped> {
|
||||
case None
|
||||
case Some(Wrapped)
|
||||
case none
|
||||
case some(Wrapped)
|
||||
}
|
||||
var possibleInteger: OptionalValue<Int> = .None
|
||||
possibleInteger = .Some(100)
|
||||
var possibleInteger: OptionalValue<Int> = .none
|
||||
possibleInteger = .some(100)
|
||||
```
|
||||
|
||||
在类型名后面使用 `where` 来指定对类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
|
||||
在类型名后面使用 `where` 来指定对类型的一系列需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
|
||||
|
||||
```swift
|
||||
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
|
||||
@ -20,6 +20,9 @@
|
||||
> 3.1
|
||||
> 翻译:[qhd](https://github.com/qhd) 2017-04-18
|
||||
|
||||
> 4.0
|
||||
> 翻译:[muhlenXi](https://github.com/muhlenxi) 2017-09-21
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [存储属性的初始赋值](#setting_initial_values_for_stored_properties)
|
||||
@ -34,7 +37,7 @@
|
||||
|
||||
*构造过程*是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。
|
||||
|
||||
通过定义*构造器*来实现构造过程,这些构造器可以看做是用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
|
||||
通过定义*构造器*来实现构造过程,就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
|
||||
|
||||
类的实例也可以通过定义*析构器*在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。
|
||||
|
||||
@ -94,12 +97,12 @@ struct Fahrenheit {
|
||||
<a name="customizing_initialization"></a>
|
||||
## 自定义构造过程
|
||||
|
||||
你可以通过输入参数和可选类型的属性来自定义构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。
|
||||
你可以通过输入参数和可选类型的属性来自定义构造过程,也可以在构造过程中给常量属性赋初值。这些都将在后面章节中提到。
|
||||
|
||||
<a name="initialization_parameters"></a>
|
||||
### 构造参数
|
||||
|
||||
自定义`构造过程`时,可以在定义中提供构造参数,指定所需值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
|
||||
自定义`构造过程`时,可以在定义中提供构造参数,指定参数值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
|
||||
|
||||
下面例子中定义了一个包含摄氏度温度的结构体 `Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)`和`init(fromKelvin:)`,二者分别通过接受不同温标下的温度值来创建新的实例:
|
||||
|
||||
@ -113,6 +116,7 @@ struct Celsius {
|
||||
temperatureInCelsius = kelvin - 273.15
|
||||
}
|
||||
}
|
||||
|
||||
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
|
||||
// boilingPointOfWater.temperatureInCelsius 是 100.0
|
||||
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
|
||||
@ -130,7 +134,7 @@ let freezingPointOfWater = Celsius(fromKelvin: 273.15)
|
||||
|
||||
以下例子中定义了一个结构体 `Color`,它包含了三个常量:`red`、`green` 和 `blue`。这些属性可以存储 `0.0` 到 `1.0` 之间的值,用来指示颜色中红、绿、蓝成分的含量。
|
||||
|
||||
`Color`提供了一个构造器,其中包含三个`Double`类型的构造参数。`Color`也可以提供第二个构造器,它只包含名为`white`的`Double`类型的参数,它被用于给上述三个构造参数赋予同样的值。
|
||||
`Color` 提供了一个构造器,其中包含三个`Double`类型的构造参数。`Color` 也提供了第二个构造器,它只包含名为`white` 的 `Double` 类型的参数,它被用于给上述三个构造参数赋予同样的值。
|
||||
|
||||
```swift
|
||||
struct Color {
|
||||
@ -148,7 +152,7 @@ struct Color {
|
||||
}
|
||||
```
|
||||
|
||||
两种构造器都能用于创建一个新的`Color`实例,你需要为构造器每个外部参数传值:
|
||||
两种构造器都能通过提供的初始参数值来创建一个新的`Color`实例:
|
||||
|
||||
```swift
|
||||
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
|
||||
@ -182,6 +186,7 @@ struct Celsius {
|
||||
temperatureInCelsius = celsius
|
||||
}
|
||||
}
|
||||
|
||||
let bodyTemperature = Celsius(37.0)
|
||||
// bodyTemperature.temperatureInCelsius 为 37.0
|
||||
```
|
||||
@ -206,6 +211,7 @@ class SurveyQuestion {
|
||||
print(text)
|
||||
}
|
||||
}
|
||||
|
||||
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
|
||||
cheeseQuestion.ask()
|
||||
// 打印 "Do you like cheese?"
|
||||
@ -215,7 +221,7 @@ cheeseQuestion.response = "Yes, I do like cheese."
|
||||
调查问题的答案在回答前是无法确定的,因此我们将属性 `response` 声明为 `String?` 类型,或者说是`可选字符串类型`。当 `SurveyQuestion` 实例化时,它将自动赋值为`nil`,表明此字符串暂时还没有值。
|
||||
|
||||
<a name="assigning_constant_properties_during_initialization"></a>
|
||||
### 构造过程中常量属性的修改
|
||||
### 构造过程中常量属性的赋值
|
||||
|
||||
你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。
|
||||
|
||||
@ -268,7 +274,7 @@ var item = ShoppingListItem()
|
||||
|
||||
下面例子中定义了一个结构体 `Size`,它包含两个属性 `width` 和 `height`。Swift 可以根据这两个属性的初始赋值`0.0` 自动推导出它们的类型为 `Double`。
|
||||
|
||||
结构体`Size`自动获得了一个逐一成员构造器`init(width:height:)`。你可以用它来为`Size`创建新的实例:
|
||||
结构体 `Size` 自动获得了一个逐一成员构造器 `init(width:height:)`。你可以用它来创建新的 Size 实例:
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
@ -278,42 +284,44 @@ let twoByTwo = Size(width: 2.0, height: 2.0)
|
||||
```
|
||||
|
||||
<a name="initializer_delegation_for_value_types"></a>
|
||||
|
||||
## 值类型的构造器代理
|
||||
|
||||
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
|
||||
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能避免多个构造器间的代码重复。
|
||||
|
||||
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.html)),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
|
||||
|
||||
对于值类型,你可以使用 `self.init` 在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 `self.init`。
|
||||
|
||||
如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器
|
||||
请注意,如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器
|
||||
|
||||
> 注意
|
||||
假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./21_Extensions.html)章节。
|
||||
|
||||
下面例子将定义一个结构体`Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体`Size`和`Point`,它们各自为其所有的属性提供了初始值`0.0`。
|
||||
下面例子将定义一个结构体 `Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体 `Size` 和 `Point`,它们各自为其所有的属性提供了默认初始值 `0.0`。
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
```
|
||||
|
||||
你可以通过以下三种方式为`Rect`创建实例——使用被初始化为默认值的`origin`和`size`属性来初始化;提供指定的`origin`和`size`实例来初始化;提供指定的`center`和`size`来初始化。在下面`Rect`结构体定义中,我们为这三种方式提供了三个自定义的构造器:
|
||||
你可以通过以下三种方式为 `Rect` 创建实例——使用含有默认值的 `origin` 和 `size` 属性来初始化;提供指定的`origin` 和 `size` 实例来初始化;提供指定的 `center` 和 `size` 来初始化。在下面 `Rect` 结构体定义中,我们为这三种方式提供了三个自定义的构造器:
|
||||
|
||||
```swift
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
init() {}
|
||||
|
||||
init(origin: Point, size: Size) {
|
||||
self.origin = origin
|
||||
self.size = size
|
||||
}
|
||||
|
||||
init(center: Point, size: Size) {
|
||||
let originX = center.x - (size.width / 2)
|
||||
let originY = center.y - (size.height / 2)
|
||||
@ -322,7 +330,7 @@ struct Rect {
|
||||
}
|
||||
```
|
||||
|
||||
第一个`Rect`构造器`init()`,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是一个空函数,使用一对大括号`{}`来表示,它没有执行任何构造过程。调用这个构造器将返回一个`Rect`实例,它的`origin`和`size`属性都使用定义时的默认值`Point(x: 0.0, y: 0.0)`和`Size(width: 0.0, height: 0.0)`:
|
||||
第一个 `Rect` 构造器 `init()`,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是一个空函数,使用一对大括号 `{}` 来表示。调用这个构造器将返回一个 `Rect` 实例,它的 `origin` 和 `size` 属性都使用定义时的默认值 `Point(x: 0.0, y: 0.0)` 和 `Size(width: 0.0, height: 0.0)`:
|
||||
|
||||
```swift
|
||||
let basicRect = Rect()
|
||||
@ -345,7 +353,7 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
// centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)
|
||||
```
|
||||
|
||||
构造器`init(center:size:)`可以直接将`origin`和`size`的新值赋值到对应的属性中。然而,利用恰好提供了相关功能的现有构造器会更为方便,构造器`init(center:size:)`的意图也会更加清晰。
|
||||
构造器 `init(center:size:)` 可以直接将 `origin` 和 `size` 的新值赋值到对应的属性中。然而,构造器 `init(center:size:)` 通过使用提供了相关功能的现有构造器将会更加便捷。
|
||||
|
||||
> 注意
|
||||
如果你想用另外一种不需要自己定义`init()`和`init(origin:size:)`的方式来实现这个例子,请参考[扩展](./21_Extensions.html)。
|
||||
@ -360,9 +368,11 @@ Swift 为类类型提供了两种构造器来确保实例中所有存储型属
|
||||
<a name="designated_initializers_and_convenience_initializers"></a>
|
||||
### 指定构造器和便利构造器
|
||||
|
||||
*指定构造器*是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
|
||||
*指定构造器*是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类合适的构造器来实现父类的初始化。
|
||||
|
||||
每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
|
||||
类倾向于拥有少量指定构造器,普遍的是一个类拥有一个指定构造器。指定构造器在初始化的地方通过 "管道" 将初始化过程持续到父类链。
|
||||
|
||||
每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
|
||||
|
||||
*便利构造器*是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
|
||||
|
||||
@ -399,7 +409,7 @@ convenience init(parameters) {
|
||||
便利构造器必须调用*同*类中定义的其它构造器。
|
||||
|
||||
##### 规则 3
|
||||
便利构造器必须最终导致一个指定构造器被调用。
|
||||
便利构造器最后必须调用指定构造器。
|
||||
|
||||
一个更方便记忆的方法是:
|
||||
|
||||
@ -415,7 +425,7 @@ convenience init(parameters) {
|
||||
子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则 2 和 3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则 1。
|
||||
|
||||
> 注意
|
||||
这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类定义如何实现。
|
||||
这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类的构造器如何实现。
|
||||
|
||||
下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。
|
||||
|
||||
@ -424,28 +434,28 @@ convenience init(parameters) {
|
||||
<a name="two_phase_initialization"></a>
|
||||
### 两段式构造过程
|
||||
|
||||
Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性被引入它们的类指定一个初始值。当每个存储型属性的初始值被确定后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性。
|
||||
Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性。
|
||||
|
||||
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。
|
||||
|
||||
> 注意
|
||||
Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值 `0` 或空值(比如说`0`或`nil`)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以 `0` 或 `nil` 作为合法默认值的情况。
|
||||
|
||||
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能不出错地完成:
|
||||
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程不出错地完成:
|
||||
|
||||
##### 安全检查 1
|
||||
|
||||
指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
|
||||
指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
|
||||
|
||||
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。
|
||||
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。
|
||||
|
||||
##### 安全检查 2
|
||||
|
||||
指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
|
||||
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器,如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
|
||||
|
||||
##### 安全检查 3
|
||||
|
||||
便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
|
||||
便利构造器必须为任意属性(包括同类中定义的)赋新值之前代理调用同一类中的其它构造器,如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
|
||||
|
||||
##### 安全检查 4
|
||||
|
||||
@ -506,11 +516,11 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
正如重写属性,方法或者是下标,`override` 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否正确。
|
||||
|
||||
> 注意
|
||||
当你重写一个父类的指定构造器时,你总是需要写`override`修饰符,即使你的子类将父类的指定构造器重写为了便利构造器。
|
||||
当你重写一个父类的指定构造器时,你总是需要写`override`修饰符,即使是为了实现子类的便利构造器。
|
||||
|
||||
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加`override`前缀。
|
||||
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。
|
||||
|
||||
在下面的例子中定义了一个叫`Vehicle`的基类。基类中声明了一个存储型属性`numberOfWheels`,它是值为`0`的`Int`类型的存储型属性。`numberOfWheels`属性用于创建名为`descrpiption`的`String`类型的计算型属性:
|
||||
在下面的例子中定义了一个叫 `Vehicle` 的基类。基类中声明了一个存储型属性 `numberOfWheels`,它是默认值为 `0` 的 `Int` 类型的存储型属性。`numberOfWheels` 属性用于创建名为 `descrpiption` 的 `String` 类型的计算型属性:
|
||||
|
||||
```swift
|
||||
class Vehicle {
|
||||
@ -521,7 +531,8 @@ class Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
`Vehicle`类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。自动获得的默认构造器总会是类中的指定构造器,它可以用于创建`numberOfWheels`为`0`的`Vehicle`实例:
|
||||
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。自动获得的默认构造器总是类中的指定构造器,它可以用于创建`numberOfWheels` 为 `0` 的 `Vehicle`
|
||||
实例:
|
||||
|
||||
```swift
|
||||
let vehicle = Vehicle()
|
||||
@ -555,20 +566,21 @@ print("Bicycle: \(bicycle.description)")
|
||||
> 注意
|
||||
子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。
|
||||
|
||||
|
||||
<a name="automatic_initializer_inheritance"></a>
|
||||
### 构造器的自动继承
|
||||
|
||||
如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。
|
||||
如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。事实上,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。
|
||||
|
||||
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:
|
||||
|
||||
##### 规则 1
|
||||
|
||||
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
|
||||
如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
|
||||
|
||||
##### 规则 2
|
||||
|
||||
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。
|
||||
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。
|
||||
|
||||
即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
|
||||
|
||||
@ -588,6 +600,7 @@ class Food {
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init(name: "[Unnamed]")
|
||||
}
|
||||
@ -607,7 +620,7 @@ let namedMeat = Food(name: "Bacon")
|
||||
|
||||
`Food` 类中的构造器 `init(name: String)` 被定义为一个指定构造器,因为它能确保 `Food` 实例的所有存储型属性都被初始化。`Food` 类没有父类,所以 `init(name: String)` 构造器不需要调用 `super.init()` 来完成构造过程。
|
||||
|
||||
`Food`类同样提供了一个没有参数的便利构造器`init()`。这个`init()`构造器为新食物提供了一个默认的占位名字,通过横向代理到指定构造器`init(name: String)`并给参数`name`传值`[Unnamed]`来实现:
|
||||
`Food` 类同样提供了一个没有参数的便利构造器 `init()`。这个 `init()` 构造器为新食物提供了一个默认的占位名字,通过横向代理到指定构造器 `init(name: String)` 并给参数 `name` 赋值为 `[Unnamed]` 来实现:
|
||||
|
||||
```swift
|
||||
let mysteryMeat = Food()
|
||||
@ -635,11 +648,11 @@ class RecipeIngredient: Food {
|
||||
|
||||
`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)`,它只通过 `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`将父类的指定构造器重写为了便利构造器,它依然提供了父类的所有指定构造器的实现。因此,`RecipeIngredient`会自动继承父类的所有便利构造器。
|
||||
尽管 `RecipeIngredient` 将父类的指定构造器重写为了便利构造器,但是它依然提供了父类的所有指定构造器的实现。因此,`RecipeIngredient` 会自动继承父类的所有便利构造器。
|
||||
|
||||
在这个例子中,`RecipeIngredient` 的父类是 `Food`,它有一个便利构造器 `init()`。这个便利构造器会被`RecipeIngredient` 继承。这个继承版本的 `init()` 在功能上跟 `Food` 提供的版本是一样的,只是它会代理到`RecipeIngredient` 版本的 `init(name: String)` 而不是 `Food` 提供的版本。
|
||||
|
||||
@ -653,7 +666,7 @@ let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
|
||||
|
||||
类层级中第三个也是最后一个类是 `RecipeIngredient` 的子类,叫做 `ShoppingListItem`。这个类构建了购物单中出现的某一种食谱原料。
|
||||
|
||||
购物单中的每一项总是从未购买状态开始的。为了呈现这一事实,`ShoppingListItem`引入了一个布尔类型的属性`purchased`,它的默认值是`false`。`ShoppingListItem`还添加了一个计算型属性`description`,它提供了关于`ShoppingListItem`实例的一些文字描述:
|
||||
购物单中的每一项总是从未购买状态开始的。为了呈现这一事实,`ShoppingListItem` 引入了一个 Boolean(布尔类型) 的属性 `purchased`,它的默认值是 `false`。`ShoppingListItem` 还添加了一个计算型属性 `description`,它提供了关于 `ShoppingListItem` 实例的一些文字描述:
|
||||
|
||||
```swift
|
||||
class ShoppingListItem: RecipeIngredient {
|
||||
@ -675,7 +688,7 @@ class ShoppingListItem: RecipeIngredient {
|
||||
|
||||

|
||||
|
||||
你可以使用全部三个继承来的构造器来创建`ShoppingListItem`的新实例:
|
||||
你可以使用三个继承来的构造器来创建 `ShoppingListItem` 的新实例:
|
||||
|
||||
```swift
|
||||
var breakfastList = [
|
||||
@ -693,12 +706,12 @@ for item in breakfastList {
|
||||
// 6 x eggs ✘
|
||||
```
|
||||
|
||||
如上所述,例子中通过字面量方式创建了一个数组`breakfastList`,它包含了三个`ShoppingListItem`实例,因此数组的类型也能被自动推导为`[ShoppingListItem]`。在数组创建完之后,数组中第一个`ShoppingListItem`实例的名字从`[Unnamed]`更改为`Orange juice`,并标记为已购买。打印数组中每个元素的描述显示了它们都已按照预期被赋值。
|
||||
如上所述,例子中通过字面量方式创建了一个数组 `breakfastList`,它包含了三个 `ShoppingListItem` 实例,因此数组的类型也能被自动推导为 `[ShoppingListItem]`。在数组创建完之后,数组中第一个 `ShoppingListItem` 实例的名字从 `[Unnamed]` 更改为 `Orange juice`,并标记状态为已购买。打印数组中每个元素的描述显示了它们都已按照预期被赋值。
|
||||
|
||||
<a name="failable_initializers"></a>
|
||||
## 可失败构造器
|
||||
|
||||
如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
|
||||
如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器是很有用的。这里所指的“失败” 指的是,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
|
||||
|
||||
为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在 `init` 关键字后面添加问号 (`init?`)。
|
||||
|
||||
@ -717,9 +730,9 @@ let wholeNumber: Double = 12345.0
|
||||
let pi = 3.14159
|
||||
|
||||
if let valueMaintained = Int(exactly: wholeNumber) {
|
||||
print("\(wholeNumber) conversion to Int maintains value")
|
||||
print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
|
||||
}
|
||||
// 打印 "12345.0 conversion to Int maintains value"
|
||||
// 打印 "12345.0 conversion to Int maintains value of 12345"
|
||||
|
||||
let valueChanged = Int(exactly: pi)
|
||||
// valueChanged 是 Int? 类型, 不是 Int 类型
|
||||
@ -736,13 +749,14 @@ if valueChanged == nil {
|
||||
struct Animal {
|
||||
let species: String
|
||||
init?(species: String) {
|
||||
if species.isEmpty { return nil }
|
||||
if species.isEmpty {
|
||||
return nil }
|
||||
self.species = species
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你可以通过该可失败构造器来构建一个`Animal`的实例,并检查构造过程是否成功:
|
||||
你可以通过该可失败构造器来尝试构建一个 `Animal` 的实例,并检查构造过程是否成功:
|
||||
|
||||
```swift
|
||||
let someCreature = Animal(species: "Giraffe")
|
||||
@ -838,7 +852,7 @@ if unknownUnit == nil {
|
||||
<a name="propagation_of_initialization_failure"></a>
|
||||
### 构造失败的传递
|
||||
|
||||
类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
|
||||
类,结构体,枚举的可失败构造器可以横向代理到同类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
|
||||
|
||||
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。
|
||||
|
||||
@ -998,7 +1012,7 @@ class SomeSubclass: SomeClass {
|
||||
|
||||
这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。
|
||||
|
||||
下面介绍了如何用闭包为属性提供默认值:
|
||||
下面模板介绍了如何用闭包为属性提供默认值:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
@ -1010,21 +1024,21 @@ class SomeClass {
|
||||
}
|
||||
```
|
||||
|
||||
注意闭包结尾的大括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
|
||||
注意闭包结尾的花括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
|
||||
|
||||
> 注意
|
||||
如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的 `self` 属性,或者调用任何实例方法。
|
||||
|
||||
下面例子中定义了一个结构体`Checkerboard`,它构建了西洋跳棋游戏的棋盘:
|
||||
下面例子中定义了一个结构体 `Chessboard`,它构建了西洋跳棋游戏的棋盘,西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行的:
|
||||
|
||||

|
||||
|
||||
西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行。为了呈现这副游戏棋盘,`Checkerboard`结构体定义了一个属性`boardColors`,它是一个包含`100`个`Bool`值的数组。在数组中,值为`true`的元素表示一个黑格,值为`false`的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。
|
||||
为了呈现这副游戏棋盘,`Chessboard `结构体定义了一个属性 `boardColors`,它是一个包含 `64` 个 `Bool`值的数组。在数组中,值为 `true` 的元素表示一个黑格,值为 `false` 的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。
|
||||
|
||||
`boardColor`数组是通过一个闭包来初始化并设置颜色值的:
|
||||
`boardColors` 数组是通过一个闭包来初始化并设置颜色值的:
|
||||
|
||||
```swift
|
||||
struct Checkerboard {
|
||||
struct Chessboard {
|
||||
let boardColors: [Bool] = {
|
||||
var temporaryBoard = [Bool]()
|
||||
var isBlack = false
|
||||
@ -1037,16 +1051,17 @@ struct Checkerboard {
|
||||
}
|
||||
return temporaryBoard
|
||||
}()
|
||||
|
||||
func squareIsBlackAtRow(row: Int, column: Int) -> Bool {
|
||||
return boardColors[(row * 8) + column]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每当一个新的`Checkerboard`实例被创建时,赋值闭包会被执行,`boardColors`的默认值会被计算出来并返回。上面例子中描述的闭包将计算出棋盘中每个格子对应的颜色,并将这些值保存到一个临时数组`temporaryBoard`中,最后在构建完成时将此数组作为闭包返回值返回。这个返回的数组会保存到`boardColors`中,并可以通过工具函数`squareIsBlackAtRow`来查询:
|
||||
每当一个新的 `Chessboard` 实例被创建时,赋值闭包则会被执行,`boardColors` 的默认值会被计算出来并返回。上面例子中描述的闭包将计算出棋盘中每个格子对应的颜色,并将这些值保存到一个临时数组 `temporaryBoard` 中,最后在构建完成时将此数组作为闭包返回值返回。这个返回的数组会保存到 `boardColors` 中,并可以通过工具函数`squareIsBlackAtRow`来查询:
|
||||
|
||||
```swift
|
||||
let board = Checkerboard()
|
||||
let board = Chessboard()
|
||||
print(board.squareIsBlackAtRow(0, column: 1))
|
||||
// 打印 "true"
|
||||
print(board.squareIsBlackAtRow(7, column: 7))
|
||||
|
||||
Reference in New Issue
Block a user