Merge pull request #711 from magicdict/master

This commit is contained in:
梁杰
2017-09-02 01:13:45 +08:00
committed by GitHub
3 changed files with 121 additions and 22 deletions

View File

@ -48,7 +48,7 @@
- [可选绑定](#optional_binding)
- [隐式解析可选类型](#implicityly_unwrapped_optionals)
- [错误处理](#error_handling)
- [断言](#assertions)
- [断言和先决条件](#assertions_and_Preconditions)
Swift 是一门开发 iOS, macOS, watchOS 和 tvOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。
@ -772,17 +772,21 @@ do {
抛出,捕捉,以及传播错误会在[错误处理](./18_Error_Handling.html)章节详细说明。
<a name="assertions"></a>
## 断言
<a name="assertions_and_Preconditions"></a>
## 断言和先决条件
可选类型可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况。然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能没办法继续执行。这时,你可以在你的代码中触发一个 *断言assertion* 来结束代码运行并通过调试来找到值缺失的原因
断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true则代码像往常一样继续执行。如果布尔条件评估结果为false程序的当前状态是无效的则代码执行结束应用程序中止
你使用断言和先决条件来表达你所做的假设和你在编码时候的期望。你可以将这些包含在你的代码中。断言帮助你在开发阶段找到错误和不正确的假设,先决条件帮助你在生产环境中探测到存在的问题。
除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的[错误处理](./18_Error_Handling.html)不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。
使用断言和先决条件不是一个能够避免出现程序出现无效状态的编码方法。然而,如果一个无效状态程序产生了,断言和先决条件可以强制检查你的数据和程序状态,使得你的程序可预测的中止(译者:不是系统强制的,被动的中止),并帮助使这个问题更容易调试。一旦探测到无效的状态,执行则被中止,防止无效的状态导致的进一步对于系统的伤害。
断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。
### 使用断言进行调试
断言会在运行时判断一个逻辑条件是否为 `true`。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为 `true`,代码运行会继续进行;如果条件判断为 `false`,代码执行结束,你的应用被终止。
如果你的代码在调试环境下触发了一个断言,比如你在 Xcode 中构建并运行一个应用,你可以清楚地看到不合法的状态发生在哪里并检查断言被触发时你的应用的状态。此外,断言允许你附加一条调试信息。
你可以使用全局 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示:
```swift
@ -799,18 +803,31 @@ assert(age >= 0, "A person's age cannot be less than zero")
assert(age >= 0)
```
> 注意:
> 当代码使用优化编译的时候,断言将会被禁用,例如在 Xcode 中,使用默认的 target Release 配置选项来 build 时,断言会被禁用。
如果代码已经检查了条件,你可以使用 `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.")
}
```
当条件可能为假时使用断言但是最终一定要_保证_条件为真这样你的代码才能继续运行。断言的适用情景
### 强制执行先决条件
* 整数类型的下标索引被传入一个自定义下标实现,但是下标索引值可能太小或者太大
* 需要给函数传入一个值,但是非法的值可能导致函数不能正常执行。
* 一个可选值现在是 `nil`,但是后面的代码运行需要一个非 `nil` 值。
当一个条件可能为false但是继续执行代码要求条件必须为true的时候需要使用先决条件。例如使用先决条件来检查是否下标越界或者来检查是否将一个正确的参数传给函数
请参考[下标](./12_Subscripts.html)和[函数](./06_Functions.html)。
你可以使用全局 `precondition(_:_:file:line:)` 函数来写一个先决条件。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示:
```swift
// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")
```
你可以调用 `precondition(_:_:file:line:)`方法来表明出现了一个错误例如switch进入了default分支但是所有的有效值应该被任意一个其他分支非default分支处理。
> 注意:
> 断言可能导致你的应用终止运行,所以你应当仔细设计你的代码来让非法条件不会出现。然而,在你的应用发布之前,有时候非法条件可能出现,这时使用断言可以快速发现问题。
> 如果你使用unchecked模式-Ounchecked编译代码先决条件不会进行检查。编译器假设所有的先决条件总是为true他将优化你的代码。然而`fatalError(_:file:line:)`函数总是中断执行,无论你怎么进行优化设定。
>你能使用 `fatalError(_:file:line:)`函数在设计原型和早期开发阶段这个阶段只有方法的声明但是没有具体实现你可以在方法体中写上fatalError("Unimplemented")作为具体实现。因为fatalError不会像断言和先决条件那样被优化掉所以你可以确保当代码执行到一个没有被实现的方法时程序会被中断。

View File

@ -646,8 +646,36 @@ wishHappyBirthday(to: birthdayPerson)
上面的例子创建了一个名为 `birthdayPerson``Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
> 注意
> 协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。
这里有一个例子将Location类和前面的Named协议进行组合
```swift
class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints "Hello, Seattle!"
```
`beginConcert(in:)`方法接受一个类型为 `Location & Named` 的参数,这意味着"任何Location的子类并且遵循Named协议"。例如City就满足这样的条件。
将 birthdayPerson 传入`beginConcert(in:)`函数是不合法的,因为 Person不是一个Location的子类。就像如果你新建一个类继承与Location但是没有遵循Named协议你用这个类的实例去调用`beginConcert(in:)`函数也是不合法的。
<a name="checking_for_protocol_conformance"></a>
## 检查协议一致性

View File

@ -31,6 +31,8 @@
- [关联类型](#associated_types)
- [泛型 Where 语句](#where_clauses)
- [具有泛型 where 子句的扩展](#extensions_with_a_generic_where_clause)
- [具有泛型 Where 子句的关联类型](#associated_types_with_a_generic_where_clause)
- [泛型下标](#generic_subscripts)
*泛型代码*让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
@ -619,3 +621,55 @@ print([1260.0, 1200.0, 98.6, 37.0].average())
此示例将一个 `average()` 方法添加到 `Item` 类型为 `Double` 的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 `Int` 转换为 `Double` 确保能够进行浮点除法。
就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。
<a name="associated_types_with_a_generic_where_clause"></a>
## 具有泛型 Where 子句的关联类型
你能够包含一个具有泛型 Where 子句的关联类型。例如建立一个包含迭代器Iterator的容器就像是标准库中使用的Sequence协议那样。下面是你所写的代码
```swift
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
```
迭代器Iterator的泛型Where子句要求无论迭代器是什么类型迭代器中的元素类型必须和容器项目的类型保持一致。makeIterator()则提供了容器的迭代器的访问接口。
一个协议继承了另一个协议你通过在协议声明的时候包含泛型Where子句来添加了一个约束到被继承协议的关联类型。例如下面的代码声明了一个ComparableContainer协议它要求所有的Item必须是Comparable的。
```swift
protocol ComparableContainer: Container where Item: Comparable { }
```
<a name="generic_subscripts"></a>
##泛型下标
下标能够是泛型的他们能够包含泛型Where子句。你可以在subscript后面的尖括号中写下占位符placeholder类型名称在下标代码体开始的标志的花括号之前写下泛型Where子句。例如
```swift
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result = [Item]()
for index in indices {
result.append(self[index])
}
return result
}
}
```
这个Container协议的扩展添加了一个下标下标是一个序列的索引返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下
- 在尖括号中的泛型参数Indices必须是符合标准库中的Sequence协议的类型。
- 下标使用的单一的参数indices必须是Indices的实例。
- 泛型Where子句要求SequenceIndices的迭代器其所有的元素都是Int类型。这样就能确保在序列Sequence中的索引和容器(Container)里面的索引类型是一致的。
综合一下,这些约束意味着传入到indices下标是一个整数的序列.
(译者原来的Container协议subscript必须是Int型的扩展中新的subscript允许下标是一个的序列而非单一的值。)