14
README.md
14
README.md
@ -1,7 +1,7 @@
|
||||
《The Swift Programming Language》in Chinese
|
||||
=============================================
|
||||
|
||||
中文版Apple官方Swift教程《The Swift Programming Language》
|
||||
中文版 Apple 官方 Swift 教程《The Swift Programming Language》
|
||||
|
||||
[英文原版](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-ID0)
|
||||
|
||||
@ -9,13 +9,17 @@
|
||||
|
||||
# 在线阅读
|
||||
|
||||
使用Gitbook制作,可以直接[在线阅读](http://swiftguide.cn/)。
|
||||
使用 Gitbook 制作,可以直接[在线阅读](http://swiftguide.cn/)。
|
||||
|
||||
# 当前阶段
|
||||
|
||||
已经更新到Swift 2.0,对应苹果官方文档7.23的更新。
|
||||
已经更新到 Swift 2.2。
|
||||
|
||||
# 2.0译者记录
|
||||
# 2.1 & 2.2 译者记录
|
||||
|
||||
详见各章节开头位置。
|
||||
|
||||
# 2.0 译者记录
|
||||
|
||||
- About Swift ([xtymichael](https://github.com/xtymichael))
|
||||
- A Swift Tour([xtymichael](https://github.com/xtymichael))
|
||||
@ -55,7 +59,7 @@
|
||||
- Generic Parameters and Arguments([wardenNScaiyi](https://github.com/wardenNScaiyi))
|
||||
- Summary of the Grammar([miaosiqi](https://github.com/miaosiqi))
|
||||
|
||||
# 1.0译者记录
|
||||
# 1.0 译者记录
|
||||
|
||||
* 欢迎使用 Swift
|
||||
* 关于 Swift ([numbbbbb])
|
||||
|
||||
BIN
cover/cover.jpg
BIN
cover/cover.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 838 KiB |
BIN
cover/cover_2.0.jpg
Normal file
BIN
cover/cover_2.0.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@ -76,7 +76,7 @@ println(sayHelloAgain("Anna"))
|
||||
func halfOpenRangeLength(start: Int, end: Int) -> Int {
|
||||
return end - start
|
||||
}
|
||||
println(halfOpenRangeLength(1, 10))
|
||||
println(halfOpenRangeLength(1, end:10))
|
||||
// prints "9"
|
||||
```
|
||||
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
> 已同步更新到 Swift 2.2
|
||||
|
||||
# 2.0 新的开始
|
||||
|
||||
> Swift 兴趣交流群:`131595168`, `146932759`, `151336833`, `153549217`. **加入一个群即可,请勿重复添加**
|
||||
|
||||
> <a target='_blank' href="http://swiftist.org">Swift 开发者社区</a>
|
||||
|
||||
> <a target='_blank' href="https://github.com/ipader/SwiftGuide">Swift 资源汇总</a>
|
||||
|
||||
> <a target='_blank' href="http://swiftsandbox.io">Swift 优秀newsletter</a>
|
||||
> <a target='_blank' href="http://swiftweekly.cn">订阅 Swift 开发者周报,每周获取最新 Swift 资源</a>
|
||||
|
||||
<!-- -->
|
||||
> 如果您觉得这个项目不错,请<a target='_blank' href="https://github.com/numbbbbb/the-swift-programming-language-in-chinese">点击Star一下</a>,您的支持是我们最大的动力。
|
||||
|
||||
|
||||
## 1
|
||||
|
||||
开源项目完成难,维护更难。
|
||||
@ -72,5 +71,6 @@ Swift 2.0 参与者名单(按照章节顺序):
|
||||
- [ray16897188](https://github.com/ray16897188)
|
||||
- [wardenNScaiyi](https://github.com/wardenNScaiyi)
|
||||
- [miaosiqi](https://github.com/miaosiqi)
|
||||
- [Realank](https://github.com/Realank)
|
||||
|
||||
最后,感谢[极客学院](http://wiki.jikexueyuan.com)提供的wiki系统,在国内访问起来速度很快,优化后的样式看起来也更舒服。
|
||||
最后,感谢[极客学院](http://wiki.jikexueyuan.com)提供的wiki系统,在国内访问起来速度很快,优化后的样式看起来也更舒服。
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
* [类和结构体](chapter2/09_Classes_and_Structures.md)
|
||||
* [属性](chapter2/10_Properties.md)
|
||||
* [方法](chapter2/11_Methods.md)
|
||||
* [下标脚本](chapter2/12_Subscripts.md)
|
||||
* [下标](chapter2/12_Subscripts.md)
|
||||
* [继承](chapter2/13_Inheritance.md)
|
||||
* [构造过程](chapter2/14_Initialization.md)
|
||||
* [析构过程](chapter2/15_Deinitialization.md)
|
||||
@ -29,8 +29,8 @@
|
||||
* [扩展](chapter2/21_Extensions.md)
|
||||
* [协议](chapter2/22_Protocols.md)
|
||||
* [泛型](chapter2/23_Generics.md)
|
||||
* [权限控制](chapter2/24_Access_Control.md)
|
||||
* [高级操作符](chapter2/25_Advanced_Operators.md)
|
||||
* [访问控制](chapter2/24_Access_Control.md)
|
||||
* [高级运算符](chapter2/25_Advanced_Operators.md)
|
||||
* 语言参考
|
||||
* [关于语言参考](chapter3/01_About_the_Language_Reference.md)
|
||||
* [词法结构](chapter3/02_Lexical_Structure.md)
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
|
||||
|
||||
> 2.2
|
||||
> 翻译:[175](https://github.com/Brian175),2016-04-09 校对:[SketchK](https://github.com/SketchK),2016-05-11
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [简单值(Simple Values)](#simple_values)
|
||||
@ -17,6 +20,7 @@
|
||||
- [对象和类(Objects and Classes)](#objects_and_classes)
|
||||
- [枚举和结构体(Enumerations and Structures)](#enumerations_and_structures)
|
||||
- [协议和扩展(Protocols and Extensions)](#protocols_and_extensions)
|
||||
- [错误处理(Error Handling)](#error_handling)
|
||||
- [泛型(Generics)](#generics)
|
||||
|
||||
通常来说,编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现:
|
||||
@ -30,8 +34,7 @@ print("Hello, world!")
|
||||
这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解。
|
||||
|
||||
> 注意:
|
||||
> 为了获得最好的体验,在 Xcode 当中使用代码预览功能。代码预览功能可以让你编辑代码并实时看到运行结果。
|
||||
> <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.playground.zip">下载Playground</a>
|
||||
> 在 Mac 上,下载 Playground 并双击文件在 Xcode 里打开:[https://developer.apple.com/go/?id=swift-tour](https://developer.apple.com/go/?id=swift-tour)
|
||||
|
||||
<a name="simple_values"></a>
|
||||
## 简单值
|
||||
@ -142,7 +145,13 @@ if let name = optionalName {
|
||||
> 练习:
|
||||
> 把`optionalName`改成`nil`,greeting会是什么?添加一个`else`语句,当`optionalName`是`nil`时给greeting赋一个不同的值。
|
||||
|
||||
如果变量的可选值是`nil`,条件会判断为`false`,大括号中的代码会被跳过。如果不是`nil`,会将值赋给`let`后面的常量,这样代码块中就可以使用这个值了。
|
||||
如果变量的可选值是`nil`,条件会判断为`false`,大括号中的代码会被跳过。如果不是`nil`,会将值赋给`let`后面的常量,这样代码块中就可以使用这个值了。
|
||||
另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。
|
||||
```swift
|
||||
let nickName: String? = nil
|
||||
let fullName: String = "John Appleseed"
|
||||
let informalGreeting = "Hi \(nickName ?? fullName)"
|
||||
```
|
||||
|
||||
`switch`支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
|
||||
|
||||
@ -188,7 +197,7 @@ print(largest)
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 添加另一个变量来记录现在和之前最大数字的类型。
|
||||
> 添加另一个变量来记录最大数字的种类(kind),同时仍然记录这个最大数字的值。
|
||||
|
||||
使用`while`来重复运行一段代码直到不满足条件。循环条件也可以在结尾,保证能至少循环一次。
|
||||
|
||||
@ -206,20 +215,14 @@ repeat {
|
||||
print(m)
|
||||
```
|
||||
|
||||
你可以在循环中使用`..<`来表示范围,也可以使用传统的写法,两者是等价的:
|
||||
你可以在循环中使用`..<`来表示范围。
|
||||
|
||||
```swift
|
||||
var firstForLoop = 0
|
||||
var total = 0
|
||||
for i in 0..<4 {
|
||||
firstForLoop += i
|
||||
total += i
|
||||
}
|
||||
print(firstForLoop)
|
||||
|
||||
var secondForLoop = 0
|
||||
for var i = 0; i < 4; ++i {
|
||||
secondForLoop += i
|
||||
}
|
||||
print(secondForLoop)
|
||||
print(total)
|
||||
```
|
||||
|
||||
使用`..<`创建的范围不包含上界,如果想包含的话需要使用`...`。
|
||||
@ -534,7 +537,7 @@ let aceRawValue = ace.rawValue
|
||||
> 练习:
|
||||
> 写一个函数,通过比较它们的原始值来比较两个`Rank`值。
|
||||
|
||||
在上面的例子中,枚举原始值的类型是`Int`,所以你只需要设置第一个原始值。剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`属性来访问一个枚举成员的原始值。
|
||||
默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,`Ace`被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`属性来访问一个枚举成员的原始值。
|
||||
|
||||
使用`init?(rawValue:)`初始化构造器在原始值和枚举值之间进行转换。
|
||||
|
||||
@ -544,7 +547,7 @@ if let convertedRank = Rank(rawValue: 3) {
|
||||
}
|
||||
```
|
||||
|
||||
枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,以防原始值没有意义,你不需要设置。
|
||||
枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。
|
||||
|
||||
```swift
|
||||
enum Suit {
|
||||
@ -595,24 +598,24 @@ let threeOfSpadesDescription = threeOfSpades.simpleDescription()
|
||||
```swift
|
||||
enum ServerResponse {
|
||||
case Result(String, String)
|
||||
case Error(String)
|
||||
case Failure(String)
|
||||
}
|
||||
|
||||
let success = ServerResponse.Result("6:00 am", "8:09 pm")
|
||||
let failure = ServerResponse.Error("Out of cheese.")
|
||||
let failure = ServerResponse.Failure("Out of cheese.")
|
||||
|
||||
switch success {
|
||||
case let .Result(sunrise, sunset):
|
||||
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
|
||||
case let .Error(error):
|
||||
let serverResponse = "Failure... \(error)"
|
||||
case let .Failure(message):
|
||||
print("Failure... \(message)")
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 给`ServerResponse`和`switch`添加第三种情况。
|
||||
|
||||
注意如何从`ServerResponse`中提取日升和日落时间并用得到的值用来和`switch`的情况作比较。
|
||||
注意日升和日落时间是如何从`ServerResponse`中提取到并与`switch`的`case`相匹配的。
|
||||
|
||||
<a name="protocols_and_extensions"></a>
|
||||
## 协议和扩展
|
||||
@ -683,6 +686,88 @@ print(protocolValue.simpleDescription)
|
||||
|
||||
即使`protocolValue`变量运行时的类型是`simpleClass`,编译器会把它的类型当做`ExampleProtocol`。这表示你不能调用类在它实现的协议之外实现的方法或者属性。
|
||||
|
||||
<a name="error_handling"></a>
|
||||
## 错误处理
|
||||
|
||||
使用采用`ErrorType`协议的类型来表示错误。
|
||||
|
||||
```swift
|
||||
enum PrinterError: ErrorType {
|
||||
case OutOfPaper
|
||||
case NoToner
|
||||
case OnFire
|
||||
}
|
||||
```
|
||||
|
||||
使用`throw`来抛出一个错误并使用`throws`来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
|
||||
|
||||
```swift
|
||||
func sendToPrinter(printerName: String) throws -> String {
|
||||
if printerName == "Never Has Toner" {
|
||||
throw PrinterError.NoToner
|
||||
}
|
||||
return "Job sent"
|
||||
}
|
||||
```
|
||||
|
||||
有多种方式可以用来进行错误处理。一种方式是使用`do-catch`。在`do`代码块中,使用`try`来标记可以抛出错误的代码。在`catch`代码块中,除非你另外命名,否则错误会自动命名为`error`。
|
||||
|
||||
```swift
|
||||
do {
|
||||
let printerResponse = try sendToPrinter("Bi Sheng")
|
||||
print(printerResponse)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 将 printer name 改为`"Never Has Toner"`使`sendToPrinter(_:)`函数抛出错误。
|
||||
|
||||
可以使用多个`catch`块来处理特定的错误。参照 switch 中的`case`风格来写`catch`。
|
||||
|
||||
```swift
|
||||
do {
|
||||
let printerResponse = try sendToPrinter("Gutenberg")
|
||||
print(printerResponse)
|
||||
} catch PrinterError.OnFire {
|
||||
print("I'll just put this over here, with the rest of the fire.")
|
||||
} catch let printerError as PrinterError {
|
||||
print("Printer error: \(printerError).")
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 在`do`代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个`catch`块进行接收?怎么使第二个和第三个`catch`进行接收呢?
|
||||
|
||||
另一种处理错误的方式使用`try?`将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为`nil`。否则的话,结果会是一个包含函数返回值的可选值。
|
||||
|
||||
```swift
|
||||
let printerSuccess = try? sendToPrinter("Mergenthaler")
|
||||
let printerFailure = try? sendToPrinter("Never Has Toner")
|
||||
```
|
||||
|
||||
使用`defer`代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用`defer`,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。
|
||||
|
||||
```swift
|
||||
var fridgeIsOpen = false
|
||||
let fridgeContent = ["milk", "eggs", "leftovers"]
|
||||
|
||||
func fridgeContains(itemName: String) -> Bool {
|
||||
fridgeIsOpen = true
|
||||
defer {
|
||||
fridgeIsOpen = false
|
||||
}
|
||||
|
||||
let result = fridgeContent.contains(itemName)
|
||||
return result
|
||||
}
|
||||
fridgeContains("banana")
|
||||
print(fridgeIsOpen)
|
||||
```
|
||||
|
||||
<a name="generics"></a>
|
||||
## 泛型
|
||||
|
||||
|
||||
@ -1,37 +1,44 @@
|
||||
# Swift 版本历史记录
|
||||
# Swift 文档修订历史
|
||||
|
||||
---
|
||||
|
||||
> 1.0
|
||||
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
|
||||
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
|
||||
[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 1.1
|
||||
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
|
||||
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
|
||||
[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 1.2
|
||||
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
|
||||
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
|
||||
[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 2.0
|
||||
> 翻译+校对:[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 2.1
|
||||
> 翻译+校对:[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[changkun](http://changkun.us/about/)
|
||||
|
||||
本页面根据 [Document Revision History](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/RevisionHistory.html) 进行适配更新。
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [XCode6.4 Beta Swift语法文档更新](#xcode6_4_Beta)
|
||||
- [XCode6.3正式版 Swift语法文档更新](#xcode6_3)
|
||||
- [XCode6.2正式版 Swift语法文档更新](#xcode6_2)
|
||||
- [XCode6.2 Beta3 Swift语法文档更新](#xcode6_2_Beta3)
|
||||
- [XCode6.2 Beta2 Swift语法文档更新](#xcode6_2_Beta2)
|
||||
- [XCode6.2 Beta1 Swift语法文档更新](#xcode6_2_Beta1)
|
||||
- [XCode6.1.1正式版 Swift语法文档更新](#xcode6_1_1)
|
||||
- [XCode6.1 Swift语法文档更新](#xcode6_1)
|
||||
- [XCode6.1 Beta2 Swift语法文档更新](#xcode6_1_Beta2)
|
||||
- [XCode6.1 Beta1 Swift语法文档更新](#xcode6_1_Beta1)
|
||||
- [XCode6 Beta7 Swift语法文档更新](#xcode6_beta7)
|
||||
- [XCode6 Beta6 Swift语法文档更新](#xcode6_beta6)
|
||||
- [XCode6 Beta5 Swift语法文档更新](#xcode6_beta5)
|
||||
- [XCode6 Beta4 Swift语法文档更新](#xcode6_beta4)
|
||||
- [XCode6 Beta3 Swift语法文档更新](#xcode6_beta3)
|
||||
- [XCode6 Beta2 Swift语法文档更新](#xcode6_beta2)
|
||||
- [XCode6 Beta1 Swift语法文档更新](#xcode6_beta1)
|
||||
- XCode6下载: [老码云盘下载](http://pan.baidu.com/disk/home#from=share_pan_logo&path=%252F%25E8%2580%2581%25E7%25A0%2581%25E4%25BA%2591%25E7%259B%2598-XCode6%252FXCode6-Beta5)
|
||||
- [Swift 2.2 更新](#swift_2_2)
|
||||
- [Swift 2.1 更新](#swift_2_1)
|
||||
- [Swift 2.0 更新](#swift_2_0)
|
||||
- [Swift 1.2 更新](#swift_1_2)
|
||||
- [Swift 1.1 更新](#swift_1_1)
|
||||
- [Swift 1.0 更新](#swift_1_0)
|
||||
|
||||
以下部分是针对XCode6每一次Beta版本直至正式版发布,Swift语法部分的更新归类
|
||||
|
||||
<a name="xcode6_4_Beta"></a>
|
||||
### XCode6.4 Beta中Swift语法更新
|
||||
|
||||
***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.4 Beta Release Note总结的更改说明:***
|
||||
<a name="swift_2_2"></a>
|
||||
### Swift 2.2 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
@ -42,10 +49,55 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2015-04-13</td>
|
||||
<td scope="row">2016-03-21</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
XCode6.4包含了对于构建和调试基于iOS8.4 App的支持
|
||||
更新至 Swift 2.2。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID539">编译配置语句</a>一节中关于如何根据 Swift 版本进行条件编译。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID400">显示成员表达式</a>一节中关于如何区分只有参数名不同的方法和构造器的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID547">选择器表达式</a>一节中针对 Objective-C 选择器的 <code>#selector</code> 语法。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189">关联类型</a>和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID374">协议关联类型</a>声明,使用 <code>associatedtype</code> 关键词修改了对于关联类型的讨论。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID224">可失败构造器</a>一节中关于当构造器在实例完全初始化之前返回 <code>nil</code>的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-ID70">比较运算符</a>一节中关于比较元组的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413">关键字和标点符号</a>一节中关于使用关键字作为外部参数名的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413">声明特性</a>一节中关于<code>@objc</code>特性的讨论,并指出枚举(Enumeration)和枚举用例(Enumaration Case)。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID418">操作符</a>一节中对于自定义运算符的讨论包含了<code>.</code>。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID531">重新抛出错误的函数和方法</a>一节中关于重新抛出错误函数不能直接抛出错误的笔记。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID262">属性观察器</a>一节中关于当作为 in-out 参数传递属性时,属性观察器的调用行为。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1">Swift 初见</a>一节中关于错误处理的内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中的图片用以更清楚的展示重新分配过程。
|
||||
</li>
|
||||
<li>
|
||||
删除了 C 语言风格的 <code>for</code> 循环,<code>++</code> 前缀和后缀运算符,以及<code>--</code> 前缀和后缀运算符。
|
||||
</li>
|
||||
<li>
|
||||
删除了对变量函数参数和柯里化函数的特殊语法的讨论。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
@ -53,9 +105,195 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="swift_2_1"></a>
|
||||
### Swift 2.1 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2015-10-20</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 2.1。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">字符串插值(String Interprolation)</a>和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID417">字符串字面量(String Literals)</a>小节,现在字符串插值可包含字符串字面量。
|
||||
</li>
|
||||
<li>
|
||||
增加了在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID546">非逃逸闭包(Nonescaping Closures)</a>一节中关于 <code>@noescape</code> 属性的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性(Declaration Attributes)</a>和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID539">编译配置语句(Build Configuration Statement)</a>小节中与 tvOS 相关的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545">In-Out 参数(In-Out Parameters)</a>小节中与 in-out 参数行为相关的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544">捕获列表(Capture Lists)</a>一节中关于指定闭包捕获列表被捕获时捕获值的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了通过<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-ID248">可选链式调用访问属性(Accessing Properties Through Optional Chaining)</a>一节,阐明了如何通过可选链式调用进行赋值。
|
||||
</li>
|
||||
<li>
|
||||
改进了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID543">自闭包(Autoclosure)</a>一节中对自闭包的讨论。
|
||||
</li>
|
||||
<li>
|
||||
在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1">Swift 初见(A Swift Tour)</a>一节中更新了一个使用<code>??</code>操作符的例子。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="swift_2_0"></a>
|
||||
### Swift 2.0 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2015-09-16</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 2.0。
|
||||
</li>
|
||||
<li>
|
||||
增加了对于错误处理相关内容,包括 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508">错误处理</a>一章、<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID533">Do 语句</a>、<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID518">Throw 语句</a>、<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID532">Defer 语句</a>以及<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID516">try 运算符</a> 的多个小节。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID509">表示并抛出错误</a>一节,现在所有类型均可遵循 <code>ErrorType</code> 协议。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID542">将错误转换成可选值</a>一节 <code>try?</code> 关键字的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145">枚举</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID536">递归枚举</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID351">声明</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID365">任意类型用例的枚举</a>一节中关于递归枚举的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120">控制流</a>一章中a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID523">检查 API 可用性</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID428">语句</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID522">可用性条件</a>一节中关于 API 可用性检查的内容。
|
||||
</li>
|
||||
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120">控制流</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525">早期退出</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID428">语句</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID524">guard语句</a>中关于新 <code>guard</code> 语句的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267">协议</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID521">协议扩展</a>一节中关于协议扩展的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID3">访问控制</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID519">单元测试 target 的访问级别</a>一节中关于单元测试的访问控制相关的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID419">模式</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID520">可选模式</a>一节中的新可选模式。
|
||||
</li>
|
||||
<li>
|
||||
更新了 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID126">Repeat-While</a> 一节中关于<code>repeat-while</code>循环的信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID285">字符串和字符</a>一章,现在<code>String</code>在 Swift 标准库中不再遵循<code>CollectionType</code>协议。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID314">打印常量和变量</a>一节中关于新 Swift 标准库中关于 <code>print(_:separator:terminator)</code> 的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145">枚举</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID535">原始值的隐式赋值</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID351">声明</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID366">包含原始值类型的枚举</a>一节中关于包含<code>String</code>原始值的枚举用例的行为。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID543">自闭包</a>一节中关于<code>@autoclosure</code>特性的相关信息,包括它的<code>@autoclosure(escaping)</code>形式。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节中关于<code>@avaliable</code>和<code>warn_unused_result</code>特性的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID350">类型特性</a>一节中关于<code>@convention</code>特性的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID333">可选绑定</a>一节中关于使用<code>where</code>子句进行多可选绑定的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">字符串字面量</a>一节中关于在编译时使用 <code>+</code> 运算符凭借字符串字面量的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html#//apple_ref/doc/uid/TP40014097-CH31-ID455">元类型</a>一节中关于元类型值的比较和使用它们通过构造器表达式构造实例。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID336">断言调试</a>一节中关于用户定义断言是被警用的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节中,对<code>@NSManaged</code>特性的讨论,现在这个特性可以被应用到一个确定实例方法。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-ID171">可变参数</a>一节,现在可变参数可以声明在函数参数列表的任意位置中。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID229">重写可失败构造器</a>一节中,关于非可失败构造器相当于一个可失败构造器通过父类构造器的结果进行强制拆包的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID365">任意类型用例的枚举</a>一节中关于枚举用例作为函数的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID399">构造器表达式</a>一节中关于显式引用一个构造器的内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID538">编译控制语句</a>一节中关于编译信息以及行控制语句的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html#//apple_ref/doc/uid/TP40014097-CH31-ID455">元类型</a>一节中关于如何从元类型值中构造类实例。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中关于弱引用作为缓存的显存的不足。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">类型特性</a>一节,提到了存储型特性其实是懒加载。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID264">捕获类型</a>一节,阐明了变量和常量在闭包中如何被捕获。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节用以描述如何在类中使用<code>@objc</code>关键字。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID512">错误处理</a>一节中关于执行<code>throw</code>语句的性能的讨论。增加了 Do 语句一节中相似的信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID533">类型特性</a>一节中关于类、结构体和枚举的存储型和计算型特性的信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID441">Break 语句</a>一节中关于带标签的 break 语句。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID262">属性观察器</a>一节,阐明了<code>willSet</code>和<code>didSet</code>观察器的行为。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID5">访问级</a>一节中关于<code>private</code>作用域访问的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中关于若应用在垃圾回收系统和 ARC 之间的区别。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID295">字符串字面量中特殊字符</a>一节中对 Unicode 标量更精确的定义。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<a name="swift_1_2"></a>
|
||||
### Swift 1.2 更新
|
||||
<a name="xcode6_3"></a>
|
||||
### XCode6.3中Swift语法更新
|
||||
***注意:苹果此时发布了统一的版本XCode6.3,其中将以前的XCode6.3 Beta系列版本合并, 而XCode6.3共计发布了4次Beta版本,[老码团队](http://weibo.com/u/5241713117)通过Release Note总结的详细更改说明请参看:[Swift语法更新记录表格](https://docs.baihui.com/sheet/published.do?rid=mxpis6d36a8b7bc254c36ae2a808c64c2361e)***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
@ -68,6 +306,9 @@
|
||||
<tr>
|
||||
<td scope="row">2015-4-8</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 1.2。
|
||||
</li>
|
||||
<li>
|
||||
Swift现在自身提供了一个<code>Set</code>集合类型,更多信息请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-ID484">集合</a>
|
||||
|
||||
@ -85,7 +326,7 @@
|
||||
增加了一个新的指导章节,它是关于<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID495">字符串索引</a>的
|
||||
</li>
|
||||
<li>
|
||||
从<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID37">溢出运算符</a>中移除了溢出除运算符和求余溢出运算符
|
||||
从<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID37">溢出运算符</a>中移除了溢出除运算符(&/)和求余溢出运算符(&%)。
|
||||
</li>
|
||||
<li>
|
||||
更新了常量和常量属性在声明和构造时的规则,更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID355">常量声明</a>
|
||||
@ -111,29 +352,7 @@
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID418">运算符</a>章节来明确指明一些例子来说明自定义运算符所支持的特性,如数学运算符,各种符号,Unicode符号块等
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_2"></a>
|
||||
### XCode6.2正式版中Swift语法更新
|
||||
|
||||
***注意:苹果此时发布了统一的版本XCode6.2,其中将以前的XCode6.2 Beta系列版本合并***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2015-02-09</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
<li>
|
||||
在函数作用域中的常量声明时可以不被初始化,它必须在第一次使用前被赋值。更多的信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID355">常量声明</a>
|
||||
</li>
|
||||
<li>
|
||||
@ -157,139 +376,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_2_Beta3"></a>
|
||||
### XCode6.2 Beta3中Swift语法更新
|
||||
|
||||
***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.2 Beta3 Release Note总结的更改说明:***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-12-19</td>
|
||||
<td>
|
||||
<ul class="list-bullet">
|
||||
<li>
|
||||
在对Watch App做消息通知模拟调试时,第一个payload.apns文件将会被默认选择
|
||||
</li>
|
||||
<li>
|
||||
在为Watch App使用asset catalog时,38mm和42mm尺寸的图片就会被使用
|
||||
</li>
|
||||
<li>
|
||||
在做Watch App开发时,<code>@IBAction</code>属性支持<code>WKInterfaceSwitch</code>和<code>WKInterfaceSlider</code> Swift类型了
|
||||
</li>
|
||||
<li>
|
||||
现在可以通过Device窗口安装,删除和访问App容器中的数据了。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_2_Beta2"></a>
|
||||
### XCode6.2 Beta2中Swift语法更新
|
||||
|
||||
***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.2 Beta2 Release Note总结的更改说明:***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-12-10</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
现在在Interface Builder中可以针对特定的Device设备自定义Watch应用的Layout布局了
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_2_Beta1"></a>
|
||||
### XCode6.2 Beta1中Swift语法更新
|
||||
|
||||
***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.2 Beta1 Release Note总结的更改说明:***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-11-28</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
XCode6.2包含了iOS8.2 SDK,该SDK中包含WatchKit用来开发Apple Watch应用。
|
||||
</li>
|
||||
<li>
|
||||
在工具集中增加了对WatchKit的支持:
|
||||
1)UI设计工具增加了Apple Watch应用的界面组件,通知和小部件。
|
||||
2)增加了调试和性能统计功能
|
||||
3)增加Apple Watch应用的模拟器帮助调试应用功能
|
||||
</li>
|
||||
<li>
|
||||
为了使Apple Watch应用能够正常工作,一些具体的参数必须设置:
|
||||
1)WatchKit中扩展配置文件Info.plist中的<code>NSExtensionAttributes</code>配置项WKAppBundleIdentifier必须和WatchKit App中的通用配置文件中的属性<code>CFBundleIdentifier</code>项目保持一致。2)WatchKit中的<code>CFBundleIdentifier</code>配置项必须和<code>WKCompanionAppBundleIdentifier</code>中的配置项保持一致
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_1_1"></a>
|
||||
### XCode6.1.1中Swift语法更新
|
||||
|
||||
***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.1.1 Release Note总结的更改说明:***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-12-2</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
在SourceKit中一些导致Crash的常见问题被修复,比如名字冲突和遗留废弃数据的问题等。
|
||||
</li>
|
||||
<li>
|
||||
把纯正的Swift类对象实例赋值给AnyObject量不会再Crash了。
|
||||
</li>
|
||||
<li>
|
||||
在泛型使用场景下,遵循了协议类要求的构造器方法或者类型方法可以直接调用继承类中的方法了。
|
||||
</li>
|
||||
<li>
|
||||
修正了InterfaceBuild中如果图片名字含有“/”时,会在OSX10.10上Crash或者无法打开的问题
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_1"></a>
|
||||
### XCode6.1中Swift语法更新
|
||||
|
||||
***注意:苹果此时发布了统一的版本XCode6.1,其中将以前的XCode6.0.1和XCode6.1 Beta系列版本合并***
|
||||
<a name="swift_1_1"></a>
|
||||
### Swift 1.1 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
@ -303,112 +392,32 @@
|
||||
<td scope="row">2014-10-16</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
增加了一个完整的关于<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html">失败构造器(Failable Initializers)</a>的指南文档
|
||||
更新至 Swift 1.1。
|
||||
</li>
|
||||
<li>
|
||||
增加了一个关于协议的<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html">失败构造器需求(Failable Initializer Requirements)</a>的描述
|
||||
增加了关于<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">失败构造器(Failable Initializers)</a>的完整章节。
|
||||
</li>
|
||||
<li>
|
||||
`Any`类型的常量或变量现在可以包含一个函数实例了。同时更新了<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html">`Any`</a>章节的案例用来演示如何在swith语句中检查和转换一个函数类型。
|
||||
增加了协议中关于失败构造器要求的描述。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_1_Beta2"></a>
|
||||
### XCode6.1 Beta2中Swift语法更新
|
||||
|
||||
***注意:苹果此时发布了XCode6.0.1版本(也称为XCode6正式版),此版本用于iOS的开发,同时也发布子版本XCode6.1 Beta2,此版本为OSX开发做准备,以下所述的更改仅对XCode6.1 Beta2有效***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-09-15</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
带有原始值的枚举类型增加了一个<code>rawValue</code>属性替代<code>toRaw()</code>方法,同时使用了一个以<code>rawValue</code>为参数的失败构造器来替代<code>fromRaw()</code>方法。更多的信息,请看<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html">原始值(Raw Values)</a>和<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">带原始值的枚举类型(Enumerations with Cases of a Raw-Value Type)</a>部分
|
||||
常量和变量的 <code>Any</code> 类型现可以包含函数实例。更新了关于 <code>Any</code> 相关的示例来展示如何在 <code>switch</code> 语句中如何检查并转换到一个函数类型。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_1_Beta1"></a>
|
||||
### XCode6.1 Beta1中Swift语法更新
|
||||
|
||||
***注意:苹果此时发布了XCode6 GM版本,此版本用于iOS的开发,同时也发布子版本XCode6.1 Beta1,此版本为OSX开发做准备,以下所述的更改仅对XCode6.1 Beta1有效***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-09-09</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
增加了一个新的关于<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">失败构造器(Failable Initializers)</a>的参考章节,失败构造器可以触发失败的构造过程
|
||||
带有原始值的枚举类型增加了一个<code>rawValue</code>属性替代<code>toRaw()</code>方法,同时使用了一个以<code>rawValue</code>为参数的失败构造器来替代<code>fromRaw()</code>方法。更多的信息,请看<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html">原始值(Raw Values)</a>和<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">带原始值的枚举类型(Enumerations with Cases of a Raw-Value Type)</a>部分。
|
||||
</li>
|
||||
<li>
|
||||
自定义运算符现在可以包含`?`字符,更新的<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html">运算符(Operators)</a>章节描述了改进后的规则,并且从<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html">自定义运算符(Custom Operators)</a>章节删除了重复的运算符有效字符集合
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_beta7"></a>
|
||||
### XCode6 Beta7中Swift语法更新
|
||||
|
||||
***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode Beta7 Release Note总结的更改说明:***
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-09-03</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
实现了内部库的修改和适配,主要包括如下:
|
||||
1)大量内部类或者函数遵循Optional类型和协议
|
||||
2)移除大部分函数返回类型隐式解封可选类型的使用
|
||||
|
||||
</li>
|
||||
<li>
|
||||
对于泛型的类库函数或接口统一从<code>T!</code>更换为<code>T?</code>或<code>T</code>,这样使得语法更加严谨,明确了可能返回为空和不为空的情况
|
||||
</li>
|
||||
<li>
|
||||
字符类型不能使用+运算法链接,可以以<code>String(C1)+String(2)</code> 的方式实现字符间链接
|
||||
</li>
|
||||
<li>
|
||||
重写了<code>Sort</code>函数,解决了栈溢出的问题
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_beta6"></a>
|
||||
### XCode6 Beta6中Swift语法更新
|
||||
<a name="swift_1_0"></a>
|
||||
### Swift 1.0 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
@ -421,6 +430,9 @@
|
||||
<tr>
|
||||
<td scope="row">2014-08-18</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
发布新的文档用以详述 Swift 1.0,苹果公司针对iOS和OS X应用的全新开发语言。
|
||||
</li>
|
||||
<li>
|
||||
在章节协议中,增加新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_397">对构造器的规定(Initializer Requirements)</a>
|
||||
</li>
|
||||
@ -436,26 +448,6 @@
|
||||
<li>
|
||||
在<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-XID_516">声明特性(Declaration Attributes)</a>章节增加了关于<code>availability</code>特性的一些信息
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_beta5"></a>
|
||||
### XCode6 Beta5中Swift语法更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-08-04</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_478">可选类型(Optionals)</a> 若有值时,不再隐式的转换为 <code>true</code>,同样,若无值时,也不再隐式的转换为 <code>false</code>, 这是为了避免在判别 optional <code>Bool</code> 的值时产生困惑。 替代的方案是,用<code>==</code> 或 <code>!=</code> 运算符显式地去判断Optinal是否是 <code>nil</code>,以确认其是否包含值。
|
||||
</li>
|
||||
@ -473,7 +465,7 @@
|
||||
在章节可选链中,增加一个新的小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_364">访问可选类型的下标脚注(Accessing Subscripts of Optional Type)</a>
|
||||
</li>
|
||||
<li>
|
||||
更新章节 <a href="CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_176" data-id="//apple_ref/doc/uid/TP40014097-CH8-XID_176">访问和修改数组(Accessing and Modifying an Array)</a> 以标示:从该版本起,不能再通过<code>+=</code> 运算符给一个数组添加一个新的项。. 对应的替代方案是, 使<code>append</code> 方法, 或者通过<code>+=</code>运算符来添加一个<b>只有一个项的数组</b>(single-item Array).</li>
|
||||
更新章节 <a href="../chapter2/04_Collection_Types.md#访问和修改数组" data-id="访问和修改数组">访问和修改数组(Accessing and Modifying an Array)</a> 以标示:从该版本起,不能再通过<code>+=</code> 运算符给一个数组添加一个新的项。. 对应的替代方案是, 使<code>append</code> 方法, 或者通过<code>+=</code>运算符来添加一个<b>只有一个项的数组</b>(single-item Array).</li>
|
||||
<li>
|
||||
添加了一个提示:在 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_126">范围运算符(Range Operators)</a>中,比如, <code>a...b</code> 和 <code>a..<b</code> ,起始值<code>a</code>不能大于结束值<code>b</code>.
|
||||
</li>
|
||||
@ -510,26 +502,6 @@
|
||||
<li>
|
||||
为章节<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_597">Curried Functions</a>添加了更多的信息.
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_beta4"></a>
|
||||
#### XCode6 Beta4中Swift语法更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-07-21</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-XID_29">权限控制(Access Control)</a>.
|
||||
</li>
|
||||
@ -572,26 +544,6 @@
|
||||
<li>
|
||||
<code>nil</code> 和布尔运算中的 <code>true</code> 和 <code>false</code> 现在被定义为字面量<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_886">Literals</a>.
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_beta3"></a>
|
||||
#### XCode6 Beta3中Swift语法更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-07-7</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
Swift 中的数组 (<code>Array</code>) 类型从现在起具备了完整的值语义。具体信息被更新到 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_170">集合的可变性(Mutability of Collections)</a> 和 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_172">数组(Arrays)</a> 两小节,以反映这个新的变化. 此外,还解释了如何 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-XID_150">给Strings, Arrays和Dictionaries进行赋值和拷贝 (Assignment and Copy Behavior for Strings, Arrays, and Dictionaries)</a>.
|
||||
</li>
|
||||
@ -616,52 +568,6 @@
|
||||
<li>
|
||||
添加一个例子 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_285">扩展一个泛型(Extending a Generic Type)</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_beta2"></a>
|
||||
#### XCode6 Beta2中Swift语法更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-07-7</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
发布新的文档用以详述Swift - 苹果公司针对iOS和OS X应用的全新开发语言
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="xcode6_beta1"></a>
|
||||
#### XCode6 Beta1中Swift语法更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-06-3</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
苹果全球开发者大会WWDC2014召开,发布了苹果最新的开发语言Swift,并释放出XCode6 Beta1版本
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -6,8 +6,19 @@
|
||||
> 校对:[lslxdx](https://github.com/lslxdx)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
|
||||
> 定稿:[shanks](http://codebuild.me)
|
||||
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Prayer](https://github.com/futantan)
|
||||
> 校对:[shanks](http://codebuild.me),[overtrue](https://github.com/overtrue)
|
||||
>
|
||||
> 2.2
|
||||
> 校对:[SketchK](https://github.com/SketchK)
|
||||
>
|
||||
> 3.0
|
||||
> 校对:[CMB](https://github.com/chenmingbiao)
|
||||
>
|
||||
> 版本日期:2016-09-13
|
||||
|
||||
本页包含内容:
|
||||
|
||||
@ -39,27 +50,27 @@
|
||||
- [错误处理](#error_handling)
|
||||
- [断言](#assertions)
|
||||
|
||||
Swift 是一门开发 iOS, OS X 和 watchOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。
|
||||
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.html)。
|
||||
Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int`表示整型值; `Double` 和 `Float` 表示浮点型值; `Bool` 是布尔型值;`String` 是文本型数据。 Swift 还提供了三个基本的集合类型,`Array` ,`Set` 和 `Dictionary` ,详见[集合类型](04_Collection_Types.html)。
|
||||
|
||||
就像 C 语言一样,Swift 使用变量来进行存储并通过变量名来关联值。在 Swift 中,广泛的使用着值不可变的变量,它们就是常量,而且比 C 语言的常量更强大。在 Swift 中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。
|
||||
|
||||
除了我们熟悉的类型,Swift 还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。
|
||||
|
||||
Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示“那儿有一个值,并且它等于 x ”或者“那儿没有值”。可选有点像在 Objective-C 中使用`nil`,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的`nil`指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。
|
||||
Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示 “那儿有一个值,并且它等于 x ” 或者 “那儿没有值” 。可选有点像在 Objective-C 中使用 `nil` ,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 `nil` 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。
|
||||
|
||||
Swift 是一门类型安全的语言,可选类型就是一个很好的例子。Swift 可以让你清楚地知道值的类型。如果你的代码期望得到一个`String`,类型安全会阻止你不小心传入一个`Int`。你可以在开发阶段尽早发现并修正错误。
|
||||
Swift 是一门类型安全的语言,可选类型就是一个很好的例子。Swift 可以让你清楚地知道值的类型。如果你的代码期望得到一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码期望得到一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。你可以在开发阶段尽早发现并修正错误。
|
||||
|
||||
<a name="constants_and_variables"></a>
|
||||
## 常量和变量
|
||||
|
||||
常量和变量把一个名字(比如`maximumNumberOfLoginAttempts`或者`welcomeMessage`)和一个指定类型的值(比如数字`10`或者字符串`"Hello"`)关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。
|
||||
常量和变量把一个名字(比如 `maximumNumberOfLoginAttempts` 或者 `welcomeMessage` )和一个指定类型的值(比如数字 `10` 或者字符串 `"Hello"` )关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。
|
||||
|
||||
<a name="declaring"></a>
|
||||
### 声明常量和变量
|
||||
|
||||
常量和变量必须在使用前声明,用`let`来声明常量,用`var`来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数:
|
||||
常量和变量必须在使用前声明,用 `let` 来声明常量,用 `var` 来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数:
|
||||
|
||||
```swift
|
||||
let maximumNumberOfLoginAttempts = 10
|
||||
@ -68,7 +79,7 @@ var currentLoginAttempt = 0
|
||||
|
||||
这两行代码可以被理解为:
|
||||
|
||||
“声明一个名字是`maximumNumberOfLoginAttempts`的新常量,并给它一个值`10`。然后,声明一个名字是`currentLoginAttempt`的变量并将它的值初始化为`0`。”
|
||||
“声明一个名字是 `maximumNumberOfLoginAttempts` 的新常量,并给它一个值 `10` 。然后,声明一个名字是 `currentLoginAttempt` 的变量并将它的值初始化为 `0` 。”
|
||||
|
||||
在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。
|
||||
|
||||
@ -78,15 +89,15 @@ var currentLoginAttempt = 0
|
||||
var x = 0.0, y = 0.0, z = 0.0
|
||||
```
|
||||
|
||||
>注意:
|
||||
如果你的代码中有不需要改变的值,请使用`let`关键字将它声明为常量。只将需要改变的值声明为变量。
|
||||
> 注意:
|
||||
> 如果你的代码中有不需要改变的值,请使用 `let` 关键字将它声明为常量。只将需要改变的值声明为变量。
|
||||
|
||||
<a name="type_annotations"></a>
|
||||
### 类型标注
|
||||
|
||||
当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
|
||||
|
||||
这个例子给`welcomeMessage`变量添加了类型标注,表示这个变量可以存储`String`类型的值:
|
||||
这个例子给 `welcomeMessage` 变量添加了类型标注,表示这个变量可以存储 `String` 类型的值:
|
||||
|
||||
```swift
|
||||
var welcomeMessage: String
|
||||
@ -94,11 +105,11 @@ var welcomeMessage: String
|
||||
|
||||
声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:
|
||||
|
||||
“声明一个类型为`String`,名字为`welcomeMessage`的变量。”
|
||||
“声明一个类型为 `String` ,名字为 `welcomeMessage` 的变量。”
|
||||
|
||||
“类型为`String`”的意思是“可以存储任意`String`类型的值。”
|
||||
“类型为 `String` ”的意思是“可以存储任意 `String` 类型的值。”
|
||||
|
||||
`welcomeMessage`变量现在可以被设置成任意字符串:
|
||||
`welcomeMessage` 变量现在可以被设置成任意字符串:
|
||||
|
||||
```swift
|
||||
welcomeMessage = "Hello"
|
||||
@ -111,7 +122,7 @@ var red, green, blue: Double
|
||||
```
|
||||
|
||||
> 注意:
|
||||
一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type_safety_and_type_inference)。在上面的例子中,没有给`welcomeMessage`赋初始值,所以变量`welcomeMessage`的类型是通过一个类型标注指定的,而不是通过初始值推断的。
|
||||
> 一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type_safety_and_type_inference)。在上面的例子中,没有给 `welcomeMessage` 赋初始值,所以变量 `welcomeMessage` 的类型是通过一个类型标注指定的,而不是通过初始值推断的。
|
||||
|
||||
<a name="naming"></a>
|
||||
### 常量和变量的命名
|
||||
@ -129,7 +140,7 @@ let 🐶🐮 = "dogcow"
|
||||
一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。
|
||||
|
||||
> 注意:
|
||||
如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
|
||||
> 如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
|
||||
|
||||
你可以更改现有的变量值为其他同类型的值,在下面的例子中,`friendlyWelcome`的值从`"Hello!"`改为了`"Bonjour!"`:
|
||||
|
||||
@ -157,7 +168,7 @@ print(friendlyWelcome)
|
||||
// 输出 "Bonjour!"
|
||||
```
|
||||
|
||||
`print(_:separator:terminator:)`是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)`将会输出内容到“console”面板上。`separator`和`terminator`参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给`terminator`参数--例如,`print(someValue, terminator:"")`。关于参数默认值的更多信息,请参考[默认参数值](./06_Functions.html#default_parameter_values)。
|
||||
`print(_:separator:terminator:)` 是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)` 将会输出内容到“console”面板上。`separator` 和 `terminator` 参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 `terminator` 参数--例如,`print(someValue, terminator:"")` 。关于参数默认值的更多信息,请参考[默认参数值](./06_Functions.html#default_parameter_values)。
|
||||
|
||||
Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
|
||||
|
||||
@ -208,38 +219,39 @@ let cat = "🐱"; print(cat)
|
||||
<a name="integers"></a>
|
||||
## 整数
|
||||
|
||||
整数就是没有小数部分的数字,比如`42`和`-23`。整数可以是`有符号`(正、负、零)或者`无符号`(正、零)。
|
||||
整数就是没有小数部分的数字,比如 `42` 和 `-23` 。整数可以是 `有符号`(正、负、零)或者 `无符号`(正、零)。
|
||||
|
||||
Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是`UInt8`,32位有符号整数类型是`Int32`。就像 Swift 的其他类型一样,整数类型采用大写命名法。
|
||||
Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是`UInt8`,32位有符号整数类型是 `Int32` 。就像 Swift 的其他类型一样,整数类型采用大写命名法。
|
||||
|
||||
<a name="integer_bounds"></a>
|
||||
### 整数范围
|
||||
|
||||
你可以访问不同整数类型的`min`和`max`属性来获取对应类型的最小值和最大值:
|
||||
你可以访问不同整数类型的 `min` 和 `max` 属性来获取对应类型的最小值和最大值:
|
||||
|
||||
```swift
|
||||
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
|
||||
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
|
||||
```
|
||||
`min`和`max`所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。
|
||||
```
|
||||
|
||||
`min` 和 `max` 所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。
|
||||
|
||||
<a name="Int"></a>
|
||||
### Int
|
||||
|
||||
一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型`Int`,长度与当前平台的原生字长相同:
|
||||
|
||||
* 在32位平台上,`Int`和`Int32`长度相同。
|
||||
* 在64位平台上,`Int`和`Int64`长度相同。
|
||||
* 在32位平台上,`Int` 和 `Int32` 长度相同。
|
||||
* 在64位平台上,`Int` 和 `Int64` 长度相同。
|
||||
|
||||
除非你需要特定长度的整数,一般来说使用`Int`就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int`可以存储的整数范围也可以达到`-2,147,483,648`~`2,147,483,647`,大多数时候这已经足够大了。
|
||||
除非你需要特定长度的整数,一般来说使用 `Int` 就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int` 可以存储的整数范围也可以达到 `-2,147,483,648` ~ `2,147,483,647` ,大多数时候这已经足够大了。
|
||||
|
||||
<a name="UInt"></a>
|
||||
### UInt
|
||||
|
||||
Swift 也提供了一个特殊的无符号类型`UInt`,长度与当前平台的原生字长相同:
|
||||
Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台的原生字长相同:
|
||||
|
||||
* 在32位平台上,`UInt`和`UInt32`长度相同。
|
||||
* 在64位平台上,`UInt`和`UInt64`长度相同。
|
||||
* 在32位平台上,`UInt` 和 `UInt32` 长度相同。
|
||||
* 在64位平台上,`UInt` 和 `UInt64` 长度相同。
|
||||
|
||||
> 注意:
|
||||
尽量不要使用`UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用`Int`,即使你要存储的值已知是非负的。统一使用`Int`可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考[类型安全和类型推断](#type_safety_and_type_inference)。
|
||||
@ -247,15 +259,15 @@ Swift 也提供了一个特殊的无符号类型`UInt`,长度与当前平台
|
||||
<a name="floating-point_numbers"></a>
|
||||
## 浮点数
|
||||
|
||||
浮点数是有小数部分的数字,比如`3.14159`,`0.1`和`-273.15`。
|
||||
浮点数是有小数部分的数字,比如 `3.14159` ,`0.1` 和 `-273.15`。
|
||||
|
||||
浮点类型比整数类型表示的范围更大,可以存储比`Int`类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
|
||||
浮点类型比整数类型表示的范围更大,可以存储比 `Int` 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
|
||||
|
||||
* `Double`表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
|
||||
* `Float`表示32位浮点数。精度要求不高的话可以使用此类型。
|
||||
|
||||
> 注意:
|
||||
`Double`精确度很高,至少有15位数字,而`Float`最少只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。
|
||||
`Double`精确度很高,至少有15位数字,而`Float`只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 `Double`。
|
||||
|
||||
<a name="type_safety_and_type_inference"></a>
|
||||
## 类型安全和类型推断
|
||||
@ -268,32 +280,32 @@ Swift 是一个*类型安全(type safe)*的语言。类型安全的语言可
|
||||
|
||||
因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
|
||||
|
||||
当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如`42`和`3.14159`。)
|
||||
当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 `42` 和 `3.14159` 。)
|
||||
|
||||
例如,如果你给一个新常量赋值`42`并且没有标明类型,Swift 可以推断出常量类型是`Int`,因为你给它赋的初始值看起来像一个整数:
|
||||
例如,如果你给一个新常量赋值 `42` 并且没有标明类型,Swift 可以推断出常量类型是 `Int` ,因为你给它赋的初始值看起来像一个整数:
|
||||
|
||||
```swift
|
||||
let meaningOfLife = 42
|
||||
// meaningOfLife 会被推测为 Int 类型
|
||||
```
|
||||
|
||||
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是`Double`:
|
||||
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 `Double`:
|
||||
|
||||
```swift
|
||||
let pi = 3.14159
|
||||
// pi 会被推测为 Double 类型
|
||||
```
|
||||
|
||||
当推断浮点数的类型时,Swift 总是会选择`Double`而不是`Float`。
|
||||
当推断浮点数的类型时,Swift 总是会选择 `Double` 而不是`Float`。
|
||||
|
||||
如果表达式中同时出现了整数和浮点数,会被推断为`Double`类型:
|
||||
如果表达式中同时出现了整数和浮点数,会被推断为 `Double` 类型:
|
||||
|
||||
```swift
|
||||
let anotherPi = 3 + 0.14159
|
||||
// anotherPi 会被推测为 Double 类型
|
||||
```
|
||||
|
||||
原始值`3`没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为`Double`类型。
|
||||
原始值 `3` 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 `Double` 类型。
|
||||
|
||||
<a name="numeric_literals"></a>
|
||||
## 数值型字面量
|
||||
@ -314,13 +326,15 @@ let octalInteger = 0o21 // 八进制的17
|
||||
let hexadecimalInteger = 0x11 // 十六进制的17
|
||||
```
|
||||
|
||||
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是`0x`)。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。浮点字面量还有一个可选的指数(exponent,在十进制浮点数中通过大写或者小写的`e`来指定,在十六进制浮点数中通过大写或者小写的`p`来指定。
|
||||
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 `0x` )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 `e` 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 `p` 来指定。
|
||||
|
||||
如果一个十进制数的指数为 `exp`,那这个数相当于基数和10^exp的乘积:
|
||||
|
||||
如果一个十进制数的指数为`exp`,那这个数相当于基数和10^exp的乘积:
|
||||
* `1.25e2` 表示 1.25 × 10^2,等于 `125.0`。
|
||||
* `1.25e-2` 表示 1.25 × 10^-2,等于 `0.0125`。
|
||||
|
||||
如果一个十六进制数的指数为`exp`,那这个数相当于基数和2^exp的乘积:
|
||||
如果一个十六进制数的指数为`exp`,那这个数相当于基数和2^exp的乘积:
|
||||
|
||||
* `0xFp2` 表示 15 × 2^2,等于 `60.0`。
|
||||
* `0xFp-2` 表示 15 × 2^-2,等于 `3.75`。
|
||||
|
||||
@ -369,9 +383,9 @@ let one: UInt8 = 1
|
||||
let twoThousandAndOne = twoThousand + UInt16(one)
|
||||
```
|
||||
|
||||
现在两个数字的类型都是`UInt16`,可以进行相加。目标常量`twoThousandAndOne`的类型被推断为`UInt16`,因为它是两个`UInt16`值的和。
|
||||
现在两个数字的类型都是 `UInt16`,可以进行相加。目标常量 `twoThousandAndOne` 的类型被推断为 `UInt16`,因为它是两个 `UInt16` 值的和。
|
||||
|
||||
`SomeType(ofInitialValue)`是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16`有一个构造器,可以接受一个`UInt8`类型的值,所以这个构造器可以用现有的`UInt8`来创建一个新的`UInt16`。注意,你并不能传入任意类型的值,只能传入`UInt16`内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考[扩展](./20_Extensions.html)。
|
||||
`SomeType(ofInitialValue)` 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16` 有一个构造器,可以接受一个`UInt8`类型的值,所以这个构造器可以用现有的 `UInt8` 来创建一个新的 `UInt16`。注意,你并不能传入任意类型的值,只能传入 `UInt16` 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考[扩展](./20_Extensions.html)。
|
||||
|
||||
<a name="integer_and_floating_point_conversion"></a>
|
||||
### 整数和浮点数转换
|
||||
@ -385,16 +399,16 @@ let pi = Double(three) + pointOneFourOneFiveNine
|
||||
// pi 等于 3.14159,所以被推测为 Double 类型
|
||||
```
|
||||
|
||||
这个例子中,常量`three`的值被用来创建一个`Double`类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。
|
||||
这个例子中,常量 `three` 的值被用来创建一个 `Double` 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。
|
||||
|
||||
浮点数到整数的反向转换同样行,整数类型可以用`Double`或者`Float`类型来初始化:
|
||||
浮点数到整数的反向转换同样行,整数类型可以用 `Double` 或者 `Float` 类型来初始化:
|
||||
|
||||
```swift
|
||||
let integerPi = Int(pi)
|
||||
// integerPi 等于 3,所以被推测为 Int 类型
|
||||
```
|
||||
|
||||
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说`4.75`会变成`4`,`-3.9`会变成`-3`。
|
||||
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 `4.75` 会变成 `4`,`-3.9` 会变成 `-3`。
|
||||
|
||||
> 注意:
|
||||
结合数字类常量和变量不同于结合数字类字面量。字面量`3`可以直接和字面量`0.14159`相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
|
||||
@ -422,16 +436,16 @@ var maxAmplitudeFound = AudioSample.min
|
||||
<a name="booleans"></a>
|
||||
## 布尔值
|
||||
|
||||
Swift 有一个基本的布尔(Boolean)类型,叫做`Bool`。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true`和`false`:
|
||||
Swift 有一个基本的布尔(Boolean)类型,叫做`Bool`。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true` 和 `false`:
|
||||
|
||||
```swift
|
||||
let orangesAreOrange = true
|
||||
let turnipsAreDelicious = false
|
||||
```
|
||||
|
||||
`orangesAreOrange`和`turnipsAreDelicious`的类型会被推断为`Bool`,因为它们的初值是布尔字面量。就像之前提到的`Int`和`Double`一样,如果你创建变量的时候给它们赋值`true`或者`false`,那你不需要将常量或者变量声明为`Bool`类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。
|
||||
`orangesAreOrange` 和 `turnipsAreDelicious` 的类型会被推断为 `Bool`,因为它们的初值是布尔字面量。就像之前提到的 `Int` 和 `Double` 一样,如果你创建变量的时候给它们赋值 `true` 或者 `false`,那你不需要将常量或者变量声明为 `Bool` 类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。
|
||||
|
||||
当你编写条件语句比如`if`语句的时候,布尔值非常有用:
|
||||
当你编写条件语句比如 `if` 语句的时候,布尔值非常有用:
|
||||
|
||||
```swift
|
||||
if turnipsAreDelicious {
|
||||
@ -444,7 +458,7 @@ if turnipsAreDelicious {
|
||||
|
||||
条件语句,例如`if`,请参考[控制流](./05_Control_Flow.html)。
|
||||
|
||||
如果你在需要使用`Bool`类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:
|
||||
如果你在需要使用 `Bool` 类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:
|
||||
|
||||
```swift
|
||||
let i = 1
|
||||
@ -462,7 +476,7 @@ if i == 1 {
|
||||
}
|
||||
```
|
||||
|
||||
`i == 1`的比较结果是`Bool`类型,所以第二个例子可以通过类型检查。类似`i == 1`这样的比较,请参考[基本操作符](./05_Control_Flow.html)。
|
||||
`i == 1` 的比较结果是 `Bool` 类型,所以第二个例子可以通过类型检查。类似 `i == 1` 这样的比较,请参考[基本操作符](./05_Control_Flow.html)。
|
||||
|
||||
和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。
|
||||
|
||||
@ -471,16 +485,16 @@ if i == 1 {
|
||||
|
||||
*元组(tuples)*把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
|
||||
|
||||
下面这个例子中,`(404, "Not Found")`是一个描述 *HTTP 状态码(HTTP status code)*的元组。HTTP 状态码是当你请求网页的时候 web 服务器返回的一个特殊值。如果你请求的网页不存在就会返回一个`404 Not Found`状态码。
|
||||
下面这个例子中,`(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)`的元组”。
|
||||
`(404, "Not Found")` 元组把一个 `Int` 值和一个 `String` 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 `(Int, String)` 的元组”。
|
||||
|
||||
你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为`(Int, Int, Int)`或者`(String, Bool)`或者其他任何你想要的组合的元组。
|
||||
你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为 `(Int, Int, Int)` 或者 `(String, Bool)` 或者其他任何你想要的组合的元组。
|
||||
|
||||
你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:
|
||||
|
||||
@ -524,7 +538,7 @@ print("The status message is \(http200Status.description)")
|
||||
// 输出 "The status message is OK"
|
||||
```
|
||||
|
||||
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个`(Int, String)`元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.html#Function_Parameters_and_Return_Values)。
|
||||
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 `(Int, String)` 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.html#Function_Parameters_and_Return_Values)。
|
||||
|
||||
> 注意:
|
||||
元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考[类和结构体](./09_Classes_and_Structures.html)。
|
||||
@ -541,11 +555,11 @@ print("The status message is \(http200Status.description)")
|
||||
* 没有值
|
||||
|
||||
> 注意:
|
||||
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回`nil`,`nil`表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如`NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示_任意类型_的值缺失,并不需要一个特殊值。
|
||||
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回`nil`,`nil`表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如`NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示*任意类型*的值缺失,并不需要一个特殊值。
|
||||
|
||||
来看一个例子。Swift 的`String`类型有一种构造器,作用是将一个`String`值转换成一个`Int`值。然而,并不是所有的字符串都可以转换成一个整数。字符串`"123"`可以被转换成数字`123`,但是字符串`"hello, world"`不行。
|
||||
来看一个例子。Swift 的 `Int` 类型有一种构造器,作用是将一个 `String` 值转换成一个 `Int` 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 `"123"` 可以被转换成数字 `123` ,但是字符串 `"hello, world"` 不行。
|
||||
|
||||
下面的例子使用这种构造器来尝试将一个`String`转换成`Int`:
|
||||
下面的例子使用这种构造器来尝试将一个 `String` 转换成 `Int`:
|
||||
|
||||
```swift
|
||||
let possibleNumber = "123"
|
||||
@ -553,7 +567,7 @@ let convertedNumber = Int(possibleNumber)
|
||||
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
|
||||
```
|
||||
|
||||
因为该构造器可能会失败,所以它返回一个_可选类型_(optional)`Int`,而不是一个`Int`。一个可选的`Int`被写作`Int?`而不是`Int`。问号暗示包含的值是可选类型,也就是说可能包含`Int`值也可能*不包含值*。(不能包含其他任何值比如`Bool`值或者`String`值。只能是`Int`或者什么都没有。)
|
||||
因为该构造器可能会失败,所以它返回一个*可选类型*(optional)`Int`,而不是一个 `Int`。一个可选的 `Int` 被写作 `Int?` 而不是 `Int`。问号暗示包含的值是可选类型,也就是说可能包含 `Int` 值也可能*不包含值*。(不能包含其他任何值比如 `Bool` 值或者 `String` 值。只能是 `Int` 或者什么都没有。)
|
||||
|
||||
<a name="nil"></a>
|
||||
### nil
|
||||
@ -570,7 +584,7 @@ serverResponseCode = nil
|
||||
> 注意:
|
||||
`nil`不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
|
||||
|
||||
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为`nil`:
|
||||
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 `nil`:
|
||||
|
||||
```swift
|
||||
var surveyAnswer: String?
|
||||
@ -578,22 +592,23 @@ var surveyAnswer: String?
|
||||
```
|
||||
|
||||
> 注意:
|
||||
Swift 的`nil`和 Objective-C 中的`nil`并不一样。在 Objective-C 中,`nil`是一个指向不存在对象的指针。在 Swift 中,`nil`不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为`nil`,不只是对象类型。
|
||||
Swift 的 `nil` 和 Objective-C 中的 `nil` 并不一样。在 Objective-C 中,`nil` 是一个指向不存在对象的指针。在 Swift 中,`nil` 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 `nil`,不只是对象类型。
|
||||
|
||||
<a name="if"></a>
|
||||
### if 语句以及强制解析
|
||||
|
||||
你可以使用`if`语句和`nil`比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。
|
||||
你可以使用 `if` 语句和 `nil` 比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。
|
||||
|
||||
如果可选类型有值,它将不等于`nil`:
|
||||
如果可选类型有值,它将不等于 `nil`:
|
||||
|
||||
```swift
|
||||
if convertedNumber != nil {
|
||||
print("convertedNumber contains some integer value.")
|
||||
}
|
||||
// 输出 "convertedNumber contains some integer value."
|
||||
```
|
||||
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的_强制解析(forced unwrapping)_:
|
||||
```
|
||||
|
||||
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的*强制解析(forced unwrapping)*:
|
||||
|
||||
```swift
|
||||
if convertedNumber != nil {
|
||||
@ -602,17 +617,17 @@ if convertedNumber != nil {
|
||||
// 输出 "convertedNumber has an integer value of 123."
|
||||
```
|
||||
|
||||
更多关于`if`语句的内容,请参考[控制流](05_Control_Flow.html)。
|
||||
更多关于 `if` 语句的内容,请参考[控制流](05_Control_Flow.html)。
|
||||
|
||||
> 注意:
|
||||
使用`!`来获取一个不存在的可选值会导致运行时错误。使用`!`来强制解析值之前,一定要确定可选包含一个非`nil`的值。
|
||||
使用 `!` 来获取一个不存在的可选值会导致运行时错误。使用 `!` 来强制解析值之前,一定要确定可选包含一个非 `nil` 的值。
|
||||
|
||||
<a name="optional_binding"></a>
|
||||
### 可选绑定
|
||||
|
||||
使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在`if`和`while`语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if`和`while`语句,请参考[控制流](./05_Control_Flow.html)。
|
||||
使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 `if` 和 `while` 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if` 和 `while` 语句,请参考[控制流](./05_Control_Flow.html)。
|
||||
|
||||
像下面这样在`if`语句中写一个可选绑定:
|
||||
像下面这样在 `if` 语句中写一个可选绑定:
|
||||
|
||||
```swift
|
||||
if let constantName = someOptional {
|
||||
@ -620,7 +635,7 @@ if let constantName = someOptional {
|
||||
}
|
||||
```
|
||||
|
||||
你可以像上面这样使用可选绑定来重写`possibleNumber`这个[例子](./01_The_Basics.html#optionals):
|
||||
你可以像上面这样使用可选绑定来重写 `possibleNumber` 这个[例子](./01_The_Basics.html#optionals):
|
||||
|
||||
```swift
|
||||
if let actualNumber = Int(possibleNumber) {
|
||||
@ -633,25 +648,37 @@ if let actualNumber = Int(possibleNumber) {
|
||||
|
||||
这段代码可以被理解为:
|
||||
|
||||
“如果`Int(possibleNumber)`返回的可选`Int`包含一个值,创建一个叫做`actualNumber`的新常量并将可选包含的值赋给它。”
|
||||
“如果 `Int(possibleNumber)` 返回的可选 `Int` 包含一个值,创建一个叫做 `actualNumber` 的新常量并将可选包含的值赋给它。”
|
||||
|
||||
如果转换成功,`actualNumber`常量可以在`if`语句的第一个分支中使用。它已经被可选类型_包含的_值初始化过,所以不需要再使用`!`后缀来获取它的值。在这个例子中,`actualNumber`只被用来输出转换结果。
|
||||
如果转换成功,`actualNumber` 常量可以在 `if` 语句的第一个分支中使用。它已经被可选类型 *包含的* 值初始化过,所以不需要再使用 `!` 后缀来获取它的值。在这个例子中,`actualNumber` 只被用来输出转换结果。
|
||||
|
||||
你可以在可选绑定中使用常量和变量。如果你想在`if`语句的第一个分支中操作`actualNumber`的值,你可以改成`if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。
|
||||
你可以在可选绑定中使用常量和变量。如果你想在`if`语句的第一个分支中操作 `actualNumber` 的值,你可以改成 `if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。
|
||||
|
||||
你可以包含多个可选绑定在`if`语句中,并使用`where`子句做布尔值判断。
|
||||
你可以包含多个可选绑定或多个布尔条件在一个 `if` 语句中,只要使用逗号分开就行。如果所有可选绑定的值为 `nil` 或者所有布尔条件语句都为 `false`,这样整个 `if` 条件判断都是为 `false`,这时你就需要使用嵌套 `if` 条件语句来处理,如下所示:
|
||||
|
||||
```swift
|
||||
if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < secondNumber {
|
||||
print("\(firstNumber) < \(secondNumber)")
|
||||
}
|
||||
// prints "4 < 42"
|
||||
```
|
||||
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
|
||||
print("\(firstNumber) < \(secondNumber) < 100")
|
||||
}
|
||||
// Prints "4 < 42 < 100"
|
||||
|
||||
if let firstNumber = Int("4") {
|
||||
if let secondNumber = Int("42") {
|
||||
if firstNumber < secondNumber && secondNumber < 100 {
|
||||
print("\(firstNumber) < \(secondNumber) < 100")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Prints "4 < 42 < 100"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 在 `if` 条件语句中使用常量和变量来创建一个可选绑定,仅在 `if` 语句的句中(`body`)中才能获取到值。相反,在 `guard` 语句中使用常量和变量来创建一个可选绑定,仅在 `guard` 语句外且在语句后才能获取到值,请参考[控制流](./05_Control_Flow#early_exit.html)。
|
||||
|
||||
<a name="implicityly_unwrapped_optionals"></a>
|
||||
### 隐式解析可选类型
|
||||
|
||||
如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过`if`语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
|
||||
如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 `if` 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
|
||||
|
||||
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型_总会_有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
|
||||
|
||||
@ -659,7 +686,7 @@ if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < seco
|
||||
|
||||
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考[无主引用以及隐式解析可选属性](./16_Automatic_Reference_Counting.html#unowned_references_and_implicitly_unwrapped_optional_properties)。
|
||||
|
||||
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型`String`和隐式解析可选类型`String`之间的区别:
|
||||
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 `String` 和隐式解析可选类型 `String` 之间的区别:
|
||||
|
||||
```swift
|
||||
let possibleString: String? = "An optional string."
|
||||
@ -672,7 +699,7 @@ let implicitString: String = assumedString // 不需要感叹号
|
||||
你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。
|
||||
|
||||
> 注意:
|
||||
如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。
|
||||
> 如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。
|
||||
|
||||
你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:
|
||||
|
||||
@ -693,11 +720,11 @@ if let definiteString = assumedString {
|
||||
```
|
||||
|
||||
> 注意:
|
||||
如果一个变量之后可能变成`nil`的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是`nil`的话,请使用普通可选类型。
|
||||
> 如果一个变量之后可能变成`nil`的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是`nil`的话,请使用普通可选类型。
|
||||
|
||||
<a name="error_handling"></a>
|
||||
## 错误处理
|
||||
你可以使用*错误处理(error handling)*来应对程序执行中可能会遇到的错误条件。
|
||||
你可以使用 *错误处理(error handling)* 来应对程序执行中可能会遇到的错误条件。
|
||||
|
||||
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。
|
||||
|
||||
@ -725,38 +752,38 @@ do {
|
||||
这里有一个错误处理如何用来应对不同错误条件的例子。
|
||||
|
||||
```swift
|
||||
func makeASandwich() throws {
|
||||
// ...
|
||||
}
|
||||
|
||||
do {
|
||||
try makeASandwich()
|
||||
eatASandwich()
|
||||
} catch Error.OutOfCleanDishes {
|
||||
washDishes()
|
||||
} catch Error.MissingIngredients(let ingredients) {
|
||||
buyGroceries(ingredients)
|
||||
func makeASandwich() throws {
|
||||
// ...
|
||||
}
|
||||
|
||||
do {
|
||||
try makeASandwich()
|
||||
eatASandwich()
|
||||
} catch SandwichError.outOfCleanDishes {
|
||||
washDishes()
|
||||
} catch SandwichError.missingIngredients(let ingredients) {
|
||||
buyGroceries(ingredients)
|
||||
}
|
||||
```
|
||||
|
||||
在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为`makeASandwich()`抛出错误,函数调用被包裹在`try`表达式中。将函数包裹在一个`do`语句中,任何被抛出的错误会被传播到提供的`catch`从句中。
|
||||
在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 `makeASandwich()` 抛出错误,函数调用被包裹在 `try` 表达式中。将函数包裹在一个 `do` 语句中,任何被抛出的错误会被传播到提供的 `catch` 从句中。
|
||||
|
||||
如果没有错误被抛出, `eatASandwich()`函数会被调用。如果一个匹配`Error.OutOfCleanDishes`的错误被抛出,`washDishes`函数会被调用。如果一个匹配`Error.MissingIngredients`的错误被抛出,`buyGroceries(_:)`函数会随着被`catch`所捕捉到的关联值`[String]`被调用。
|
||||
如果没有错误被抛出,`eatASandwich()` 函数会被调用。如果一个匹配 `SandwichError.outOfCleanDishes` 的错误被抛出,`washDishes()` 函数会被调用。如果一个匹配 `SandwichError.missingIngredients` 的错误被抛出,`buyGroceries(_:)` 函数会被调用,并且使用 `catch` 所捕捉到的关联值 `[String]` 作为参数。
|
||||
|
||||
抛出,捕捉,以及传播错误会在[错误处理](./18_Error_Handling.html)章节详细说明。
|
||||
|
||||
<a name="assertions"></a>
|
||||
## 断言
|
||||
|
||||
可选类型可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况。然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能没办法继续执行。这时,你可以在你的代码中触发一个*断言(assertion)*来结束代码运行并通过调试来找到值缺失的原因。
|
||||
可选类型可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况。然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能没办法继续执行。这时,你可以在你的代码中触发一个 *断言(assertion)* 来结束代码运行并通过调试来找到值缺失的原因。
|
||||
|
||||
### 使用断言进行调试
|
||||
|
||||
断言会在运行时判断一个逻辑条件是否为`true`。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为`true`,代码运行会继续进行;如果条件判断为`false`,代码执行结束,你的应用被终止。
|
||||
断言会在运行时判断一个逻辑条件是否为 `true`。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为 `true`,代码运行会继续进行;如果条件判断为 `false`,代码执行结束,你的应用被终止。
|
||||
|
||||
如果你的代码在调试环境下触发了一个断言,比如你在 Xcode 中构建并运行一个应用,你可以清楚地看到不合法的状态发生在哪里并检查断言被触发时你的应用的状态。此外,断言允许你附加一条调试信息。
|
||||
|
||||
你可以使用全局`assert(_:_file:line:)`函数来写一个断言。向这个函数传入一个结果为`true`或者`false`的表达式以及一条信息,当表达式的结果为`false`的时候这条信息会被显示:
|
||||
你可以使用全局 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示:
|
||||
|
||||
```swift
|
||||
let age = -3
|
||||
@ -764,7 +791,7 @@ assert(age >= 0, "A person's age cannot be less than zero")
|
||||
// 因为 age < 0,所以断言会触发
|
||||
```
|
||||
|
||||
在这个例子中,只有`age >= 0`为`true`的时候,即`age`的值非负的时候,代码才会继续执行。如果`age`的值是负数,就像代码中那样,`age >= 0`为`false`,断言被触发,终止应用。
|
||||
在这个例子中,只有 `age >= 0` 为 `true` 的时候,即 `age` 的值非负的时候,代码才会继续执行。如果 `age` 的值是负数,就像代码中那样,`age >= 0` 为 `false`,断言被触发,终止应用。
|
||||
|
||||
如果不需要断言信息,可以省略,就像这样:
|
||||
|
||||
@ -773,17 +800,17 @@ assert(age >= 0)
|
||||
```
|
||||
|
||||
> 注意:
|
||||
当代码使用优化编译的时候,断言将会被禁用,例如在 Xcode 中,使用默认的 target Release 配置选项来 build 时,断言会被禁用。
|
||||
> 当代码使用优化编译的时候,断言将会被禁用,例如在 Xcode 中,使用默认的 target Release 配置选项来 build 时,断言会被禁用。
|
||||
|
||||
### 何时使用断言
|
||||
|
||||
当条件可能为假时使用断言,但是最终一定要_保证_条件为真,这样你的代码才能继续运行。断言的适用情景:
|
||||
|
||||
* 整数类型的下标索引被传入一个自定义下标脚本实现,但是下标索引值可能太小或者太大。
|
||||
* 整数类型的下标索引被传入一个自定义下标实现,但是下标索引值可能太小或者太大。
|
||||
* 需要给函数传入一个值,但是非法的值可能导致函数不能正常执行。
|
||||
* 一个可选值现在是`nil`,但是后面的代码运行需要一个非`nil`值。
|
||||
* 一个可选值现在是 `nil`,但是后面的代码运行需要一个非 `nil` 值。
|
||||
|
||||
请参考[下标脚本](./12_Subscripts.html)和[函数](./06_Functions.html)。
|
||||
请参考[下标](./12_Subscripts.html)和[函数](./06_Functions.html)。
|
||||
|
||||
> 注意:
|
||||
断言可能导致你的应用终止运行,所以你应当仔细设计你的代码来让非法条件不会出现。然而,在你的应用发布之前,有时候非法条件可能出现,这时使用断言可以快速发现问题。
|
||||
> 断言可能导致你的应用终止运行,所以你应当仔细设计你的代码来让非法条件不会出现。然而,在你的应用发布之前,有时候非法条件可能出现,这时使用断言可以快速发现问题。
|
||||
|
||||
@ -8,41 +8,47 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.2
|
||||
> 翻译+校对:[Cee](https://github.com/Cee) 校对:[SketchK](https://github.com/SketchK),2016-05-11
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [术语](#terminology)
|
||||
- [赋值运算符](#assignment_operator)
|
||||
- [算术运算符](#arithmetic_operators)
|
||||
- [组合赋值运算符(Compound Assignment Operators)](#compound_assignment_operators)
|
||||
- [组合赋值运算符](#compound_assignment_operators)
|
||||
- [比较运算符](#comparison_operators)
|
||||
- [三目运算符(Ternary Conditional Operator)](#ternary_conditional_operator)
|
||||
- [三目运算符](#ternary_conditional_operator)
|
||||
- [空合运算符](#nil_coalescing_operator)
|
||||
- [区间运算符](#range_operators)
|
||||
- [逻辑运算符](#logical_operators)
|
||||
|
||||
运算符是检查、改变、合并值的特殊符号或短语。例如,加号`+`将两个数相加(如`let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符`&&`(如`if enteredDoorCode && passedRetinaScan`),或让 i 值加1的便捷自增运算符`++i`等。
|
||||
运算符是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。
|
||||
|
||||
Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+`,`-`,`*`,`/`,`%`等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](./24_Advanced_Operators.html#overflow_operators)。
|
||||
Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+`,`-`,`*`,`/`,`%`等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。
|
||||
|
||||
区别于 C 语言,在 Swift 中你可以对浮点数进行取余运算(`%`),Swift 还提供了 C 语言没有的表达两数之间的值的区间运算符(`a..<b`和`a...b`),这方便我们表达一个区间内的数值。
|
||||
区别于 C 语言,在 Swift 中你可以对浮点数进行取余运算(`%`),Swift 还提供了 C 语言没有的表达两数之间的值的区间运算符(`a..<b` 和 `a...b`),这方便我们表达一个区间内的数值。
|
||||
|
||||
本章节只描述了 Swift 中的基本运算符,[高级运算符](./24_Advanced_Operators.html)包含了高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
|
||||
本章节只描述了 Swift 中的基本运算符,[高级运算符](../chapter2/25_Advanced_Operators.html)这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
|
||||
|
||||
<a name="terminology"></a>
|
||||
## 术语
|
||||
|
||||
运算符有一元、二元和三元运算符。
|
||||
运算符分为一元、二元和三元运算符。
|
||||
|
||||
- 一元运算符对单一操作对象操作(如`-a`)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如`!b`),后置运算符需紧跟在操作对象之后(如`i++`)。
|
||||
- 二元运算符操作两个操作对象(如`2 + 3`),是中置的,因为它们出现在两个操作对象之间。
|
||||
- 一元运算符对单一操作对象操作(如 `-a`)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如 `!b`),后置运算符需紧跟在操作对象之后(如 `c!`)。
|
||||
- 二元运算符操作两个操作对象(如 `2 + 3`),是中置的,因为它们出现在两个操作对象之间。
|
||||
- 三元运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三目运算符(`a ? b : c`)。
|
||||
|
||||
受运算符影响的值叫操作数,在表达式`1 + 2`中,加号`+`是二元运算符,它的两个操作数是值`1`和`2`。
|
||||
受运算符影响的值叫操作数,在表达式 `1 + 2` 中,加号 `+` 是二元运算符,它的两个操作数是值 `1` 和 `2`。
|
||||
|
||||
<a name="assignment_operator"></a>
|
||||
## 赋值运算符
|
||||
|
||||
赋值运算(`a = b`),表示用`b`的值来初始化或更新`a`的值:
|
||||
赋值运算(`a = b`),表示用 `b` 的值来初始化或更新 `a` 的值:
|
||||
|
||||
```swift
|
||||
let b = 10
|
||||
@ -55,7 +61,7 @@ a = b
|
||||
|
||||
```swift
|
||||
let (x, y) = (1, 2)
|
||||
// 现在 x 等于 1, y 等于 2
|
||||
// 现在 x 等于 1,y 等于 2
|
||||
```
|
||||
|
||||
与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下代码是错误的:
|
||||
@ -66,7 +72,7 @@ if x = y {
|
||||
}
|
||||
```
|
||||
|
||||
这个特性使你无法把(`==`)错写成(`=`),由于`if x = y`是错误代码,Swift帮你避免此类错误的的发生。
|
||||
这个特性使你无法把(`==`)错写成(`=`),由于 `if x = y` 是错误代码,Swift 能帮你避免此类错误发生。
|
||||
|
||||
<a name="arithmetic_operators"></a>
|
||||
## 算术运算符
|
||||
@ -85,9 +91,9 @@ Swift 中所有数值类型都支持了基本的四则算术运算:
|
||||
10.0 / 2.5 // 等于 4.0
|
||||
```
|
||||
|
||||
与 C 语言和 Objective-C 不同的是,Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如`a &+ b`)。详情参见[溢出运算符](./24_Advanced_Operators.html#overflow_operators)。
|
||||
与 C 语言和 Objective-C 不同的是,Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 `a &+ b`)。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。
|
||||
|
||||
加法运算符也可用于`String`的拼接:
|
||||
加法运算符也可用于 `String` 的拼接:
|
||||
|
||||
```swift
|
||||
"hello, " + "world" // 等于 "hello, world"
|
||||
@ -95,16 +101,16 @@ Swift 中所有数值类型都支持了基本的四则算术运算:
|
||||
|
||||
### 求余运算符
|
||||
|
||||
求余运算(`a % b`)是计算`b`的多少倍刚刚好可以容入`a`,返回多出来的那部分(余数)。
|
||||
求余运算(`a % b`)是计算 `b` 的多少倍刚刚好可以容入`a`,返回多出来的那部分(余数)。
|
||||
|
||||
>注意:
|
||||
求余运算(`%`)在其他语言也叫取模运算。然而严格说来,我们看该运算符对负数的操作结果,"求余"比"取模"更合适些。
|
||||
> 注意:
|
||||
求余运算(`%`)在其他语言也叫取模运算。然而严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。
|
||||
|
||||
我们来谈谈取余是怎么回事,计算`9 % 4`,你先计算出`4`的多少倍会刚好可以容入`9`中:
|
||||
我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中:
|
||||
|
||||

|
||||
|
||||
2倍,非常好,那余数是1(用橙色标出)
|
||||
你可以在 `9` 中放入两个 `4`,那余数是 1(用橙色标出)。
|
||||
|
||||
在 Swift 中可以表达为:
|
||||
|
||||
@ -112,13 +118,13 @@ Swift 中所有数值类型都支持了基本的四则算术运算:
|
||||
9 % 4 // 等于 1
|
||||
```
|
||||
|
||||
为了得到`a % b`的结果,`%`计算了以下等式,并输出`余数`作为结果:
|
||||
为了得到 `a % b` 的结果,`%` 计算了以下等式,并输出`余数`作为结果:
|
||||
|
||||
a = (b × 倍数) + 余数
|
||||
|
||||
当`倍数`取最大值的时候,就会刚好可以容入`a`中。
|
||||
当`倍数`取最大值的时候,就会刚好可以容入 `a` 中。
|
||||
|
||||
把`9`和`4`代入等式中,我们得`1`:
|
||||
把 `9` 和 `4` 代入等式中,我们得 `1`:
|
||||
|
||||
9 = (4 × 2) + 1
|
||||
|
||||
@ -128,13 +134,13 @@ Swift 中所有数值类型都支持了基本的四则算术运算:
|
||||
-9 % 4 // 等于 -1
|
||||
```
|
||||
|
||||
把`-9`和`4`代入等式,`-2`是取到的最大整数:
|
||||
把 `-9` 和 `4` 代入等式,`-2` 是取到的最大整数:
|
||||
|
||||
-9 = (4 × -2) + -1
|
||||
|
||||
余数是`-1`。
|
||||
余数是 `-1`。
|
||||
|
||||
在对负数`b`求余时,`b`的符号会被忽略。这意味着 `a % b` 和 `a % -b`的结果是相同的。
|
||||
在对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b` 和 `a % -b` 的结果是相同的。
|
||||
|
||||
### 浮点数求余计算
|
||||
|
||||
@ -144,46 +150,14 @@ Swift 中所有数值类型都支持了基本的四则算术运算:
|
||||
8 % 2.5 // 等于 0.5
|
||||
```
|
||||
|
||||
这个例子中,`8`除于`2.5`等于`3`余`0.5`,所以结果是一个`Double`值`0.5`。
|
||||
这个例子中,`8` 除以 `2.5` 等于 `3` 余 `0.5`,所以结果是一个 `Double` 型的值为 `0.5`。
|
||||
|
||||

|
||||
|
||||
### 自增和自减运算
|
||||
|
||||
和 C 语言一样,Swift 也提供了对变量本身加1或减1的自增(`++`)和自减(`--`)的缩略算符。其操作对象可以是整形和浮点型。
|
||||
|
||||
```swift
|
||||
var i = 0
|
||||
++i // 现在 i = 1
|
||||
```
|
||||
|
||||
每调用一次`++i`,`i`的值就会加1。实际上,`++i`是`i = i + 1`的简写,而`--i`是`i = i - 1`的简写。
|
||||
|
||||
`++`和`--`既可以用作前置运算又可以用作后置运算。`++i`,`i++`,`--i`和`i--`都是有效的写法。
|
||||
|
||||
我们需要注意的是这些运算符即可修改了`i`的值也可以返回`i`的值。如果你只想修改`i`的值,那你就可以忽略这个返回值。但如果你想使用返回值,你就需要留意前置和后置操作的返回值是不同的,她们遵循以下原则:
|
||||
|
||||
- 当`++`前置的时候,先自増再返回。
|
||||
- 当`++`后置的时候,先返回再自增。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
var a = 0
|
||||
let b = ++a // a 和 b 现在都是 1
|
||||
let c = a++ // a 现在 2, 但 c 是 a 自增前的值 1
|
||||
```
|
||||
|
||||
上述例子,`let b = ++a`先把`a`加1了再返回`a`的值。所以`a`和`b`都是新值`1`。
|
||||
|
||||
而`let c = a++`,是先返回了`a`的值,然后`a`才加1。所以`c`得到了`a`的旧值1,而`a`加1后变成2。
|
||||
|
||||
除非你需要使用`i++`的特性,不然推荐你使用`++i`和`--i`,因为先修改后返回这样的行为更符合我们的逻辑。
|
||||
|
||||
|
||||
### 一元负号运算符
|
||||
|
||||
数值的正负号可以使用前缀`-`(即一元负号)来切换:
|
||||
数值的正负号可以使用前缀 `-`(即一元负号)来切换:
|
||||
|
||||
```swift
|
||||
let three = 3
|
||||
@ -202,28 +176,29 @@ let minusSix = -6
|
||||
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
|
||||
```
|
||||
|
||||
虽然一元`+`什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。
|
||||
虽然一元 `+` 什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。
|
||||
|
||||
|
||||
<a name="compound_assignment_operators"></a>
|
||||
## 复合赋值(Compound Assignment Operators)
|
||||
## 组合赋值运算符
|
||||
|
||||
如同 C 语言,Swift 也提供把其他运算符和赋值运算(`=`)组合的复合赋值运算符,组合加运算(`+=`)是其中一个例子:
|
||||
如同 C 语言,Swift 也提供把其他运算符和赋值运算(`=`)组合的组合赋值运算符,组合加运算(`+=`)是其中一个例子:
|
||||
|
||||
```swift
|
||||
var a = 1
|
||||
a += 2 // a 现在是 3
|
||||
a += 2
|
||||
// a 现在是 3
|
||||
```
|
||||
|
||||
表达式`a += 2`是`a = a + 2`的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。
|
||||
表达式 `a += 2` 是 `a = a + 2` 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。
|
||||
|
||||
>注意:
|
||||
> 注意:
|
||||
复合赋值运算没有返回值,`let b = a += 2`这类代码是错误。这不同于上面提到的自增和自减运算符。
|
||||
|
||||
在[表达式](../chapter3/04_Expressions.html)章节里有复合运算符的完整列表。
|
||||
|
||||
<a name="comparison_operators"></a>
|
||||
## 比较运算符
|
||||
## 比较运算符(Comparison Operators)
|
||||
|
||||
所有标准 C 语言中的比较运算都可以在 Swift 中使用:
|
||||
|
||||
@ -235,7 +210,7 @@ a += 2 // a 现在是 3
|
||||
- 小于等于(`a <= b`)
|
||||
|
||||
> 注意:
|
||||
Swift 也提供恒等`===`和不恒等`!==`这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](./09_Classes_and_Structures.html)。
|
||||
Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](../chapter2/09_Classes_and_Structures.html)。
|
||||
|
||||
每个比较运算都返回了一个标识表达式是否成立的布尔值:
|
||||
|
||||
@ -260,12 +235,25 @@ if name == "world" {
|
||||
// 输出 "hello, world", 因为 `name` 就是等于 "world"
|
||||
```
|
||||
|
||||
关于`if`语句,请看[控制流](./05_Control_Flow.html)。
|
||||
关于 `if` 语句,请看[控制流](../chapter2/05_Control_Flow.html)。
|
||||
|
||||
当元组中的值可以比较时,你也可以使用这些运算符来比较它们的大小。例如,因为 `Int` 和 `String` 类型的值可以比较,所以类型为 `(Int, String)` 的元组也可以被比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。
|
||||
|
||||
比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
> 注意:
|
||||
Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
|
||||
|
||||
<a name="ternary_conditional_operator"></a>
|
||||
## 三目运算符(Ternary Conditional Operator)
|
||||
## 三目运算符(Ternary Conditional Operator)
|
||||
|
||||
三目运算符的特殊在于它是有三个操作数的运算符,它的原型是 `问题 ? 答案1 : 答案2`。它简洁地表达根据`问题`成立与否作出二选一的操作。如果`问题`成立,返回`答案1`的结果; 如果不成立,返回`答案2`的结果。
|
||||
三目运算符的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果。
|
||||
|
||||
三目运算符是以下代码的缩写形式:
|
||||
|
||||
@ -277,7 +265,7 @@ if question {
|
||||
}
|
||||
```
|
||||
|
||||
这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出50点;如果没有表头,只需高出20点:
|
||||
这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点:
|
||||
|
||||
```swift
|
||||
let contentHeight = 40
|
||||
@ -300,28 +288,25 @@ if hasHeader {
|
||||
// rowHeight 现在是 90
|
||||
```
|
||||
|
||||
第一段代码例子使用了三目运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将`rowHeight`定义成变量,因为它的值无需在`if`语句中改变。
|
||||
第一段代码例子使用了三目运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。
|
||||
|
||||
三目运算提供有效率且便捷的方式来表达二选一的选择。需要注意的事,过度使用三目运算符会使简洁的代码变的难懂。我们应避免在一个组合语句中使用多个三目运算符。
|
||||
|
||||
<a name="nil_coalescing_operator"></a>
|
||||
## 空合运算符(Nil Coalescing Operator)
|
||||
## 空合运算符(Nil Coalescing Operator)
|
||||
|
||||
空合运算符(`a ?? b`)将对可选类型`a`进行空判断,如果`a`包含一个值就进行解封,否则就返回一个默认值`b`.这个运算符有两个条件:
|
||||
空合运算符(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解封,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
|
||||
|
||||
- 表达式`a`必须是Optional类型
|
||||
- 默认值`b`的类型必须要和`a`存储值的类型保持一致
|
||||
|
||||
空合运算符是对以下代码的简短表达方法
|
||||
空合运算符是对以下代码的简短表达方法:
|
||||
|
||||
```swift
|
||||
a != nil ? a! : b
|
||||
```
|
||||
|
||||
上述代码使用了三目运算符。当可选类型`a`的值不为空时,进行强制解封(`a!`)访问`a`中值,反之当`a`中值为空时,返回默认值b。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。
|
||||
上述代码使用了三目运算符。当可选类型 `a` 的值不为空时,进行强制解封(`a!`),访问 `a` 中的值;反之返回默认值 `b`。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。
|
||||
|
||||
> 注意:
|
||||
如果`a`为非空值(`non-nil`),那么值`b`将不会被估值。这也就是所谓的短路求值。
|
||||
如果 `a` 为非空值(`non-nil`),那么值 `b` 将不会被计算。这也就是所谓的短路求值。
|
||||
|
||||
下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择:
|
||||
|
||||
@ -333,10 +318,10 @@ var colorNameToUse = userDefinedColorName ?? defaultColorName
|
||||
// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red"
|
||||
```
|
||||
|
||||
`userDefinedColorName`变量被定义为一个可选`String`类型,默认值为`nil`。由于`userDefinedColorName`是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为`colorNameToUse`的变量赋予一个字符串类型初始值。
|
||||
由于`userDefinedColorName`值为空,因此表达式`userDefinedColorName ?? defaultColorName`返回`defaultColorName`的值,即`red`。
|
||||
`userDefinedColorName` 变量被定义为一个可选的 `String` 类型,默认值为 `nil`。由于 `userDefinedColorName` 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 `colorNameToUse` 的变量赋予一个字符串类型初始值。
|
||||
由于 `userDefinedColorName` 值为空,因此表达式 `userDefinedColorName ?? defaultColorName` 返回 `defaultColorName` 的值,即 `red`。
|
||||
|
||||
另一种情况,分配一个非空值(`non-nil`)给`userDefinedColorName`,再次执行空合运算,运算结果为封包在`userDefaultColorName`中的值,而非默认值。
|
||||
另一种情况,分配一个非空值(`non-nil`)给 `userDefinedColorName`,再次执行空合运算,运算结果为封包在 `userDefaultColorName` 中的值,而非默认值。
|
||||
|
||||
```swift
|
||||
userDefinedColorName = "green"
|
||||
@ -345,14 +330,14 @@ colorNameToUse = userDefinedColorName ?? defaultColorName
|
||||
```
|
||||
|
||||
<a name="range_operators"></a>
|
||||
## 区间运算符
|
||||
## 区间运算符(Range Operators)
|
||||
|
||||
Swift 提供了两个方便表达一个区间的值的运算符。
|
||||
|
||||
### 闭区间运算符
|
||||
闭区间运算符(`a...b`)定义一个包含从`a`到`b`(包括`a`和`b`)的所有值的区间,`b`必须大于等于`a`。
|
||||
闭区间运算符(`a...b`)定义一个包含从 `a` 到 `b`(包括 `a` 和 `b`)的所有值的区间。`a` 的值不能超过 `b`。
|
||||
|
||||
闭区间运算符在迭代一个区间的所有值时是非常有用的,如在`for-in`循环中:
|
||||
闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 `for-in` 循环中:
|
||||
|
||||
```swift
|
||||
for index in 1...5 {
|
||||
@ -365,14 +350,14 @@ for index in 1...5 {
|
||||
// 5 * 5 = 25
|
||||
```
|
||||
|
||||
关于`for-in`,请看[控制流](./05_Control_Flow.html)。
|
||||
关于 `for-in`,请看[控制流](../chapter2/05_Control_Flow.html)。
|
||||
|
||||
### 半开区间运算符
|
||||
|
||||
半开区间(`a..<b`)定义一个从`a`到`b`但不包括`b`的区间。
|
||||
半开区间(`a..<b`)定义一个从 `a` 到 `b` 但不包括 `b` 的区间。
|
||||
之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。
|
||||
|
||||
半开区间的实用性在于当你使用一个从0开始的列表(如数组)时,非常方便地从0数到列表的长度。
|
||||
半开区间的实用性在于当你使用一个从 0 开始的列表(如数组)时,非常方便地从0数到列表的长度。
|
||||
|
||||
```swift
|
||||
let names = ["Anna", "Alex", "Brian", "Jack"]
|
||||
@ -386,10 +371,10 @@ for i in 0..<count {
|
||||
// 第 4 个人叫 Jack
|
||||
```
|
||||
|
||||
数组有4个元素,但`0..<count`只数到3(最后一个元素的下标),因为它是半开区间。关于数组,请查阅[数组](./04_Collection_Types.html#arrays)。
|
||||
数组有 4 个元素,但 `0..<count` 只数到3(最后一个元素的下标),因为它是半开区间。关于数组,请查阅[数组](../chapter2/04_Collection_Types.html#arrays)。
|
||||
|
||||
<a name="logical_operators"></a>
|
||||
## 逻辑运算
|
||||
## 逻辑运算(Logical Operators)
|
||||
|
||||
逻辑运算的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。
|
||||
|
||||
@ -399,9 +384,9 @@ for i in 0..<count {
|
||||
|
||||
### 逻辑非
|
||||
|
||||
逻辑非运算(`!a`)对一个布尔值取反,使得`true`变`false`,`false`变`true`。
|
||||
逻辑非运算(`!a`)对一个布尔值取反,使得 `true` 变 `false`,`false` 变 `true`。
|
||||
|
||||
它是一个前置运算符,需紧跟在操作数之前,且不加空格。读作`非 a`,例子如下:
|
||||
它是一个前置运算符,需紧跟在操作数之前,且不加空格。读作 `非 a` ,例子如下:
|
||||
|
||||
```swift
|
||||
let allowedEntry = false
|
||||
@ -411,17 +396,17 @@ if !allowedEntry {
|
||||
// 输出 "ACCESS DENIED"
|
||||
```
|
||||
|
||||
`if !allowedEntry`语句可以读作“如果非 allowedEntry。”,接下一行代码只有在“非 allowedEntry”为`true`,即`allowEntry`为`false`时被执行。
|
||||
`if !allowedEntry` 语句可以读作「如果非 allowedEntry」,接下一行代码只有在「非 allowedEntry」为 `true`,即 `allowEntry` 为 `false` 时被执行。
|
||||
|
||||
在示例代码中,小心地选择布尔常量或变量有助于代码的可读性,并且避免使用双重逻辑非运算,或混乱的逻辑语句。
|
||||
|
||||
### 逻辑与
|
||||
|
||||
逻辑与(`a && b`)表达了只有`a`和`b`的值都为`true`时,整个表达式的值才会是`true`。
|
||||
逻辑与(`a && b`)表达了只有 `a` 和 `b` 的值都为 `true` 时,整个表达式的值才会是 `true`。
|
||||
|
||||
只要任意一个值为`false`,整个表达式的值就为`false`。事实上,如果第一个值为`false`,那么是不去计算第二个值的,因为它已经不可能影响整个表达式的结果了。这被称做“短路计算(short-circuit evaluation)”。
|
||||
只要任意一个值为 `false`,整个表达式的值就为 `false`。事实上,如果第一个值为 `false`,那么是不去计算第二个值的,因为它已经不可能影响整个表达式的结果了。这被称做「短路计算(short-circuit evaluation)」。
|
||||
|
||||
以下例子,只有两个`Bool`值都为`true`的时候才允许进入:
|
||||
以下例子,只有两个 `Bool` 值都为 `true` 的时候才允许进入 if:
|
||||
|
||||
```swift
|
||||
let enteredDoorCode = true
|
||||
@ -436,11 +421,11 @@ if enteredDoorCode && passedRetinaScan {
|
||||
|
||||
### 逻辑或
|
||||
|
||||
逻辑或(`a || b`)是一个由两个连续的`|`组成的中置运算符。它表示了两个逻辑表达式的其中一个为`true`,整个表达式就为`true`。
|
||||
逻辑或(`a || b`)是一个由两个连续的 `|` 组成的中置运算符。它表示了两个逻辑表达式的其中一个为 `true`,整个表达式就为 `true`。
|
||||
|
||||
同逻辑与运算类似,逻辑或也是“短路计算”的,当左端的表达式为`true`时,将不计算右边的表达式了,因为它不可能改变整个表达式的值了。
|
||||
同逻辑与运算类似,逻辑或也是「短路计算」的,当左端的表达式为 `true` 时,将不计算右边的表达式了,因为它不可能改变整个表达式的值了。
|
||||
|
||||
以下示例代码中,第一个布尔值(`hasDoorKey`)为`false`,但第二个值(`knowsOverridePassword`)为`true`,所以整个表达是`true`,于是允许进入:
|
||||
以下示例代码中,第一个布尔值(`hasDoorKey`)为 `false`,但第二个值(`knowsOverridePassword`)为 `true`,所以整个表达是 `true`,于是允许进入:
|
||||
|
||||
```swift
|
||||
let hasDoorKey = false
|
||||
@ -466,14 +451,14 @@ if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
|
||||
// 输出 "Welcome!"
|
||||
```
|
||||
|
||||
这个例子使用了含多个`&&`和`||`的复合逻辑。但无论怎样,`&&`和`||`始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:
|
||||
这个例子使用了含多个 `&&` 和 `||` 的复合逻辑。但无论怎样,`&&` 和 `||` 始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:
|
||||
|
||||
如果我们输入了正确的密码并通过了视网膜扫描,或者我们有一把有效的钥匙,又或者我们知道紧急情况下重置的密码,我们就能把门打开进入。
|
||||
|
||||
前两种情况,我们都不满足,所以前两个简单逻辑的结果是`false`,但是我们是知道紧急情况下重置的密码的,所以整个复杂表达式的值还是`true`。
|
||||
前两种情况,我们都不满足,所以前两个简单逻辑的结果是 `false`,但是我们是知道紧急情况下重置的密码的,所以整个复杂表达式的值还是 `true`。
|
||||
|
||||
>注意:
|
||||
Swift 逻辑操作符`&&`和`||`是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。
|
||||
> 注意:
|
||||
Swift 逻辑操作符 `&&` 和 `||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。
|
||||
|
||||
### 使用括号来明确优先级
|
||||
|
||||
|
||||
@ -8,6 +8,18 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[DianQK](https://github.com/DianQK)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[DianQK](https://github.com/DianQK)
|
||||
> 校对:[shanks](http://codebuild.me), [Realank](https://github.com/Realank),
|
||||
|
||||
> 2.2
|
||||
> 校对:[SketchK](https://github.com/SketchK)
|
||||
|
||||
> 3.0
|
||||
> 校对:[CMB](https://github.com/chenmingbiao)
|
||||
>
|
||||
> 版本日期:2016-09-13
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [字符串字面量](#string_literals)
|
||||
@ -23,20 +35,20 @@
|
||||
- [比较字符串](#comparing_strings)
|
||||
- [字符串的 Unicode 表示形式](#unicode_representations_of_strings)
|
||||
|
||||
|
||||
`String`是例如"hello, world","albatross"这样的有序的`Character`(字符)类型的值的集合。通过`String`类型来表示。
|
||||
一个`String`的内容可以用变量的方式读取,它包括一个`Character`值的集合。
|
||||
一个`String`的内容可以用许多方式读取,它包括一个`Character`值的集合。
|
||||
|
||||
创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。
|
||||
字符串连接操作只需要简单地通过`+`符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。
|
||||
字符串连接操作只需要简单地通过`+`符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。
|
||||
|
||||
尽管语法简易,但`String`类型是一种快速、现代化的字符串实现。
|
||||
每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式(representations)。你也可以在常量、变量、字面量和表达式中进行字符串插值操作,这可以帮助你轻松创建用于展示、存储和打印的自定义字符串。
|
||||
每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式(representations)。
|
||||
|
||||
> 注意:
|
||||
> Swift 的`String`类型与 Foundation `NSString`类进行了无缝桥接。就像 [`AnyObject`类型](./19_Type_Casting.html#anyobject) 中提到的一样,在使用 Cocoa 中的 Foundation 框架时,您可以将创建的任何字符串的值转换成`NSString`,并调用任意的`NSString` API。您也可以在任意要求传入`NSString`实例作为参数的 API 中用`String`类型的值代替。
|
||||
> 更多关于在 Foundation 和 Cocoa 中使用`String`的信息请查看 *[Using Swift with Cocoa and Objective-C (Swift 2.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)*。
|
||||
|
||||
|
||||
|
||||
<a name="string_literals"></a>
|
||||
## 字符串字面量(String Literals)
|
||||
|
||||
@ -64,7 +76,7 @@ var anotherEmptyString = String() // 初始化方法
|
||||
// 两个字符串均为空并等价。
|
||||
```
|
||||
|
||||
您可以通过检查其`Boolean`类型的`isEmpty`属性来判断该字符串是否为空:
|
||||
您可以通过检查其`Bool`类型的`isEmpty`属性来判断该字符串是否为空:
|
||||
|
||||
```swift
|
||||
if emptyString.isEmpty {
|
||||
@ -73,6 +85,7 @@ if emptyString.isEmpty {
|
||||
// 打印输出:"Nothing to see here"
|
||||
```
|
||||
|
||||
|
||||
<a name="string_mutability"></a>
|
||||
## 字符串可变性 (String Mutability)
|
||||
|
||||
@ -91,6 +104,7 @@ constantString += " and another Highlander"
|
||||
> 注意:
|
||||
在 Objective-C 和 Cocoa 中,您需要通过选择两个不同的类(`NSString`和`NSMutableString`)来指定字符串是否可以被修改。
|
||||
|
||||
|
||||
<a name="strings_are_value_types"></a>
|
||||
## 字符串是值类型(Strings Are Value Types)
|
||||
|
||||
@ -105,6 +119,7 @@ Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字
|
||||
|
||||
在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。
|
||||
|
||||
|
||||
<a name="working_with_characters"></a>
|
||||
## 使用字符(Working with Characters)
|
||||
|
||||
@ -121,7 +136,7 @@ for character in "Dog!🐶".characters {
|
||||
// 🐶
|
||||
```
|
||||
|
||||
`for-in`循环在 [For Loops](./05_Control_Flow.html#for_loops) 中进行了详细描述。
|
||||
`for-in`循环在 [For 循环](./05_Control_Flow.html#for_loops) 中进行了详细描述。
|
||||
|
||||
另外,通过标明一个`Character`类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
|
||||
|
||||
@ -137,6 +152,7 @@ print(catString)
|
||||
// 打印输出:"Cat!🐱"
|
||||
```
|
||||
|
||||
|
||||
<a name="concatenating_strings_and_characters"></a>
|
||||
## 连接字符串和字符 (Concatenating Strings and Characters)
|
||||
|
||||
@ -199,6 +215,7 @@ Unicode 是一个国际标准,用于文本的编码和表示。
|
||||
它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。
|
||||
Swift 的`String`和`Character`类型是完全兼容 Unicode 标准的。
|
||||
|
||||
|
||||
<a name="unicode_scalars"></a>
|
||||
### Unicode 标量(Unicode Scalars)
|
||||
|
||||
@ -244,7 +261,7 @@ let sparklingHeart = "\u{1F496}" // 💖, Unicode 标量 U+1F496
|
||||
```swift
|
||||
let eAcute: Character = "\u{E9}" // é
|
||||
let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
|
||||
// eAcute 是 é, combinedEAcute 是 é
|
||||
// eAcute 是 é, combinedEAcute 是 é
|
||||
```
|
||||
|
||||
可扩展的字符群集是一个灵活的方法,用许多复杂的脚本字符表示单一的`Character`值。
|
||||
@ -255,7 +272,7 @@ let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
|
||||
```swift
|
||||
let precomposed: Character = "\u{D55C}" // 한
|
||||
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ
|
||||
// precomposed 是 한, decomposed 是 한
|
||||
// precomposed 是 한, decomposed 是 한
|
||||
```
|
||||
|
||||
可拓展的字符群集可以使包围记号(例如`COMBINING ENCLOSING CIRCLE`或者`U+20DD`)的标量包围其他 Unicode 标量,作为一个单一的`Character`值:
|
||||
@ -265,7 +282,7 @@ let enclosedEAcute: Character = "\u{E9}\u{20DD}"
|
||||
// enclosedEAcute 是 é⃝
|
||||
```
|
||||
|
||||
局部的指示符号的 Unicode 标量可以组合成一个单一的`Character`值,例如`REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和`REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`):
|
||||
地域性指示符号的 Unicode 标量可以组合成一个单一的`Character`值,例如`REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和`REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`):
|
||||
|
||||
|
||||
```swift
|
||||
@ -286,7 +303,7 @@ print("unusualMenagerie has \(unusualMenagerie.characters.count) characters")
|
||||
|
||||
注意在 Swift 中,使用可拓展的字符群集作为`Character`值来连接或改变字符串时,并不一定会更改字符串的字符数量。
|
||||
|
||||
例如,如果你用四个字符的单词`cafe`初始化一个新的字符串,然后添加一个`COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是`4`,因为第四个字符是`é`,而不是`e`:
|
||||
例如,如果你用四个字符的单词`cafe`初始化一个新的字符串,然后添加一个`COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是`4`,因为第四个字符是`é`,而不是`e`:
|
||||
|
||||
```swift
|
||||
var word = "cafe"
|
||||
@ -308,7 +325,7 @@ print("the number of characters in \(word) is \(word.characters.count)")
|
||||
<a name="accessing_and_modifying_a_string"></a>
|
||||
## 访问和修改字符串 (Accessing and Modifying a String)
|
||||
|
||||
你可以通字符串的属性和方法来访问和读取它,当然也可以用下标语法完成。
|
||||
你可以通过字符串的属性和方法来访问和修改它,当然也可以用下标语法完成。
|
||||
|
||||
<a name="string_indices"></a>
|
||||
### 字符串索引 (String Indices)
|
||||
@ -319,72 +336,70 @@ print("the number of characters in \(word) is \(word.characters.count)")
|
||||
|
||||
使用`startIndex`属性可以获取一个`String`的第一个`Character`的索引。使用`endIndex`属性可以获取最后一个`Character`的后一个位置的索引。因此,`endIndex`属性不能作为一个字符串的有效下标。如果`String`是空串,`startIndex`和`endIndex`是相等的。
|
||||
|
||||
通过调用`String.Index`的`predecessor()`方法,可以立即得到前面一个索引,调用`successor()`方法可以立即得到后面一个索引。任何一个`String`的索引都可以通过锁链作用的这些方法来获取另一个索引,也可以调用`advancedBy(_:)`方法来获取。但如果尝试获取出界的字符串索引,就会抛出一个运行时错误。
|
||||
通过调用 `String` 的 `index(before:)` 或 `index(after:)` 方法,可以立即得到前面或后面的一个索引。您还可以通过调用 `index(_:offsetBy:)` 方法来获取对应偏移量的索引,这种方式可以避免多次调用 `index(before:)` 或 `index(after:)` 方法。
|
||||
|
||||
你可以使用下标语法来访问`String`特定索引的`Character`。
|
||||
你可以使用下标语法来访问 `String` 特定索引的 `Character`。
|
||||
|
||||
```swift
|
||||
let greeting = "Guten Tag!"
|
||||
greeting[greeting.startIndex]
|
||||
// G
|
||||
greeting[greeting.endIndex.predecessor()]
|
||||
greeting[greeting.index(before: greeting.endIndex)]
|
||||
// !
|
||||
greeting[greeting.startIndex.successor()]
|
||||
greeting[greeting.index(after: greeting.startIndex)]
|
||||
// u
|
||||
let index = greeting.startIndex.advancedBy(7)
|
||||
let index = greeting.index(greeting.startIndex, offsetBy: 7)
|
||||
greeting[index]
|
||||
// a
|
||||
```
|
||||
|
||||
试图获取越界索引对应的`Character`,将引发一个运行时错误。
|
||||
试图获取越界索引对应的 `Character`,将引发一个运行时错误。
|
||||
|
||||
```swift
|
||||
greeting[greeting.endIndex] // error
|
||||
greeting.endIndex.successor() // error
|
||||
greeting.index(after: endIndex) // error
|
||||
```
|
||||
|
||||
使用`characters`属性的`indices`属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。
|
||||
使用 `characters` 属性的 `indices` 属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。
|
||||
|
||||
```swift
|
||||
for index in greeting.characters.indices {
|
||||
print("\(greeting[index]) ", terminator: " ")
|
||||
print("\(greeting[index]) ", terminator: "")
|
||||
}
|
||||
// 打印输出 "G u t e n T a g !"
|
||||
// 打印输出 "G u t e n T a g ! "
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 您可以使用 `startIndex` 和 `endIndex` 属性或者 `index(before:)` 、`index(after:)` 和 `index(_:offsetBy:)` 方法在任意一个确认的并遵循 `Collection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set`中。
|
||||
|
||||
<a name="inserting_and_removing"></a>
|
||||
### 插入和删除 (Inserting and Removing)
|
||||
|
||||
调用`insert(_:atIndex:)`方法可以在一个字符串的指定索引插入一个字符。
|
||||
调用 `insert(_:atIndex:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串。
|
||||
|
||||
```swift
|
||||
var welcome = "hello"
|
||||
welcome.insert("!", atIndex: welcome.endIndex)
|
||||
// welcome now 现在等于 "hello!"
|
||||
welcome.insert("!", at: welcome.endIndex)
|
||||
// welcome 变量现在等于 "hello!"
|
||||
|
||||
welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome.endIndex))
|
||||
// welcome 变量现在等于 "hello there!"
|
||||
```
|
||||
|
||||
调用`insertContentsOf(_:at:)`方法可以在一个字符串的指定索引插入一个字符串。
|
||||
调用 `remove(at:)` 方法可以在一个字符串的指定索引删除一个字符,调用 `removeSubrange(_:)` 方法可以在一个字符串的指定索引删除一个子字符串。
|
||||
|
||||
```swift
|
||||
welcome.insertContentsOf(" there".characters, at: welcome.endIndex.predecessor())
|
||||
// welcome 现在等于 "hello there!"
|
||||
```
|
||||
|
||||
调用`removeAtIndex(_:)`方法可以在一个字符串的指定索引删除一个字符。
|
||||
|
||||
```swift
|
||||
welcome.removeAtIndex(welcome.endIndex.predecessor())
|
||||
welcome.remove(at: welcome.index(before: welcome.endIndex))
|
||||
// welcome 现在等于 "hello there"
|
||||
```
|
||||
|
||||
调用`removeRange(_:)`方法可以在一个字符串的指定索引删除一个子字符串。
|
||||
|
||||
```swift
|
||||
let range = welcome.endIndex.advancedBy(-6)..<welcome.endIndex
|
||||
welcome.removeRange(range)
|
||||
|
||||
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
|
||||
welcome.removeSubrange(range)
|
||||
// welcome 现在等于 "hello"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 您可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
|
||||
|
||||
|
||||
<a name="comparing_strings"></a>
|
||||
## 比较字符串 (Comparing Strings)
|
||||
@ -413,7 +428,7 @@ if quotation == sameQuotation {
|
||||
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E WITH ACUTE
|
||||
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
|
||||
|
||||
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
|
||||
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
|
||||
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
|
||||
|
||||
if eAcuteQuestion == combinedEAcuteQuestion {
|
||||
@ -436,7 +451,7 @@ if latinCapitalLetterA != cyrillicCapitalLetterA {
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 在 Swift 中,字符串和字符并不区分区域。
|
||||
> 在 Swift 中,字符串和字符并不区分地域(not locale-sensitive)。
|
||||
|
||||
|
||||
<a name="prefix_and_suffix_equality"></a>
|
||||
@ -468,7 +483,7 @@ let romeoAndJuliet = [
|
||||
var act1SceneCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasPrefix("Act 1 ") {
|
||||
++act1SceneCount
|
||||
act1SceneCount += 1
|
||||
}
|
||||
}
|
||||
print("There are \(act1SceneCount) scenes in Act 1")
|
||||
@ -482,9 +497,9 @@ var mansionCount = 0
|
||||
var cellCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasSuffix("Capulet's mansion") {
|
||||
++mansionCount
|
||||
mansionCount += 1
|
||||
} else if scene.hasSuffix("Friar Lawrence's cell") {
|
||||
++cellCount
|
||||
cellCount += 1
|
||||
}
|
||||
}
|
||||
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
|
||||
@ -498,7 +513,7 @@ print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
|
||||
<a name="unicode_representations_of_strings"></a>
|
||||
## 字符串的 Unicode 表示形式(Unicode Representations of Strings)
|
||||
|
||||
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种编码格式编码。每一个字符串中的小块编码都被称为代码单元。这些包括 UTF-8 编码格式(编码字符串为8位的代码单元), UTF-16 编码格式(编码字符串位16位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。
|
||||
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种`编码格式`(encoding forms)编码。每一个字符串中的小块编码都被称`代码单元`(code units)。这些包括 UTF-8 编码格式(编码字符串为8位的代码单元), UTF-16 编码格式(编码字符串位16位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。
|
||||
|
||||
Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。
|
||||
您可以利用`for-in`来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个`Character`值。
|
||||
@ -510,13 +525,12 @@ Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式
|
||||
* UTF-16 代码单元集合 (利用字符串的`utf16`属性进行访问)
|
||||
* 21位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式 (利用字符串的`unicodeScalars`属性进行访问)
|
||||
|
||||
下面由`D``o``g``‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和`<EFBFBD>`(`DOG FACE`,Unicode 标量为`U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
|
||||
下面由`D`,`o`,`g`,`‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和`🐶`(`DOG FACE`,Unicode 标量为`U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
|
||||
|
||||
```swift
|
||||
let dogString = "Dog‼🐶"
|
||||
```
|
||||
|
||||
|
||||
<a name="UTF-8_representation"></a>
|
||||
### UTF-8 表示
|
||||
|
||||
@ -629,7 +643,7 @@ print("")
|
||||
### Unicode 标量表示 (Unicode Scalars Representation)
|
||||
|
||||
您可以通过遍历`String`值的`unicodeScalars`属性来访问它的 Unicode 标量表示。
|
||||
其为`UnicodeScalarView`类型的属性,`UnicodeScalarView`是`UnicodeScalar`的集合。
|
||||
其为`UnicodeScalarView`类型的属性,`UnicodeScalarView`是`UnicodeScalar`类型的值的集合。
|
||||
`UnicodeScalar`是21位的 Unicode 代码点。
|
||||
|
||||
每一个`UnicodeScalar`拥有一个`value`属性,可以返回对应的21位数值,用`UInt32`来表示:
|
||||
@ -645,7 +659,7 @@ print("")
|
||||
<td>🐶<br>U+1F436</td>
|
||||
</tr>
|
||||
<tr height="77">
|
||||
<td height="77">UTF-16<br>Code Unit</td>
|
||||
<td height="77">Unicode Scalar<br>Code Unit</td>
|
||||
<td>68</td>
|
||||
<td>111</td>
|
||||
<td>103</td>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -5,36 +5,40 @@
|
||||
> 翻译:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao)
|
||||
> 校对:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0,2.1
|
||||
> 2.0
|
||||
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
|
||||
> 定稿:[shanksyang](http//codebuild.me)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Prayer](https://github.com/futantan)
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.2
|
||||
> 翻译:[LinusLing](https://github.com/linusling)
|
||||
> 校对:[SketchK](https://github.com/SketchK)
|
||||
|
||||
> 3.0
|
||||
> 翻译:[Realank](https://github.com/realank) 2016-09-13
|
||||
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [For 循环](#for_loops)
|
||||
- [For-In 循环](#for_in_loops)
|
||||
- [While 循环](#while_loops)
|
||||
- [条件语句](#conditional_statement)
|
||||
- [控制转移语句(Control Transfer Statements)](#control_transfer_statements)
|
||||
- [提前退出](#early_exit)
|
||||
- [检测API可用性](#checking_api_availability)
|
||||
- [检测 API 可用性](#checking_api_availability)
|
||||
|
||||
Swift提供了类似 C 语言的流程控制结构,包括可以多次执行任务的`for`和`while`循环,基于特定条件选择执行不同代码分支的`if`、`guard`和`switch`语句,还有控制流程跳转到其他代码的`break`和`continue`语句。
|
||||
Swift提供了多种流程控制结构,包括可以多次执行任务的`while`循环,基于特定条件选择执行不同代码分支的`if`、`guard`和`switch`语句,还有控制流程跳转到其他代码位置的`break`和`continue`语句。
|
||||
|
||||
除了 C 语言里面传统的 for 循环,Swift 还增加了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。
|
||||
Swift 还提供了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。
|
||||
|
||||
Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿的情况。case 还可以匹配更多的类型模式,包括区间匹配(range matching),元组(tuple)和特定类型的描述。`switch`的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由`where`分句描述更复杂的匹配条件。
|
||||
Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿的情况。case 还可以匹配很多不同的模式,包括间隔匹配(interval match),元组(tuple)和转换到特定类型。`switch`语句的 case 中匹配的值可以绑定成临时常量或变量,在case体内使用,也可以用`where`来描述更复杂的匹配条件。
|
||||
|
||||
<a name="for_loops"></a>
|
||||
## For 循环
|
||||
<a name="for_in_loops"></a>
|
||||
## For-In 循环
|
||||
|
||||
Swift 提供两种`for`循环形式以来按照指定的次数多次执行一系列语句:
|
||||
|
||||
* `for-in`循环对一个集合里面的每个元素执行一系列语句。
|
||||
* for 循环,用来重复执行一系列语句直到达成特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。
|
||||
|
||||
<a name="for_in"></a>
|
||||
### For-In
|
||||
|
||||
你可以使用`for-in`循环来遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。
|
||||
你可以使用`for-in`循环来遍历一个集合中的所有元素,例如数字范围、数组中的元素或者字符串中的字符。
|
||||
|
||||
下面的例子用来输出乘 5 乘法表前面一部分内容:
|
||||
|
||||
@ -49,11 +53,11 @@ for index in 1...5 {
|
||||
// 5 times 5 is 25
|
||||
```
|
||||
|
||||
例子中用来进行遍历的元素是一组使用闭区间操作符(`...`)表示的从`1`到`5`的数字。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`print(_:separator:terminator:)`函数会再执行一次。整个过程会进行到闭区间结尾为止。
|
||||
例子中用来进行遍历的元素是使用闭区间操作符(`...`)表示的从`1`到`5`的数字区间。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表的结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`print(_:separator:terminator:)`函数会再执行一次。整个过程会进行到闭区间结尾为止。
|
||||
|
||||
上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。
|
||||
|
||||
如果你不需要知道区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略对值的访问:
|
||||
如果你不需要区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略这个值:
|
||||
|
||||
```swift
|
||||
let base = 3
|
||||
@ -66,7 +70,7 @@ print("\(base) to the power of \(power) is \(answer)")
|
||||
// 输出 "3 to the power of 10 is 59049"
|
||||
```
|
||||
|
||||
这个例子计算 base 这个数的 power 次幂(本例中,是`3`的`10`次幂),从`1`(`3`的`0`次幂)开始做`3`的乘法, 进行`10`次,使用`1`到`10`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略具体的值,并且不提供循环遍历时对值的访问。
|
||||
这个例子计算 base 这个数的 power 次幂(本例中,是`3`的`10`次幂),从`1`(`3`的`0`次幂)开始做`3`的乘法, 进行`10`次,使用`1`到`10`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。
|
||||
|
||||
使用`for-in`遍历一个数组所有元素:
|
||||
|
||||
@ -95,55 +99,10 @@ for (animalName, legCount) in numberOfLegs {
|
||||
|
||||
字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](./04_Collection_Types.html)。
|
||||
|
||||
<a name="for"></a>
|
||||
### For
|
||||
|
||||
除了`for-in`循环,Swift 提供使用条件判断和递增方法的标准 C 样式`for`循环:
|
||||
|
||||
```swift
|
||||
for var index = 0; index < 3; ++index {
|
||||
print("index is \(index)")
|
||||
}
|
||||
// index is 0
|
||||
// index is 1
|
||||
// index is 2
|
||||
```
|
||||
|
||||
下面是一般情况下这种循环方式的格式:
|
||||
|
||||
> for `initialization`; `condition`; `increment` {
|
||||
> `statements`
|
||||
> }
|
||||
|
||||
和 C 语言中一样,分号将循环的定义分为 3 个部分,不同的是,Swift 不需要使用圆括号将“initialization; condition; increment”包括起来。
|
||||
|
||||
这个循环执行流程如下:
|
||||
|
||||
1. 循环首次启动时,*初始化表达式( initialization expression )*被调用一次,用来初始化循环所需的所有常量和变量。
|
||||
2. *条件表达式(condition expression)*被调用,如果表达式调用结果为`false`,循环结束,继续执行`for`循环关闭大括号(`}`)之后的代码。如果表达式调用结果为`true`,则会执行大括号内部的代码。
|
||||
3. 执行所有语句之后,执行*递增表达式(increment expression)*。通常会增加或减少计数器的值,或者根据语句输出来修改某一个初始化的变量。当递增表达式运行完成后,重复执行第 2 步,条件表达式会再次执行。
|
||||
|
||||
|
||||
在初始化表达式中声明的常量和变量(比如`var index = 0`)只在`for`循环的生命周期里有效。如果想在循环结束后访问`index`的值,你必须要在循环生命周期开始前声明`index`。
|
||||
|
||||
```swift
|
||||
var index: Int
|
||||
for index = 0; index < 3; ++index {
|
||||
print("index is \(index)")
|
||||
}
|
||||
// index is 0
|
||||
// index is 1
|
||||
// index is 2
|
||||
print("The loop statements were executed \(index) times")
|
||||
// 输出 "The loop statements were executed 3 times
|
||||
```
|
||||
|
||||
注意`index`在循环结束后最终的值是`3`而不是`2`。最后一次调用递增表达式`++index`会将`index`设置为`3`,从而导致`index < 3`条件为`false`,并终止循环。
|
||||
|
||||
<a name="while_loops"></a>
|
||||
## While 循环
|
||||
|
||||
`while`循环运行一系列语句直到条件变成`false`。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种`while`循环形式:
|
||||
`while`循环会一直运行一段语句直到条件变成`false`。这类循环适合使用在第一次迭代前,迭代次数未知的情况下。Swift 提供两种`while`循环形式:
|
||||
|
||||
* `while`循环,每次在循环开始时计算条件是否符合;
|
||||
* `repeat-while`循环,每次在循环结束时计算条件是否符合。
|
||||
@ -151,22 +110,24 @@ print("The loop statements were executed \(index) times")
|
||||
<a name="while"></a>
|
||||
###While
|
||||
|
||||
`while`循环从计算单一条件开始。如果条件为`true`,会重复运行一系列语句,直到条件变为`false`。
|
||||
`while`循环从计算一个条件开始。如果条件为`true`,会重复运行一段语句,直到条件变为`false`。
|
||||
|
||||
下面是一般情况下 `while` 循环格式:
|
||||
下面是 `while` 循环的一般格式:
|
||||
|
||||
> while `condition` {
|
||||
> `statements`
|
||||
> }
|
||||
```
|
||||
while condition {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子来玩一个叫做蛇和梯子的小游戏,也叫做滑道和梯子:
|
||||
|
||||

|
||||

|
||||
|
||||
游戏的规则如下:
|
||||
|
||||
* 游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格;
|
||||
* 每一轮,你通过掷一个 6 边的骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示;
|
||||
* 每一轮,你通过掷一个六面体骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示;
|
||||
* 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去;
|
||||
* 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。
|
||||
|
||||
@ -174,26 +135,27 @@ print("The loop statements were executed \(index) times")
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
|
||||
var board = [Int](repeating: 0, count: finalSquare + 1)
|
||||
```
|
||||
|
||||
一些方块被设置成有蛇或者梯子的指定值。梯子底部的方块是一个正值,使你可以向上移动,蛇头处的方块是一个负值,会让你向下移动:
|
||||
一些方格被设置成特定的值来表示有蛇或者梯子。梯子底部的方格是一个正值,使你可以向上移动,蛇头处的方格是一个负值,会让你向下移动:
|
||||
|
||||
```swift
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
```
|
||||
|
||||
3 号方块是梯子的底部,会让你向上移动到 11 号方格,我们使用`board[03]`等于`+08`(来表示`11`和`3`之间的差值)。使用一元加运算符(`+i`)是为了和一元减运算符(`-i`)对称,为了让盘面代码整齐,小于 10 的数字都使用 0 补齐(这些风格上的调整都不是必须的,只是为了让代码看起来更加整洁)。
|
||||
3 号方格是梯子的底部,会让你向上移动到 11 号方格,我们使用`board[03]`等于`+08`(来表示`11`和`3`之间的差值)。使用一元正运算符(`+i`)是为了和一元负运算符(`-i`)对称,为了让盘面代码整齐,小于 10 的数字都使用 0 补齐(这些风格上的调整都不是必须的,只是为了让代码看起来更加整洁)。
|
||||
|
||||
玩家由左下角编号为 0 的方格开始游戏。一般来说玩家第一次掷骰子后才会进入游戏盘面:
|
||||
玩家由左下角空白处编号为 0 的方格开始游戏。玩家第一次掷骰子后才会进入游戏盘面:
|
||||
|
||||
```swift
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
while square < finalSquare {
|
||||
// 掷骰子
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
diceRoll += 1
|
||||
if diceRoll == 7 { diceRoll = 1 }
|
||||
// 根据点数移动
|
||||
square += diceRoll
|
||||
if square < board.count {
|
||||
@ -204,36 +166,39 @@ while square < finalSquare {
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值使用前置自增操作符(`++i`)来自增 1 ,然后检测是否超出了最大值。`++diceRoll`调用完成_后_,返回值等于`diceRoll`自增后的值。任何时候如果`diceRoll`的值等于7时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是`1`,`2`,`3`,`4`,`5`,`6`,`1`,`2`。
|
||||
本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值增加 1 ,然后检测是否超出了最大值。当`diceRoll`的值等于 7 时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是 `1` ,`2`,`3`,`4`,`5`,`6`,`1`,`2` 等。
|
||||
|
||||
掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏结束,相应地,代码会在`square`增加`board[square]`的值向前或向后移动(遇到了梯子或者蛇)之前,检测`square`的值是否小于`board`的`count`属性。
|
||||
掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏将会结束,为了应对这种情况,代码会首先判断`square`的值是否小于`board`的`count`属性,只有小于才会在`board[square]`上增加`square`,来向前或向后移动(遇到了梯子或者蛇)。
|
||||
|
||||
如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。例如如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。
|
||||
> 注意:
|
||||
> 如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。
|
||||
|
||||
当本轮`while`循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为`false`,此时游戏结束。
|
||||
|
||||
`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。
|
||||
`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏要跑多久,只有在达成指定条件时循环才会结束。
|
||||
|
||||
|
||||
<a name="repeat_while"></a>
|
||||
###Repeat-While
|
||||
|
||||
`while`循环的另外一种形式是`repeat-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为`false`。
|
||||
`while`循环的另外一种形式是`repeat-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块。然后重复循环直到条件为`false`。
|
||||
|
||||
> 注意:
|
||||
> Swift语言的`repeat-while`循环合其他语言中的`do-while`循环是类似的。
|
||||
> Swift语言的`repeat-while`循环和其他语言中的`do-while`循环是类似的。
|
||||
|
||||
下面是一般情况下 `repeat-while`循环的格式:
|
||||
下面是 `repeat-while`循环的一般格式:
|
||||
|
||||
> repeat {
|
||||
> `statements`
|
||||
> } while `condition`
|
||||
```swift
|
||||
repeat {
|
||||
statements
|
||||
} while condition
|
||||
```
|
||||
|
||||
还是蛇和梯子的游戏,使用`repeat-while`循环来替代`while`循环。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循环一样:
|
||||
还是蛇和梯子的游戏,使用`repeat-while`循环来替代`while`循环。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循环时一样:
|
||||
|
||||
``` swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
|
||||
var board = [Int](repeating: 0, count: finalSquare + 1)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
@ -249,7 +214,8 @@ repeat {
|
||||
// 顺着梯子爬上去或者顺着蛇滑下去
|
||||
square += board[square]
|
||||
// 掷骰子
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
diceRoll += 1
|
||||
if diceRoll == 7 { diceRoll = 1 }
|
||||
// 根据点数移动
|
||||
square += diceRoll
|
||||
} while square < finalSquare
|
||||
@ -263,14 +229,14 @@ print("Game over!")
|
||||
<a name="conditional_statement"></a>
|
||||
## 条件语句
|
||||
|
||||
根据特定的条件执行特定的代码通常是十分有用的,例如:当错误发生时,你可能想运行额外的代码;或者,当输入的值太大或太小时,向用户显示一条消息等。要实现这些功能,你就需要使用*条件语句*。
|
||||
根据特定的条件执行特定的代码通常是十分有用的。当错误发生时,你可能想运行额外的代码;或者,当值太大或太小时,向用户显示一条消息。要实现这些功能,你就需要使用*条件语句*。
|
||||
|
||||
Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、可能情况较多且需要用到模式匹配(pattern-matching)的情境。
|
||||
Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、有更多排列组合的时候。并且`switch`在需要用到模式匹配(pattern-matching)的情况下会更有用。
|
||||
|
||||
<a name="if"></a>
|
||||
### If
|
||||
|
||||
`if`语句最简单的形式就是只包含一个条件,当且仅当该条件为`true`时,才执行相关代码:
|
||||
`if`语句最简单的形式就是只包含一个条件,只有该条件为`true`时,才执行相关代码:
|
||||
|
||||
```swift
|
||||
var temperatureInFahrenheit = 30
|
||||
@ -282,7 +248,7 @@ if temperatureInFahrenheit <= 32 {
|
||||
|
||||
上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行`if`块后面的代码。
|
||||
|
||||
当然,`if`语句允许二选一,也就是当条件为`false`时,执行 *else 语句*:
|
||||
当然,`if`语句允许二选一执行,叫做`else`从句。也就是当条件为`false`时,执行 *else 语句*:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 40
|
||||
@ -294,9 +260,9 @@ if temperatureInFahrenheit <= 32 {
|
||||
// 输出 "It's not that cold. Wear a t-shirt."
|
||||
```
|
||||
|
||||
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾——因此,`else`分支就被触发了。
|
||||
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾。因此,`else`分支就被触发了。
|
||||
|
||||
你可以把多个`if`语句链接在一起,像下面这样:
|
||||
你可以把多个`if`语句链接在一起,来实现更多分支:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 90
|
||||
@ -312,10 +278,10 @@ if temperatureInFahrenheit <= 32 {
|
||||
|
||||
在上面的例子中,额外的`if`语句用于判断是不是特别热。而最后的`else`语句被保留了下来,用于打印既不冷也不热时的消息。
|
||||
|
||||
实际上,最后的`else`语句是可选的:
|
||||
实际上,当不需要完整判断情况的时候,最后的`else`语句是可选的:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 90
|
||||
temperatureInFahrenheit = 72
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
} else if temperatureInFahrenheit >= 86 {
|
||||
@ -332,46 +298,49 @@ if temperatureInFahrenheit <= 32 {
|
||||
|
||||
`switch`语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较:
|
||||
|
||||
> switch `some value to consider` {
|
||||
> case `value 1`:
|
||||
> `respond to value 1`
|
||||
> case `value 2`,
|
||||
> `value 3`:
|
||||
> `respond to value 2 or 3`
|
||||
> default:
|
||||
> `otherwise, do something else`
|
||||
> }
|
||||
```swift
|
||||
switch some value to consider {
|
||||
case value 1:
|
||||
respond to value 1
|
||||
case value 2,
|
||||
value 3:
|
||||
respond to value 2 or 3
|
||||
default:
|
||||
otherwise, do something else
|
||||
}
|
||||
```
|
||||
|
||||
`switch`语句都由*多个 case* 构成。为了匹配某些更特定的值,Swift 提供了几种更复杂的匹配模式,这些模式将在本节的稍后部分提到。
|
||||
|
||||
每一个 case 都是代码执行的一条分支,这与`if`语句类似。与之不同的是,`switch`语句会决定哪一条分支应该被执行。
|
||||
`switch`语句由*多个 case* 构成,每个由`case`关键字开始。为了匹配某些更特定的值,Swift 提供了几种方法来进行更复杂的模式匹配,这些模式将在本节的稍后部分提到。
|
||||
|
||||
`switch`语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支满足该要求,这个默认分支必须在`switch`语句的最后面。
|
||||
与`if`语句类似,每一个 case 都是代码执行的一条分支。`switch`语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值*切换(switching)*。
|
||||
|
||||
`switch`语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支来涵盖其它所有没有对应的值,这个默认分支必须在`switch`语句的最后面。
|
||||
|
||||
下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符:
|
||||
|
||||
|
||||
```swift
|
||||
let someCharacter: Character = "e"
|
||||
let someCharacter: Character = "z"
|
||||
switch someCharacter {
|
||||
case "a", "e", "i", "o", "u":
|
||||
print("\(someCharacter) is a vowel")
|
||||
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
|
||||
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
|
||||
print("\(someCharacter) is a consonant")
|
||||
case "a":
|
||||
print("The first letter of the alphabet")
|
||||
case "z":
|
||||
print("The last letter of the alphabet")
|
||||
default:
|
||||
print("\(someCharacter) is not a vowel or a consonant")
|
||||
print("Some other character")
|
||||
}
|
||||
// 输出 "e is a vowel"
|
||||
// Prints "The last letter of the alphabet"
|
||||
```
|
||||
|
||||
在这个例子中,第一个 case 分支用于匹配五个元音,第二个 case 分支用于匹配所有的辅音。
|
||||
在这个例子中,第一个 case 分支用于匹配第一个英文字母`a`,第二个 case 分支用于匹配最后一个字母`z`。
|
||||
因为`switch`语句必须有一个case分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以switch语句使用`default`分支来匹配除了`a`和`z`外的所有值,这个分支保证了swith语句的完备性。
|
||||
|
||||
由于为其它可能的字符写 case 分支没有实际的意义,因此在这个例子中使用了默认分支来处理剩下的既不是元音也不是辅音的字符——这就保证了`switch`语句的完备性。
|
||||
|
||||
<a name="no_implicit_fallthrough"></a>
|
||||
#### 不存在隐式的贯穿(No Implicit Fallthrough)
|
||||
|
||||
与 C 语言和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。
|
||||
与 C 和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。
|
||||
|
||||
> 注意:
|
||||
虽然在Swift中`break`不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用`break`跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。
|
||||
@ -381,34 +350,39 @@ default:
|
||||
```swift
|
||||
let anotherCharacter: Character = "a"
|
||||
switch anotherCharacter {
|
||||
case "a":
|
||||
case "a": // Invalid, the case has an empty body
|
||||
case "A":
|
||||
print("The letter A")
|
||||
default:
|
||||
print("Not the letter A")
|
||||
}
|
||||
// this will report a compile-time error
|
||||
// This will report a compile-time error.
|
||||
```
|
||||
|
||||
不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会同时匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": does not contain any executable statements`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。
|
||||
不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会一起匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": 不包含任何可执行语句`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。
|
||||
|
||||
一个 case 也可以包含多个模式,用逗号把它们分开(如果太长了也可以分行写):
|
||||
|
||||
> switch `some value to consider` {
|
||||
> case `value 1`,
|
||||
> `value 2`:
|
||||
> `statements`
|
||||
> }
|
||||
为了让单个case同时匹配`a`和`A`,可以将这个两个值组合成一个复合匹配,并且用逗号分开:
|
||||
```swift
|
||||
let anotherCharacter: Character = "a"
|
||||
switch anotherCharacter {
|
||||
case "a", "A":
|
||||
print("The letter A")
|
||||
default:
|
||||
print("Not the letter A")
|
||||
}
|
||||
// Prints "The letter A
|
||||
```
|
||||
为了可读性,符合匹配可以写成多行形式,详情请参考[复合匹配(Compound Cases)](#compound_cases)
|
||||
|
||||
> 注意:
|
||||
如果想要贯穿至特定的 case 分支中,请使用`fallthrough`语句,详情请参考[贯穿(Fallthrough)](#fallthrough)。
|
||||
如果想要显式贯穿case分支,请使用`fallthrough`语句,详情请参考[贯穿(Fallthrough)](#fallthrough)。
|
||||
|
||||
<a name="interval_matching"></a>
|
||||
#### 区间匹配
|
||||
|
||||
case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式:
|
||||
|
||||
```
|
||||
```swift
|
||||
let approximateCount = 62
|
||||
let countedThings = "moons orbiting Saturn"
|
||||
var naturalCount: String
|
||||
@ -430,10 +404,9 @@ print("There are \(naturalCount) \(countedThings).")
|
||||
// 输出 "There are dozens of moons orbiting Saturn."
|
||||
```
|
||||
|
||||
在上例中,`approximateCount`在一个`switch`声明中被估值。每一个`case`都与之进行比较。因为`approximateCount`落在了12到100的区间,所以`naturalCount`等于`"dozens of"`值,并且此后这段执行跳出了`switch`声明。
|
||||
在上例中,`approximateCount`在一个`switch`声明中被评估。每一个`case`都与之进行比较。因为`approximateCount`落在了 12 到 100 的区间,所以`naturalCount`等于`"dozens of"`值,并且此后的执行跳出了`switch`语句。
|
||||
|
||||
|
||||
> 注意:
|
||||
> 闭区间操作符(`...`)以及半开区间操作符(`..<`)功能被重载去返回`IntervalType`或`Range`。一个区间可以决定他是否包含特定的元素,就像当匹配一个`switch`声明的`case`一样。区间是一个连续值的集合,可以用`for-in`语句遍历它。
|
||||
|
||||
<a name="tuples"></a>
|
||||
#### 元组(Tuple)
|
||||
@ -459,17 +432,17 @@ default:
|
||||
// 输出 "(1, 1) is inside the box"
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在黄色y轴上,是否在一个以原点为中心的4x4的矩形里,或者在这个矩形外面。
|
||||
在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在橘黄色的y轴上,是否在一个以原点为中心的4x4的蓝色矩形里,或者在这个矩形外面。
|
||||
|
||||
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配(0, 0)的 case 分支都会被忽视掉。
|
||||
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。
|
||||
|
||||
|
||||
<a name="value_bindings"></a>
|
||||
#### 值绑定(Value Bindings)
|
||||
|
||||
case 分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该 case 分支里就可以被引用了——这种行为被称为*值绑定*(value binding)。
|
||||
case 分支允许将匹配的值绑定到一个临时的常量或变量,并且在case分支体内使用 —— 这种行为被称为*值绑定*(value binding),因为匹配的值在case分支体内,与临时的常量或变量绑定。
|
||||
|
||||
下面的例子展示了如何在一个`(Int, Int)`类型的元组中使用值绑定来分类下图中的点(x, y):
|
||||
|
||||
@ -486,18 +459,16 @@ case let (x, y):
|
||||
// 输出 "on the x-axis with an x value of 2"
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在黄色y轴上,或者不在坐标轴上。
|
||||
在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在橘黄色的y轴上,或者不在坐标轴上。
|
||||
|
||||
这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。
|
||||
|
||||
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,它们用于简化`print(_:)`的书写。
|
||||
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里使用。在这个例子中,它们用于打印给定点的类型。
|
||||
|
||||
请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。
|
||||
|
||||
在上面的例子中,`x`和`y`是常量,这是因为没有必要在其对应的 case 分支中修改它们的值。然而,它们也可以是变量——程序将会创建临时变量,并用相应的值初始化它。修改这些变量只会影响其对应的 case 分支。
|
||||
|
||||
<a name="where"></a>
|
||||
#### Where
|
||||
|
||||
@ -518,18 +489,52 @@ case let (x, y):
|
||||
// 输出 "(1, -1) is on the line x == -y"
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
在上面的例子中,`switch`语句会判断某个点是否在绿色的对角线`x == y`上,是否在紫色的对角线`x == -y`上,或者不在对角线上。
|
||||
|
||||
这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`yetAnotherPoint`的两个值。这些常量被用作`where`语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当`where`语句的条件为`true`时,匹配到的 case 分支才会被执行。
|
||||
这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`yetAnotherPoint`的两个值。这两个常量被用作`where`语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当`where`语句的条件为`true`时,匹配到的 case 分支才会被执行。
|
||||
|
||||
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch`语句就已经完备了,因此不需要再书写默认分支。
|
||||
|
||||
<a name="compound_cases"></a>
|
||||
#### 复合匹配(Compound Cases)
|
||||
|
||||
当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个case后面,并且用逗号隔开。当case后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写:
|
||||
|
||||
```swift
|
||||
let someCharacter: Character = "e"
|
||||
switch someCharacter {
|
||||
case "a", "e", "i", "o", "u":
|
||||
print("\(someCharacter) is a vowel")
|
||||
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
|
||||
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
|
||||
print("\(someCharacter) is a consonant")
|
||||
default:
|
||||
print("\(someCharacter) is not a vowel or a consonant")
|
||||
}
|
||||
```
|
||||
|
||||
这个switch语句中的第一个case,匹配了英语中的五个小写原因字母。相似的,第二个case匹配了英语中所有的小写辅音字母。最终,default分支匹配了其它所有字符。
|
||||
复合匹配同样可以包含值绑定。复合匹配里所有的匹配模式,都必须包含相同的值绑定。并且每一个绑定都必须获取到相同类型的值。这保证了,无论复合匹配中的哪个模式发生了匹配,分支体内的代码,都能获取到绑定的值,并且绑定的值都有一样的类型。
|
||||
|
||||
```swift
|
||||
let stillAnotherPoint = (9, 0)
|
||||
switch stillAnotherPoint {
|
||||
case (let distance, 0), (0, let distance):
|
||||
print("On an axis, \(distance) from the origin")
|
||||
default:
|
||||
print("Not on an axis")
|
||||
}
|
||||
```
|
||||
|
||||
上面的case有两个模式:`(let distance, 0)`匹配了在x轴上的值,`(0, let distance)`匹配了在y轴上的值。两个模式都绑定了`distance`,并且`distance`在两种模式下,都是整型——这意味着分支体内的代码,只要case匹配,都可以获取到`distance`值
|
||||
|
||||
|
||||
<a name="control_transfer_statements"></a>
|
||||
## 控制转移语句(Control Transfer Statements)
|
||||
|
||||
控制转移语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift有四种控制转移语句。
|
||||
控制转移语句改变你代码的执行顺序,通过它可以实现代码的跳转。Swift 有五种控制转移语句:
|
||||
|
||||
- `continue`
|
||||
- `break`
|
||||
@ -537,15 +542,12 @@ case let (x, y):
|
||||
- `return`
|
||||
- `throw`
|
||||
|
||||
我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](./06_Functions.html)章节讨论,`throw`语句会在[错误抛出](./18_Error_Handling.html#throwing_errors)
|
||||
我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](./06_Functions.html)章节讨论,`throw`语句会在[错误抛出](./18_Error_Handling.html#throwing_errors)章节讨论。
|
||||
|
||||
<a name="continue"></a>
|
||||
### Continue
|
||||
|
||||
`continue`语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。
|
||||
|
||||
> 注意:
|
||||
> 在一个带有条件和递增的for循环体中,调用`continue`语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。
|
||||
`continue`语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。
|
||||
|
||||
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句:
|
||||
|
||||
@ -564,7 +566,7 @@ print(puzzleOutput)
|
||||
// 输出 "grtmndsthnklk"
|
||||
```
|
||||
|
||||
在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环迭代结束,从新开始下次循环迭代。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
|
||||
在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环结束,重新开始下次循环。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
|
||||
|
||||
<a name="break"></a>
|
||||
### Break
|
||||
@ -574,7 +576,7 @@ print(puzzleOutput)
|
||||
<a name="break_in_a_loop_statement"></a>
|
||||
#### 循环语句中的 break
|
||||
|
||||
当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环迭代的代码被执行,也不会再有下次的循环迭代产生。
|
||||
当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环的代码被执行,也不会再有下次的循环产生。
|
||||
|
||||
<a name="break_in_a_switch_statement"></a>
|
||||
#### Switch 语句中的 break
|
||||
@ -584,7 +586,7 @@ print(puzzleOutput)
|
||||
这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的`switch`需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上`break`语句。当那个分支被匹配到时,分支内的`break`语句立即结束`switch`代码块。
|
||||
|
||||
>注意:
|
||||
当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你总是可以使用`break`来忽略某个分支。
|
||||
当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你应该使用`break`来忽略某个分支。
|
||||
|
||||
下面的例子通过`switch`来判断一个`Character`值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
|
||||
|
||||
@ -613,14 +615,14 @@ if let integerValue = possibleIntegerValue {
|
||||
|
||||
这个例子检查`numberSymbol`是否是拉丁,阿拉伯,中文或者泰语中的`1`到`4`之一。如果被匹配到,该`switch`分支语句给`Int?`类型变量`possibleIntegerValue`设置一个整数值。
|
||||
|
||||
当`switch`代码块执行完后,接下来的代码通过使用可选绑定来判断`possibleIntegerValue`是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue`有一个隐式的初始值`nil`,所以仅仅当`possibleIntegerValue`曾被`switch`代码块的前四个分支中的某个设置过一个值时,可选的绑定将会被判定为成功。
|
||||
当`switch`代码块执行完后,接下来的代码通过使用可选绑定来判断`possibleIntegerValue`是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue`有一个隐式的初始值`nil`,所以仅仅当`possibleIntegerValue`曾被`switch`代码块的前四个分支中的某个设置过一个值时,可选的绑定才会被判定为成功。
|
||||
|
||||
在上面的例子中,想要把`Character`所有的的可能性都枚举出来是不现实的,所以使用`default`分支来包含所有上面没有匹配到字符的情况。由于这个`default`分支不需要执行任何动作,所以它只写了一条`break`语句。一旦落入到`default`分支中后,`break`语句就完成了该分支的所有代码操作,代码继续向下,开始执行`if let`语句。
|
||||
|
||||
<a name="fallthrough"></a>
|
||||
### 贯穿(Fallthrough)
|
||||
|
||||
Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显示的插入`break`语句到每个`switch`分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
|
||||
Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显式地插入`break`语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
|
||||
|
||||
如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。
|
||||
|
||||
@ -638,29 +640,29 @@ print(description)
|
||||
// 输出 "The number 5 is a prime number, and also an integer."
|
||||
```
|
||||
|
||||
这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数添加一段文字在`description`后,来表明这个是数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支添加一段额外的文字在`description`的最后,至此`switch`代码块执行完了。
|
||||
这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数在`description`后添加一段文字,来表明这个数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支在`description`的最后添加一段额外的文字,至此`switch`代码块执行完了。
|
||||
|
||||
如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到包含所有的`default`分支中。
|
||||
如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到`default`分支中。
|
||||
|
||||
当`switch`代码块执行完后,使用`print`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。
|
||||
当`switch`代码块执行完后,使用`print(_:separator:terminator:)`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。
|
||||
|
||||
> 注意:
|
||||
> `fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的`switch`语句特性是一样的。
|
||||
> `fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的`switch`语句特性是一样的。
|
||||
|
||||
<a name="labeled_statements"></a>
|
||||
### 带标签的语句
|
||||
|
||||
在 Swift 中,你可以在循环体和`switch`代码块中嵌套循环体和`switch`代码块来创造复杂的控制流结构。然而,循环体和`switch`代码块两者都可以使用`break`语句来提前结束整个方法体。因此,显示地指明`break`语句想要终止的是哪个循环体或者`switch`代码块,会很有用。类似地,如果你有许多嵌套的循环体,显示指明`continue`语句想要影响哪一个循环体也会非常有用。
|
||||
在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用`break`语句来提前结束整个代码块。因此,显式地指明`break`语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明`continue`语句想要影响哪一个循环体也会非常有用。
|
||||
|
||||
为了实现这个目的,你可以使用标签来标记一个循环体或者`switch`代码块,当使用`break`或者`continue`时,带上这个标签,可以控制该标签代表对象的中断或者执行。
|
||||
为了实现这个目的,你可以使用标签(*statement label*)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用`break`加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用`break`或者`continue`加标签,来结束或者继续这条被标记语句的执行。
|
||||
|
||||
产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。下面是一个`while`循环体的语法,同样的规则适用于所有的循环体和`switch`代码块。
|
||||
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor keyword),并且该标签后面跟随一个冒号。下面是一个针对`while`循环体的标签语法,同样的规则适用于所有的循环体和条件语句。
|
||||
|
||||
> `label name`: while `condition` {
|
||||
> `statements`
|
||||
> }
|
||||
|
||||
下面的例子是在一个带有标签的`while`循环体中调用`break`和`continue`语句,该循环体是前面章节中*蛇和梯子*的改编版本。这次,游戏增加了一条额外的规则:
|
||||
下面的例子是前面章节中*蛇和梯子*的适配版本,在此版本中,我们将使用一个带有标签的`while`循环体中调用`break`和`continue`语句。这次,游戏增加了一条额外的规则:
|
||||
|
||||
- 为了获胜,你必须*刚好*落在第 25 个方块中。
|
||||
|
||||
@ -668,35 +670,36 @@ print(description)
|
||||
|
||||
游戏的棋盘和之前一样:
|
||||
|
||||

|
||||

|
||||
|
||||
`finalSquare`、`board`、`square`和`diceRoll`值被和之前一样的方式初始化:
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
|
||||
var board = [Int](repeating: 0, count: finalSquare + 1)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
```
|
||||
|
||||
这个版本的游戏使用`while`循环体和`switch`方法块来实现游戏的逻辑。`while`循环体有一个标签名`gameLoop`,来表明它是蛇与梯子的主循环。
|
||||
这个版本的游戏使用`while`循环和`switch`语句来实现游戏的逻辑。`while`循环有一个标签名`gameLoop`,来表明它是游戏的主循环。
|
||||
|
||||
该`while`循环体的条件判断语句是`while square !=finalSquare`,这表明你必须刚好落在方格25中。
|
||||
|
||||
```swift
|
||||
gameLoop: while square != finalSquare {
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
diceRoll += 1
|
||||
if diceRoll == 7 { diceRoll = 1 }
|
||||
switch square + diceRoll {
|
||||
case finalSquare:
|
||||
// 到达最后一个方块,游戏结束
|
||||
// diceRoll will move us to the final square, so the game is over
|
||||
break gameLoop
|
||||
case let newSquare where newSquare > finalSquare:
|
||||
// 超出最后一个方块,再掷一次骰子
|
||||
// diceRoll will move us beyond the final square, so roll again
|
||||
continue gameLoop
|
||||
default:
|
||||
// 本次移动有效
|
||||
// this is a valid move, so find out its effect
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
}
|
||||
@ -704,20 +707,21 @@ gameLoop: while square != finalSquare {
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
|
||||
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`语句来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
|
||||
|
||||
- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,游戏结束。
|
||||
- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环的迭代,开始下一次循环迭代。
|
||||
- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动骰子数个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。本次循环迭代结束,控制跳转到`while`循环体的条件判断语句处,再决定是否能够继续执行下次循环迭代。
|
||||
- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,意味着游戏结束。
|
||||
- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环,开始下一次循环。
|
||||
- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动 `diceRoll` 个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。接着本次循环结束,控制跳转到`while`循环体的条件判断语句处,再决定是否需要继续执行下次循环。
|
||||
|
||||
>注意:
|
||||
如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`代码块而不是`while`循环体。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。
|
||||
如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`语句而不是`while`循环。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。
|
||||
同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。
|
||||
|
||||
<a name="early_exit"></a>
|
||||
## 提前退出
|
||||
|
||||
像`if`语句一样,`guard`的执行取决于一个表达式的布尔值。我们可以使用`guard`语句来要求条件必须为真时,以执行`guard`语句后的代码。不同于`if`语句,一个`guard`语句总是有一个`else`分句,如果条件不为真则执行`else`分局中的代码。
|
||||
像`if`语句一样,`guard`的执行取决于一个表达式的布尔值。我们可以使用`guard`语句来要求条件必须为真时,以执行`guard`语句后的代码。不同于`if`语句,一个`guard`语句总是有一个`else`从句,如果条件不为真则执行`else`从句中的代码。
|
||||
|
||||
|
||||
```swift
|
||||
func greet(person: [String: String]) {
|
||||
@ -725,7 +729,6 @@ func greet(person: [String: String]) {
|
||||
return
|
||||
}
|
||||
print("Hello \(name)")
|
||||
|
||||
guard let location = person["location"] else {
|
||||
print("I hope the weather is nice near you.")
|
||||
return
|
||||
@ -733,43 +736,45 @@ func greet(person: [String: String]) {
|
||||
print("I hope the weather is nice in \(location).")
|
||||
}
|
||||
greet(["name": "John"])
|
||||
// prints "Hello John!"
|
||||
// prints "I hope the weather is nice near you."
|
||||
// 输出 "Hello John!"
|
||||
// 输出 "I hope the weather is nice near you."
|
||||
greet(["name": "Jane", "location": "Cupertino"])
|
||||
// prints "Hello Jane!"
|
||||
// prints "I hope the weather is nice in Cupertino."
|
||||
// 输出 "Hello Jane!"
|
||||
// 输出 "I hope the weather is nice in Cupertino."
|
||||
```
|
||||
|
||||
如果`guard`语句的条件被满足,则在保护语句的封闭大括号结束后继续执行代码。任何使用了可选绑定作为条件的一部分并被分配了值的变量或常量对于剩下的保护语句出现的代码段是可用的。
|
||||
如果`guard`语句的条件被满足,则继续执行`guard`语句大括号后的代码。将变量或者常量的可选绑定作为`guard`语句的条件,都可以保护`guard`语句后面的代码。
|
||||
|
||||
如果条件不被满足,在`else`分支上的代码就会被执行。这个分支必须转移控制以退出`guard`语句出现的代码段。它可以用控制转移语句如`return`,`break`或`continue`做这件事,或者它调用了一个不返回的方法或函数,例如`fatalError()`。
|
||||
如果条件不被满足,在`else`分支上的代码就会被执行。这个分支必须转移控制以退出`guard`语句出现的代码段。它可以用控制转移语句如`return`,`break`,`continue`或者`throw`做这件事,或者调用一个不返回的方法或函数,例如`fatalError()`。
|
||||
|
||||
相比于可以实现同样功能的`if`语句,按需使用`guard`语句会提升我们代码的可靠性。
|
||||
它可以使你的代码连贯的被执行而不需要将它包在`else`块中,它可以使你处理违反要求的代码接近要求。
|
||||
相比于可以实现同样功能的`if`语句,按需使用`guard`语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在`else`块中,它可以使你在紧邻条件判断的地方,处理违规的情况。
|
||||
|
||||
<a name="checking_api_availability"></a>
|
||||
## 检测 API 可用性
|
||||
|
||||
Swift 有内置支持去检查接口的可用性的,这可以确保我们不会不小心地使用对于当前部署目标不可用的 API。
|
||||
Swift内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的API。
|
||||
|
||||
编译器使用 SDK 中的可用信息来验证在我们在可用部署目标指定项目的代码中所有的 API 调用。如果我们尝试使用一个不可用的 API,Swift 会在编译期报错。
|
||||
编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 API,Swift 会在编译时报错。
|
||||
|
||||
我们使用一个可用性条件在一个`if`或`guard`语句中去有条件的执行一段代码,这取决于我们想要使用的 API 是否在运行时是可用的。编译器使用从可用性条件语句中获取的信息,这时它会去验证在代码块中调用的 API 是否都可用。
|
||||
我们在`if`或`guard`语句中使用`可用性条件(availability condition)`去有条件的执行一段代码,来在运行时判断调用的API是否可用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 API 是否可用。
|
||||
|
||||
```swift
|
||||
if #available(iOS 9, OSX 10.10, *) {
|
||||
// 在 iOS 使用 iOS 9 APIs , 并且在 OS X 使用 OS X v10.10 APIs
|
||||
if #available(iOS 10, macOS 10.12, *) {
|
||||
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
|
||||
} else {
|
||||
// 回滚至早前 iOS and OS X 的API
|
||||
// 使用先前版本的 iOS 和 macOS 的 API
|
||||
}
|
||||
```
|
||||
|
||||
以上可用性条件指定在iOS,`if`段的代码仅仅在 iOS 9 及更高可运行;在 OS X,仅在 OS X v10.10 及更高可运行。最后一个参数,`*`,是必须的并且指定在任何其他平台上,`if`段的代码在最小可用部署目标指定项目中执行。
|
||||
以上可用性条件指定,在iOS中,`if`语句的代码块仅仅在 iOS 10 及更高的系统下运行;在 macOS中,仅在 macOS 10.12 及更高才会运行。最后一个参数,`*`,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行。
|
||||
|
||||
在它普遍的形式中,可用性条件获取了平台名字和版本的清单。平台名字可以是`iOS`,`OSX`或`watchOS`。除了特定的主板本号像 iOS 8,我们可以指定较小的版本号像 iOS 8.3 以及 OS X v10.10.3。
|
||||
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是`iOS`,`macOS`,`watchOS`和`tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8的主板本号,我们可以指定像iOS 8.3 以及 macOS 10.10.3的子版本号。
|
||||
|
||||
```swift
|
||||
if #available(platform name version, ..., *) {
|
||||
statements to execute if the APIs are available
|
||||
} else {
|
||||
fallback statements to execute if the APIs are unavailable
|
||||
}
|
||||
```
|
||||
|
||||
>if #available(`platform name` `version`, `...`, *) {
|
||||
> `statements to execute if the APIs are available`
|
||||
>} else {
|
||||
> `fallback statements to execute if the APIs are unavailable`
|
||||
>}
|
||||
|
||||
@ -8,163 +8,167 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[dreamkidd](https://github.com/dreamkidd)
|
||||
|
||||
本页包含内容:
|
||||
> 2.1
|
||||
> 翻译:[DianQK](https://github.com/DianQK)
|
||||
> 定稿:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-12
|
||||
|
||||
> 3.0
|
||||
> 翻译: [crayygy](https://github.com/crayygy) 2016-09-12
|
||||
|
||||
本页包含内容:
|
||||
- [函数定义与调用(Defining and Calling Functions)](#Defining_and_Calling_Functions)
|
||||
- [函数参数与返回值(Function Parameters and Return Values)](#Function_Parameters_and_Return_Values)
|
||||
- [函数参数名称(Function Parameter Names)](#Function_Parameter_Names)
|
||||
- [函数参数标签和参数名称 (Function Argument Labels and Parameter Names) ](#Function_Argument_Labels_and_Parameter_Names)
|
||||
- [函数类型(Function Types)](#Function_Types)
|
||||
- [函数嵌套(Nested Functions)](#Nested_Functions)
|
||||
- [嵌套函数(Nested Functions)](#Nested_Functions)
|
||||
|
||||
函数是用来完成特定任务的独立的代码块。你给一个函数起一个合适的名字,用来标识函数做什么,并且当函数需要执行的时候,这个名字会被“调用”。
|
||||
|
||||
Swift 统一的函数语法足够灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值可以被修改。
|
||||
`函数` 是一段完成特定任务的独立代码片段。你可以通过给函数命名来标识某个函数的功能,这个名字可以被用来在需要的时候"调用"这个函数来完成它的任务。
|
||||
|
||||
在 Swift 中,每个函数都有一种类型,包括函数的参数值类型和返回值类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
|
||||
Swift 统一的函数语法非常的灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值将被修改。
|
||||
|
||||
在 Swift 中,每个函数都有一个由函数的参数值类型和返回值类型组成的类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
|
||||
|
||||
<a name="Defining_and_Calling_Functions"></a>
|
||||
## 函数的定义与调用(Defining and Calling Functions)
|
||||
## 函数的定义与调用 (Defining and Calling Functions)
|
||||
|
||||
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入(称为参数,parameters),也可以定义某种类型的值作为函数执行结束的输出(称为返回类型)。
|
||||
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入(称为*参数*,*parameters*),也可以定义某种类型的值作为函数执行结束时的输出(称为 *返回* 类型,*return* type)。
|
||||
|
||||
每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,你用函数名“调用”,并传给它匹配的输入值(称作实参,arguments)。一个函数的实参必须与函数参数表里参数的顺序一致。
|
||||
每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作 *实参* ,*arguments*)。函数的实参必须与函数参数表里参数的顺序一致。
|
||||
|
||||
|
||||
下面例子中的函数的名字是`sayHello(_:)`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回向这个人问候的语句。为了完成这个任务,你需要定义一个输入参数——一个叫做 `personName` 的 `String` 值,和一个包含给这个人问候语的 `String` 类型的返回值:
|
||||
|
||||
在下面例子中的函数叫做`"sayHello(_:)"`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回给这个人的问候语。为了完成这个任务,你定义一个输入参数-一个叫做 `personName` 的 `String` 值,和一个包含给这个人问候语的 `String` 类型的返回值:
|
||||
|
||||
```swift
|
||||
func sayHello(personName: String) -> String {
|
||||
let greeting = "Hello, " + personName + "!"
|
||||
func greet(person: String) -> String {
|
||||
let greeting = "Hello, " + person + "!"
|
||||
return greeting
|
||||
}
|
||||
```
|
||||
|
||||
所有的这些信息汇总起来成为函数的定义,并以 `func` 作为前缀。指定函数返回类型时,用返回箭头 `->`(一个连字符后跟一个右尖括号)后跟返回类型的名称的方式来表示。
|
||||
所有的这些信息汇总起来成为函数的*定义*,并以 `func` 作为前缀。指定函数返回类型时,用返回箭头 `->`(一个连字符后跟一个右尖括号)后跟返回类型的名称的方式来表示。
|
||||
|
||||
该定义描述了函数做什么,它期望接收什么和执行结束时它返回的结果是什么类型。这样的定义使得函数可以在别的地方以一种清晰的方式被调用:
|
||||
该定义描述了函数的功能,它期望接收什么作为参数和执行结束时它返回的结果是什么类型。这样的定义使得函数可以在别的地方以一种清晰的方式被调用:
|
||||
|
||||
```swift
|
||||
print(sayHello("Anna"))
|
||||
// prints "Hello, Anna!"
|
||||
print(sayHello("Brian"))
|
||||
// prints "Hello, Brian!"
|
||||
print(greet(person: "Anna"))
|
||||
// 打印 "Hello, Anna!"
|
||||
print(greet(person: "Brian"))
|
||||
// 打印 "Hello, Brian!"
|
||||
```
|
||||
|
||||
调用 `sayHello(_:)` 函数时,在圆括号中传给它一个 `String` 类型的实参。因为这个函数返回一个 `String` 类型的值,`sayHello` 可以被包含在 `print` 的调用中,用来输出这个函数的返回值,正如上面所示。
|
||||
调用 `sayHello(_:)` 函数时,在圆括号中传给它一个 `String` 类型的实参,例如 `sayHello("Anna")`。正如上面所示,因为这个函数返回一个 `String` 类型的值,所以`sayHello` 可以被包含在 `print(_:separator:terminator:)` 的调用中,用来输出这个函数的返回值。
|
||||
|
||||
在 `sayHello(_:)` 的函数体中,先定义了一个新的名为 `greeting` 的 `String` 常量,同时赋值了给 `personName` 的一个简单问候消息。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。
|
||||
>注意
|
||||
`print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名 以及 默认参数值。
|
||||
|
||||
|
||||
在 `sayHello(_:)` 的函数体中,先定义了一个新的名为 `greeting` 的 `String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。
|
||||
|
||||
你可以用不同的输入值多次调用 `sayHello(_:)`。上面的例子展示的是用`"Anna"`和`"Brian"`调用的结果,该函数分别返回了不同的结果。
|
||||
|
||||
为了简化这个函数的定义,可以将问候消息的创建和返回写成一句:
|
||||
|
||||
```swift
|
||||
func sayHelloAgain(personName: String) -> String {
|
||||
return "Hello again, " + personName + "!"
|
||||
func greetAgain(person: String) -> String {
|
||||
return "Hello again, " + person + "!"
|
||||
}
|
||||
print(sayHelloAgain("Anna"))
|
||||
// prints "Hello again, Anna!"
|
||||
print(greetAgain(person: "Anna"))
|
||||
// 打印 "Hello again, Anna!"
|
||||
```
|
||||
|
||||
<a name="Function_Parameters_and_Return_Values"></a>
|
||||
## 函数参数与返回值(Function Parameters and Return Values)
|
||||
## 函数参数与返回值 (Function Parameters and Return Values)
|
||||
|
||||
函数参数与返回值在Swift中极为灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。
|
||||
函数参数与返回值在 Swift 中非常的灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。
|
||||
|
||||
### 多重输入参数(Multiple Input Parameters)
|
||||
<a name="functions_without_parameters"></a>
|
||||
### 无参数函数 (Functions Without Parameters)
|
||||
|
||||
函数可以有多个输入参数,写在圆括号中,用逗号分隔。
|
||||
|
||||
下面这个函数用一个半开区间的开始点和结束点,计算出这个范围内包含多少数字:
|
||||
|
||||
```swift
|
||||
func halfOpenRangeLength(start: Int, end: Int) -> Int {
|
||||
return end - start
|
||||
}
|
||||
print(halfOpenRangeLength(1, 10))
|
||||
// prints "9"
|
||||
```
|
||||
|
||||
### 无参函数(Functions Without Parameters)
|
||||
|
||||
函数可以没有参数。下面这个函数就是一个无参函数,当被调用时,它返回固定的 `String` 消息:
|
||||
函数可以没有参数。下面这个函数就是一个无参数函数,当被调用时,它返回固定的 `String` 消息:
|
||||
|
||||
```swift
|
||||
func sayHelloWorld() -> String {
|
||||
return "hello, world"
|
||||
}
|
||||
print(sayHelloWorld())
|
||||
// prints "hello, world"
|
||||
// 打印 "hello, world"
|
||||
```
|
||||
|
||||
尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
|
||||
|
||||
### 多参量函数 (Functions With Multiple Parameters)
|
||||
<a name="functions_with_multiple_parameters"></a>
|
||||
### 多参数函数 (Functions With Multiple Parameters)
|
||||
|
||||
函数可以有多种输入参数,这些参数被包含在函数的括号之中,以逗号分隔。
|
||||
|
||||
这个函数取得一个人的名字和是否被招呼作为输入,并对那个人返回适当的问候语:
|
||||
下面这个函数用一个人名和是否已经打过招呼作为输入,并返回对这个人的适当问候语:
|
||||
|
||||
```swift
|
||||
func sayHello(personName: String, alreadyGreeted: Bool) -> String {
|
||||
func greet(person: String, alreadyGreeted: Bool) -> String {
|
||||
if alreadyGreeted {
|
||||
return sayHelloAgain(personName)
|
||||
return greetAgain(person: person)
|
||||
} else {
|
||||
return sayHello(personName)
|
||||
return greet(person: person)
|
||||
}
|
||||
}
|
||||
print(sayHello("Tim", alreadyGreeted: true))
|
||||
// prints "Hello again, Tim!"
|
||||
print(greet(person: "Tim", alreadyGreeted: true))
|
||||
// 打印 "Hello again, Tim!"
|
||||
```
|
||||
|
||||
你通过在括号内传递一个`String`参数值和一个标识为`alreadyGreeted`的`Bool`值,使用逗号分隔来调用`sayHello(_:alreadyGreeted:)`函数。
|
||||
|
||||
当调用超过一个参数的函数时,第一个参数后的参数根据其对应的参数名称标记,函数参数命名在[函数参数名称(Function Parameter Names)](#Function_Parameter_Names)有更详细的描述。
|
||||
你可以通过在括号内使用逗号分隔来传递一个`String`参数值和一个标识为`alreadyGreeted`的`Bool`值,来调用`sayHello(_:alreadyGreeted:)`函数。注意这个函数和上面`greet(person:)`是不同的。虽然它们都有着同样的名字`greet`,但是`greet(person:alreadyGreeted:)`函数需要两个参数,而`greet(person:)`只需要一个参数。
|
||||
|
||||
<a name="functions_without_return_values"></a>
|
||||
### 无返回值函数(Functions Without Return Values)
|
||||
### 无返回值函数 (Functions Without Return Values)
|
||||
|
||||
函数可以没有返回值。下面是 `sayHello(_:)` 函数的另一个版本,叫 `sayGoodbye(_:)`,这个函数直接输出 `String` 值,而不是返回它:
|
||||
函数可以没有返回值。下面是 `sayHello(_:)` 函数的另一个版本,叫 `sayGoodbye(_:)`,这个函数直接打印一个`String`值,而不是返回它:
|
||||
|
||||
```swift
|
||||
func sayGoodbye(personName: String) {
|
||||
print("Goodbye, \(personName)!")
|
||||
func greet(person: String) {
|
||||
print("Hello, \(person)!")
|
||||
}
|
||||
sayGoodbye("Dave")
|
||||
// prints "Goodbye, Dave!"
|
||||
greet(person: "Dave")
|
||||
// 打印 "Hello, Dave!"
|
||||
```
|
||||
|
||||
因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。
|
||||
|
||||
> 注意:
|
||||
> 严格上来说,虽然没有返回值被定义,`sayGoodbye(_:)` 函数依然返回了值。没有定义返回类型的函数会返回特殊的值,叫 `Void`。它其实是一个空的元组(tuple),没有任何元素,可以写成`()`。
|
||||
>注意
|
||||
严格上来说,虽然没有返回值被定义,`sayGoodbye(_:)` 函数依然返回了值。没有定义返回类型的函数会返回一个特殊的`Void`值。它其实是一个空的元组(tuple),没有任何元素,可以写成()。
|
||||
|
||||
|
||||
被调用时,一个函数的返回值可以被忽略:
|
||||
|
||||
```swift
|
||||
func printAndCount(stringToPrint: String) -> Int {
|
||||
print(stringToPrint)
|
||||
return stringToPrint.characters.count
|
||||
func printAndCount(string: String) -> Int {
|
||||
print(string)
|
||||
return string.characters.count
|
||||
}
|
||||
func printWithoutCounting(stringToPrint: String) {
|
||||
printAndCount(stringToPrint)
|
||||
func printWithoutCounting(string: String) {
|
||||
let _ = printAndCount(string: string)
|
||||
}
|
||||
printAndCount("hello, world")
|
||||
// prints "hello, world" and returns a value of 12
|
||||
printWithoutCounting("hello, world")
|
||||
// prints "hello, world" but does not return a value
|
||||
|
||||
printAndCount(string: "hello, world")
|
||||
// 打印 "hello, world" 并且返回值 12
|
||||
printWithoutCounting(string: "hello, world")
|
||||
// 打印 "hello, world" 但是没有返回任何值
|
||||
```
|
||||
|
||||
第一个函数 `printAndCount(_:)`,输出一个字符串并返回 `Int` 类型的字符数。第二个函数 `printWithoutCounting`调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。
|
||||
|
||||
> 注意:
|
||||
> 返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,并且试图这样做,这将导致编译错误(compile-time error)。
|
||||
>注意
|
||||
返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译时错误(compile-time error)。
|
||||
|
||||
### 多重返回值函数(Functions with Multiple Return Values)
|
||||
|
||||
<a name="functions_with_multiple_return_values"></a>
|
||||
### 多重返回值函数 (Functions with Multiple Return Values)
|
||||
|
||||
你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
|
||||
|
||||
下面的这个例子中,定义了一个名为`minMax(_:)`的函数,作用是在一个`Int`数组中找出最小值与最大值。
|
||||
下例中定义了一个名为 `minMax(_:)` 的函数,作用是在一个 `Int` 类型的数组中找出最小值与最大值。
|
||||
|
||||
```swift
|
||||
func minMax(array: [Int]) -> (min: Int, max: Int) {
|
||||
@ -181,30 +185,33 @@ func minMax(array: [Int]) -> (min: Int, max: Int) {
|
||||
}
|
||||
```
|
||||
|
||||
`minMax(_:)`函数返回一个包含两个`Int`值的元组,这些值被标记为`min`和`max`,以便查询函数的返回值时他们可以被访问。
|
||||
`minMax(_:)` 函数返回一个包含两个 `Int` 值的元组,这些值被标记为 `min` 和 `max` ,以便查询函数的返回值时可以通过名字访问它们。
|
||||
|
||||
`minMax(_:)`的函数体中,在开始的时候设置两个工作变量`currentMin`和`currentMax`作为数组中的第一个`Int`值。然后函数会遍历数组中剩余的值并检查该值是否比`currentMin`和`currentMax`更小或更大。最后数组中的最小值与最大值返回两个`Int`值最为一个元组。
|
||||
在 `minMax(_:)` 的函数体中,在开始的时候设置两个工作变量 `currentMin` 和 `currentMax` 的值为数组中的第一个数。然后函数会遍历数组中剩余的值并检查该值是否比 `currentMin` 和 `currentMax` 更小或更大。最后数组中的最小值与最大值作为一个包含两个 `Int` 值的元组返回。
|
||||
|
||||
因为元组的成员值被命名为函数的返回类型的一部分,可以通过点语法来访问与取回发现的最小值与最小值:
|
||||
因为元组的成员值已被命名,因此可以通过 `.` 语法来检索找到的最小值与最大值:
|
||||
|
||||
```swift
|
||||
let bounds = minMax([8, -6, 2, 109, 3, 71])
|
||||
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
|
||||
print("min is \(bounds.min) and max is \(bounds.max)")
|
||||
// prints "min is -6 and max is 109"
|
||||
// 打印 "min is -6 and max is 109"
|
||||
```
|
||||
|
||||
需要注意的是,元组的成员不需要在函数中返回时命名,因为它们的名字已经在函数返回类型中有了定义。
|
||||
需要注意的是,元组的成员不需要在元组从函数中返回时命名,因为它们的名字已经在函数返回类型中指定了。
|
||||
|
||||
可选元组返回类型(Optional Tuple Return Types)
|
||||
<a name="optional_tuple_return_types"></a>
|
||||
### 可选元组返回类型 (Optional Tuple Return Types)
|
||||
|
||||
如果函数返回的元组类型中有可能在整个元组中含有“没有值”,你可以使用*可选的(Optional)* 元组返回类型反映整个元组可以是`nil`的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如`(Int,Int)?`或`(String,Int,Bool)?`
|
||||
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用可选的( `optional` ) 元组返回类型反映整个元组可以是`nil`的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 `(Int, Int)?` 或 `(String, Int, Bool)?`
|
||||
|
||||
> 注意:
|
||||
> 可选元组类型如`(Int,Int)?`与元组包含可选属性如`(Int?,Int?)`是不同的.可选的元组类型,整个数组是可选的,而不只是元组中的每个元素值。
|
||||
>注意
|
||||
可选元组类型如 `(Int, Int)?` 与元组包含可选类型如 `(Int?, Int?)` 是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
|
||||
|
||||
前面的`minMax(_:)`函数返回了一个包含两个`Int`值的元组。但是函数不会在数组中执行任何安全检查,如果`array`参数有一个空数组,如上定义的`minMax(_:)`在试图访问`array[0]`时会触发一个运行时错误。
|
||||
|
||||
为了安全的处理这个"空数组"问题,写一个`minMax(_:)`函数使用可选元组返回类型,并且当数组为空时返回`nil`:
|
||||
前面的 `minMax(_:)` 函数返回了一个包含两个 `Int` 值的元组。但是函数不会对传入的数组执行任何安全检查,如果 `array` 参数是一个空数组,如上定义的 `minMax(_:)` 在试图访问 `array[0]` 时会触发一个运行时错误(runtime error)。
|
||||
|
||||
为了安全地处理这个“空数组”问题,将 `minMax(_:)` 函数改写为使用可选元组返回类型,并且当数组为空时返回 `nil`:
|
||||
|
||||
|
||||
```swift
|
||||
func minMax(array: [Int]) -> (min: Int, max: Int)? {
|
||||
@ -222,104 +229,97 @@ func minMax(array: [Int]) -> (min: Int, max: Int)? {
|
||||
}
|
||||
```
|
||||
|
||||
你可以选择性的绑定当`minMax(_:)`函数返回的是一个实际的元组值还是`nil`
|
||||
你可以使用可选绑定来检查 `minMax(_:)` 函数返回的是一个存在的元组值还是 `nil`:
|
||||
|
||||
```swift
|
||||
if let bounds = minMax([8, -6, 2, 109, 3, 71]) {
|
||||
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
|
||||
print("min is \(bounds.min) and max is \(bounds.max)")
|
||||
}
|
||||
// prints "min is -6 and max is 109"
|
||||
// 打印 "min is -6 and max is 109"
|
||||
```
|
||||
|
||||
<a name="Function_Parameter_Names"></a>
|
||||
## 函数参数名称(Function Parameter Names)
|
||||
|
||||
函数参数都有一个*外部参数名(external parameter name)*和一个*本地参数名(local parameter name)*。外部参数名用来标记传递给函数调用的参数,本地参数名在实现函数的时候使用。
|
||||
<a name="Function_Argument_Labels_and_Parameter_Names"></a>
|
||||
## 函数参数标签和参数名称 (Function Argument Labels and Parameter Names)
|
||||
|
||||
每个函数参数都有一个参数标签( argument label )以及一个参数名称( parameter name )。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。
|
||||
|
||||
```swift
|
||||
func someFunction(firstParameterName: Int, secondParameterName: Int) {
|
||||
// function body goes here
|
||||
// firstParameterName and secondParameterName refer to
|
||||
// the argument values for the first and second parameters
|
||||
// In the function body, firstParameterName and secondParameterName
|
||||
// refer to the argument values for the first and second parameters.
|
||||
}
|
||||
someFunction(firstParameterName: 1, secondParameterName: 2)
|
||||
```
|
||||
|
||||
所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的函数标签能够使你的代码更具可读性。
|
||||
|
||||
<a name="specifying_argument_labels"></a>
|
||||
### 参数标签 (Specifying Argument Labels)
|
||||
|
||||
你可以在函数名称前指定它的参数标签,中间以空格分隔:
|
||||
|
||||
```swift
|
||||
func someFunction(argumentLabel parameterName: Int) {
|
||||
// In the function body, parameterName refers to the argument value
|
||||
// for that parameter.
|
||||
}
|
||||
```
|
||||
|
||||
这个版本的 `greet(person:)` 函数,接收一个人的名字和他的家乡,并且返回一句问候:
|
||||
|
||||
```swift
|
||||
func greet(person: String, from hometown: String) -> String {
|
||||
return "Hello \(person)! Glad you could visit from \(hometown)."
|
||||
}
|
||||
print(greet(person: "Bill", from: "Cupertino"))
|
||||
// Prints "Hello Bill! Glad you could visit from Cupertino."
|
||||
```
|
||||
|
||||
参数标签的使用能够让一个函数在调用时更有表达力,更类似自然语言,并且仍保持了函数内部的可读性以及清晰的意图。
|
||||
|
||||
<a name="omitting_argument_labels"></a>
|
||||
### 忽略参数标签(Omitting Argument Labels)
|
||||
|
||||
如果你不希望为某个参数添加一个标签,可以使用一个下划线(`_`)来代替一个明确的参数标签。
|
||||
|
||||
```swift
|
||||
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
|
||||
// In the function body, firstParameterName and secondParameterName
|
||||
// refer to the argument values for the first and second parameters.
|
||||
}
|
||||
someFunction(1, secondParameterName: 2)
|
||||
```
|
||||
|
||||
一般情况下,第一个参数省略其外部参数名,第二个以后的参数使用其本地参数名作为自己的外部参数名。所有参数需要有不同的本地参数名,但可以共享相同的外部参数名。
|
||||
|
||||
<a name="specifying_external_parameter_names"></a>
|
||||
### 指定外部参数名(Specifying External Parameter Names)
|
||||
|
||||
你可以在本地参数名前指定外部参数名,中间以空格分隔。
|
||||
|
||||
```swift
|
||||
func someFunction(externalParameterName localParameterName: Int) {
|
||||
// function body goes here, and can use localParameterName
|
||||
// to refer to the argument value for that parameter
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。
|
||||
|
||||
这个版本的`sayHello(_:)`函数,得到了两个人的名字,会同时返回对他俩的问候:
|
||||
|
||||
```swift
|
||||
func sayHello(to person: String, and anotherPerson: String) -> String {
|
||||
return "Hello \(person) and \(anotherPerson)!"
|
||||
}
|
||||
print(sayHello(to: "Bill", and: "Ted"))
|
||||
// prints "Hello Bill and Ted!"
|
||||
```
|
||||
|
||||
为每个参数指定外部参数名,在你调用函数`sayHello(to:and:)`函数时时两个参数都必须被标记出来。
|
||||
|
||||
使用外部函数名可以使得函数可以用一句话表达清楚,并且使得函数体内部可读,能表达出函数的明确意图。
|
||||
|
||||
### 忽略外部参数名(Omitting External Parameter Names)
|
||||
|
||||
如果你不想为第二个及后续的参数设置参数名,用一个下划线(`_`)代替一个明确地参数名。
|
||||
|
||||
```swift
|
||||
func someFunction(firstParameterName: Int, _ secondParameterName: Int) {
|
||||
// function body goes here
|
||||
// firstParameterName and secondParameterName refer to
|
||||
// the argument values for the first and second parameters
|
||||
}
|
||||
someFunction(1, 2)
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 因为第一个参数默认忽略其外部参数名称,明确写下划线是多余的。
|
||||
如果一个参数有一个标签,那么在调用的时候必须使用标签来标记这个参数。
|
||||
|
||||
<a name="default_parameter_values"></a>
|
||||
### 默认参数值(Default Parameter Values)
|
||||
### 默认参数值 (Default Parameter Values)
|
||||
|
||||
你可以在函数体中为每个参数定义`默认值(Deafult Values)`。当默认值被定义后,调用这个函数时可以忽略这个参数。
|
||||
你可以在函数体中通过给参数赋值来为任意一个参数定义默认值(Deafult Values)。当默认值被定义后,调用这个函数时可以忽略这个参数。
|
||||
|
||||
```swift
|
||||
func someFunction(parameterWithDefault: Int = 12) {
|
||||
// function body goes here
|
||||
// if no arguments are passed to the function call,
|
||||
// value of parameterWithDefault is 12
|
||||
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
|
||||
// If you omit the second argument when calling this function, then
|
||||
// the value of parameterWithDefault is 12 inside the function body.
|
||||
}
|
||||
someFunction(6) // parameterWithDefault is 6
|
||||
someFunction() // parameterWithDefault is 12
|
||||
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6
|
||||
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 将带有默认值的参数放在函数参数列表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。
|
||||
将不带有默认值的参数放在函数参数列表的最前。一般来说,没有默认值的参数更加的重要,将不带默认值的参数放在最前保证在函数调用时,非默认参数的顺序是一致的,同时也使得相同的函数在不同情况下调用时显得更为清晰。
|
||||
|
||||
### 可变参数(Variadic Parameters)
|
||||
<a name="variadic_parameters"></a>
|
||||
### 可变参数 (Variadic Parameters)
|
||||
|
||||
一个`可变参数(variadic parameter)`可以接受零个或多个值。函数调用时,你可以用可变参数来传入不确定数量的输入参数。通过在变量类型名后面加入`(...)`的方式来定义可变参数。
|
||||
一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(`...`)的方式来定义可变参数。
|
||||
|
||||
可变参数的传入值在函数体为此类型的一个数组。例如,一个叫做 `numbers` 的 `Double...` 型可变参数,在函数体内可以当做一个叫 `numbers` 的 `[Double]` 型的数组常量。
|
||||
可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 `numbers` 的 `Double...` 型可变参数,在函数体内可以当做一个叫 `numbers` 的 `[Double]` 型的数组常量。
|
||||
|
||||
下面的这个函数用来计算一组任意长度数字的`算术平均数(arithmetic mean)`:
|
||||
下面的这个函数用来计算一组任意长度数字的 *算术平均数(arithmetic mean)*:
|
||||
|
||||
```swift
|
||||
func arithmeticMean(numbers: Double...) -> Double {
|
||||
func arithmeticMean(_ numbers: Double...) -> Double {
|
||||
var total: Double = 0
|
||||
for number in numbers {
|
||||
total += number
|
||||
@ -332,75 +332,33 @@ arithmeticMean(3, 8.25, 18.75)
|
||||
// returns 10.0, which is the arithmetic mean of these three numbers
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 一个函数最多只能有一个可变参数。
|
||||
|
||||
如果函数有一个或多个带默认值的参数,而且还有一个可变参数,那么把可变参数放在参数表的最后。
|
||||
|
||||
### 常量参数和变量参数(Constant and Variable Parameters)
|
||||
|
||||
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。
|
||||
|
||||
但是,有时候,如果函数中有传入参数的变量值副本将是很有用的。你可以通过指定一个或多个参数为变量参数,从而避免自己在函数中定义新的变量。变量参数不是常量,你可以在函数中把它当做新的可修改副本来使用。
|
||||
|
||||
通过在参数名前加关键字 `var` 来定义变量参数:
|
||||
|
||||
```swift
|
||||
func alignRight(var string: String, totalLength: Int, pad: Character) -> String {
|
||||
let amountToPad = totalLength - string.characters.count
|
||||
if amountToPad < 1 {
|
||||
return string
|
||||
}
|
||||
let padString = String(pad)
|
||||
for _ in 1...amountToPad {
|
||||
string = padString + string
|
||||
}
|
||||
return string
|
||||
}
|
||||
let originalString = "hello"
|
||||
let paddedString = alignRight(originalString, totalLength: 10, pad: "-")
|
||||
// paddedString is equal to "-----hello"
|
||||
// originalString is still equal to "hello"
|
||||
```
|
||||
|
||||
这个例子中定义了一个新的叫做 `alignRight(_:totalLength:pad:)` 的函数,用来右对齐输入的字符串到一个长的输出字符串中。左侧空余的地方用指定的填充字符填充。这个例子中,字符串`"hello"`被转换成了`"-----hello"`。
|
||||
|
||||
`alignRight(_:totalLength:pad:)` 函数将参数 `string` 定义为变量参数。这意味着 `string` 现在可以作为一个局部变量,用传入的字符串值初始化,并且可以在函数体中进行操作。
|
||||
|
||||
函数首先找出有多少字符需要被添加到左边的字符串以右对齐在整个字符串。这个值是存储在一个本地常数称为amountToPad。如果不需要填充(也就是说,如果amountToPad小于1),该函数返回字符串没有填充的输入值。
|
||||
|
||||
否则,该函数创建一个新的临时字符串常量称为padString,初始化填充字符,并将amountToPad padString副本添加到现有的左边的字符串。(一个字符串值不能被添加到一个字符值,所以padString常数用于确保双方+操作符的字符串值)。
|
||||
|
||||
该函数首先计算出多少个字符需要被添加到 `string` 的左边,以右对齐到总的字符串中。这个值存在局部常量 `amountToPad` 中。如果不需要填充(即,如果`amountToPad`小于`1`),该函数返回没有填充输入的`string`。
|
||||
|
||||
否则,该函数会建立一个临时的String常量称为`padString`,初始化`pad`字符,并将`amountToPad`作为`padString`的副本添加到现有的字符串左边。(一个`Character`后不能直接添加一个`String`值,所以`padString`经常用于确保`+`号两边都是`String`值。)
|
||||
|
||||
> 注意:
|
||||
> 对变量参数所进行的修改在函数调用结束后便消失了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。
|
||||
>注意
|
||||
一个函数最多只能拥有一个可变参数。
|
||||
|
||||
<a name="in_out_parameters"></a>
|
||||
### 输入输出参数(In-Out Parameters)
|
||||
|
||||
变量参数,正如上面所述,仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。
|
||||
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误(compile-time error)。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。
|
||||
|
||||
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。
|
||||
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个`输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看`输入输出参数`一节。
|
||||
|
||||
你只能将变量作为输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数前加`&`符,表示这个值可以被函数修改。
|
||||
你只能传递变量给输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 `&` 符,表示这个值可以被函数修改。
|
||||
|
||||
> 注意:
|
||||
> 输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。如果你用 `inout` 标记一个参数,这个参数不能被 `var` 或者 `let` 标记。
|
||||
>注意
|
||||
输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。
|
||||
|
||||
下面是例子,`swapTwoInts(_:_:)` 函数,有两个分别叫做 `a` 和 `b` 的输入输出参数:
|
||||
|
||||
下例中,`swapTwoInts(_:_:)` 函数有两个分别叫做 `a` 和 `b` 的输入输出参数:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(inout a: Int, inout _ b: Int) {
|
||||
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
这个 `swapTwoInts(_:_:)` 函数仅仅交换 `a` 与 `b` 的值。该函数先将 `a` 的值存到一个暂时常量 `temporaryA` 中,然后将 `b` 的值赋给 `a`,最后将 `temporaryA` 幅值给 `b`。
|
||||
`swapTwoInts(_:_:)` 函数简单地交换 `a` 与 `b` 的值。该函数先将 `a` 的值存到一个临时常量 `temporaryA` 中,然后将 `b` 的值赋给 `a`,最后将 `temporaryA` 赋值给 `b`。
|
||||
|
||||
你可以用两个 `Int` 型的变量来调用 `swapTwoInts(_:_:)`。需要注意的是,`someInt` 和 `anotherInt` 在传入 `swapTwoInts(_:_:)` 函数前,都加了 `&` 的前缀:
|
||||
|
||||
@ -409,33 +367,34 @@ var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// prints "someInt is now 107, and anotherInt is now 3"
|
||||
// Prints "someInt is now 107, and anotherInt is now 3"
|
||||
```
|
||||
|
||||
从上面这个例子中,我们可以看到 `someInt` 和 `anotherInt` 的原始值在 `swapTwoInts(_:_:)` 函数中被修改,尽管它们的定义在函数体外。
|
||||
|
||||
> 注意:
|
||||
> 输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
|
||||
>注意
|
||||
输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
|
||||
|
||||
|
||||
<a name="Function_Types"></a>
|
||||
## 函数类型(Function Types)
|
||||
## 函数类型 (Function Types)
|
||||
|
||||
每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。
|
||||
每个函数都有种特定的`函数类型`,函数的类型由函数的参数类型和返回类型组成。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
func addTwoInts(a: Int, _ b: Int) -> Int {
|
||||
func addTwoInts(_ a: Int, _ b: Int) -> Int {
|
||||
return a + b
|
||||
}
|
||||
func multiplyTwoInts(a: Int, _ b: Int) -> Int {
|
||||
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
|
||||
return a * b
|
||||
}
|
||||
```
|
||||
|
||||
这个例子中定义了两个简单的数学函数:`addTwoInts` 和 `multiplyTwoInts`。这两个函数都传入两个 `Int` 类型, 返回一个合适的`Int`值。
|
||||
这个例子中定义了两个简单的数学函数:`addTwoInts` 和 `multiplyTwoInts`。这两个函数都接受两个 `Int` 值, 返回一个 `Int` 值。
|
||||
|
||||
这两个函数的类型是 `(Int, Int) -> Int`,可以读作”这个函数类型,它有两个 `Int` 型的参数并返回一个 `Int` 型的值。”。
|
||||
这两个函数的类型是 `(Int, Int) -> Int`,可以解读为“这个函数类型有两个 `Int` 型的参数并返回一个 `Int` 型的值。”。
|
||||
|
||||
下面是另一个例子,一个没有参数,也没有返回值的函数:
|
||||
|
||||
@ -445,98 +404,103 @@ func printHelloWorld() {
|
||||
}
|
||||
```
|
||||
|
||||
这个函数的类型是:`() -> void`,或者叫“没有参数,并返回 `Void` 类型的函数”。
|
||||
这个函数的类型是:`() -> Void`,或者叫“没有参数,并返回 `Void` 类型的函数”。
|
||||
|
||||
### 使用函数类型(Using Function Types)
|
||||
<a name="using_function_types"></a>
|
||||
### 使用函数类型 (Using Function Types)
|
||||
|
||||
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将函数赋值给它:
|
||||
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:
|
||||
|
||||
```swift
|
||||
var mathFunction: (Int, Int) -> Int = addTwoInts
|
||||
```
|
||||
|
||||
这个可以读作:
|
||||
这段代码可以被解读为:
|
||||
|
||||
“定义一个叫做 `mathFunction` 的变量,类型是‘一个有两个 `Int` 型的参数并返回一个 `Int` 型的值的函数’,并让这个新变量指向 `addTwoInts` 函数”。
|
||||
”定义一个叫做 `mathFunction` 的变量,类型是‘一个有两个 `Int` 型的参数并返回一个 `Int` 型的值的函数’,并让这个新变量指向 `addTwoInts` 函数”。
|
||||
|
||||
`addTwoInts` 和 `mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查中是允许的。
|
||||
`addTwoInts` 和 `mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查(type-check)中是允许的。
|
||||
|
||||
现在,你可以用 `mathFunction` 来调用被赋值的函数了:
|
||||
|
||||
```swift
|
||||
print("Result: \(mathFunction(2, 3))")
|
||||
// prints "Result: 5"
|
||||
// Prints "Result: 5"
|
||||
```
|
||||
|
||||
有相同匹配类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样:
|
||||
|
||||
|
||||
```swift
|
||||
mathFunction = multiplyTwoInts
|
||||
print("Result: \(mathFunction(2, 3))")
|
||||
// prints "Result: 6"
|
||||
// Prints "Result: 6"
|
||||
```
|
||||
|
||||
就像其他类型一样,当赋值一个函数给常量或变量时,你可以让 Swift 来推断其函数类型:
|
||||
|
||||
```swift
|
||||
let anotherMathFunction = addTwoInts
|
||||
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
|
||||
// anotherMathFunction 被推断为 (Int, Int) -> Int 类型
|
||||
```
|
||||
|
||||
### 函数类型作为参数类型(Function Types as Parameter Types)
|
||||
<a name="function_types_as_parameter_types"></a>
|
||||
### 函数类型作为参数类型 (Function Types as Parameter Types)
|
||||
|
||||
你可以用`(Int, Int) -> Int`这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现交由给函数的调用者。
|
||||
你可以用 `(Int, Int) -> Int` 这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。
|
||||
|
||||
下面是另一个例子,正如上面的函数一样,同样是输出某种数学运算结果:
|
||||
|
||||
```swift
|
||||
func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
|
||||
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
|
||||
print("Result: \(mathFunction(a, b))")
|
||||
}
|
||||
printMathResult(addTwoInts, 3, 5)
|
||||
// prints "Result: 8"
|
||||
// 打印 "Result: 8"
|
||||
```
|
||||
|
||||
这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是`(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a` 和 `b`,它们的类型都是 `Int`,这两个值作为已给的函数的输入值。
|
||||
这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是 `(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a` 和 `b`,它们的类型都是 `Int`,这两个值作为已给出的函数的输入值。
|
||||
|
||||
当 `printMathResult(_:_:_:)` 被调用时,它被传入 `addTwoInts` 函数和整数`3`和`5`。它用传入`3`和`5`调用 `addTwoInts`,并输出结果:`8`。
|
||||
当 `printMathResult(_:_:_:)` 被调用时,它被传入 `addTwoInts` 函数和整数 `3` 和 `5`。它用传入 `3` 和 `5` 调用 `addTwoInts`,并输出结果:`8`。
|
||||
|
||||
`printMathResult(_:_:_:)` 函数的作用就是输出另一个合适类型的数学函数的调用结果。它不关心传入函数是如何实现的,它只关心这个传入的函数类型是正确的。这使得 `printMathResult(_:_:_:)` 可以以一种类型安全(type-safe)的方式来保证传入函数的调用是正确的。
|
||||
`printMathResult(_:_:_:)` 函数的作用就是输出另一个适当类型的数学函数的调用结果。它不关心传入函数是如何实现的,只关心传入的函数是不是一个正确的类型。这使得 `printMathResult(_:_:_:)` 能以一种类型安全(type-safe)的方式将一部分功能转给调用者实现。
|
||||
|
||||
### 函数类型作为返回类型(Function Type as Return Types)
|
||||
<a name="function_types_as_return_types"></a>
|
||||
### 函数类型作为返回类型 (Function Types as Return Types)
|
||||
|
||||
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(`->`)后写一个完整的函数类型。
|
||||
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(->)后写一个完整的函数类型。
|
||||
|
||||
下面的这个例子中定义了两个简单函数,分别是 `stepForward` 和`stepBackward`。`stepForward` 函数返回一个比输入值大一的值。`stepBackward` 函数返回一个比输入值小一的值。这两个函数的类型都是 `(Int) -> Int`:
|
||||
下面的这个例子中定义了两个简单函数,分别是 `stepForward` 和 `stepBackward`。`stepForward`函数返回一个比输入值大 `1` 的值。`stepBackward` 函数返回一个比输入值小 `1` 的值。这两个函数的类型都是 `(Int) -> Int`:
|
||||
|
||||
```swift
|
||||
func stepForward(input: Int) -> Int {
|
||||
func stepForward(_ input: Int) -> Int {
|
||||
return input + 1
|
||||
}
|
||||
func stepBackward(input: Int) -> Int {
|
||||
func stepBackward(_ input: Int) -> Int {
|
||||
return input - 1
|
||||
}
|
||||
```
|
||||
|
||||
下面这个叫做 `chooseStepFunction(_:)` 的函数,它的返回类型是 `(Int) -> Int` 的函数。`chooseStepFunction(_:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
|
||||
如下名为 `chooseStepFunction(_:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(_:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
|
||||
return backwards ? stepBackward : stepForward
|
||||
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
|
||||
return backward ? stepBackward : stepForward
|
||||
}
|
||||
```
|
||||
|
||||
你现在可以用 `chooseStepFunction(_:)` 来获得一个函数,不管是那个方向:
|
||||
你现在可以用 `chooseStepFunction(_:)` 来获得两个函数其中的一个:
|
||||
|
||||
```swift
|
||||
var currentValue = 3
|
||||
let moveNearerToZero = chooseStepFunction(currentValue > 0)
|
||||
// moveNearerToZero now refers to the stepBackward() function
|
||||
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
|
||||
// moveNearerToZero 现在指向 stepBackward() 函数。
|
||||
```
|
||||
|
||||
上面这个例子中计算出从 `currentValue` 逐渐接近到`0`是需要向正数走还是向负数走。`currentValue` 的初始值是`3`,这意味着 `currentValue > 0` 是真的(`true`),这将使得 `chooseStepFunction(_:)` 返回 `stepBackward(_:)` 函数。一个指向返回的函数的引用保存在了 `moveNearerToZero` 常量中。
|
||||
上面这个例子中计算出从 `currentValue` 逐渐接近到0是需要向正数走还是向负数走。`currentValue` 的初始值是 `3`,这意味着 `currentValue > 0` 为真(true),这将使得 `chooseStepFunction(_:)` 返回 `stepBackward(_:)` 函数。一个指向返回的函数的引用保存在了 `moveNearerToZero` 常量中。
|
||||
|
||||
现在,moveNearerToZero 指向了正确的函数,它可以被用来数到零:
|
||||
|
||||
现在,`moveNearerToZero` 指向了正确的函数,它可以被用来数到`0`:
|
||||
|
||||
```swift
|
||||
print("Counting to zero:")
|
||||
@ -550,26 +514,25 @@ print("zero!")
|
||||
// 2...
|
||||
// 1...
|
||||
// zero!
|
||||
|
||||
```
|
||||
|
||||
<a name="Nested_Functions"></a>
|
||||
## 嵌套函数(Nested Functions)
|
||||
## 嵌套函数 (Nested Functions)
|
||||
|
||||
这章中你所见到的所有函数都叫全局函数(global functions),它们定义在全局域中。你也可以把函数定义在别的函数体中,称作嵌套函数(nested functions)。
|
||||
到目前为止本章中你所见到的所有函数都叫`全局`函数(global functions),它们定义在`全局域`中。你也可以把函数定义在别的函数体中,称作 `嵌套函数`(nested functions)。
|
||||
|
||||
默认情况下,嵌套函数是对外界不可见的,但是可以被他们封闭函数(enclosing function)来调用。一个封闭函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。
|
||||
默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。
|
||||
|
||||
你可以用返回嵌套函数的方式重写 `chooseStepFunction(_:)` 函数:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
|
||||
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
|
||||
func stepForward(input: Int) -> Int { return input + 1 }
|
||||
func stepBackward(input: Int) -> Int { return input - 1 }
|
||||
return backwards ? stepBackward : stepForward
|
||||
return backward ? stepBackward : stepForward
|
||||
}
|
||||
var currentValue = -4
|
||||
let moveNearerToZero = chooseStepFunction(currentValue > 0)
|
||||
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
|
||||
// moveNearerToZero now refers to the nested stepForward() function
|
||||
while currentValue != 0 {
|
||||
print("\(currentValue)... ")
|
||||
|
||||
@ -8,23 +8,33 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[100mango](https://github.com/100mango)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[100mango](https://github.com/100mango), [magicdict](https://github.com/magicdict)
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-12
|
||||
>
|
||||
> 3.0
|
||||
> 翻译:[Lanford](https://github.com/LanfordCai) 2016-09-19
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [闭包表达式(Closure Expressions)](#closure_expressions)
|
||||
- [尾随闭包(Trailing Closures)](#trailing_closures)
|
||||
- [值捕获(Capturing Values)](#capturing_values)
|
||||
- [闭包是引用类型(Closures Are Reference Types)](#closures_are_reference_types)
|
||||
- [逃逸闭包(Escaping Closures) ](#escaping_closures)
|
||||
- [自动闭包(Autoclosures)](#autoclosures)
|
||||
|
||||
闭包是自包含的函数代码块,可以在代码中被传递和使用。
|
||||
Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 匿名函数比较相似。
|
||||
*闭包*是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。
|
||||
|
||||
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。
|
||||
这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。
|
||||
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。*闭合、包裹*常量和变量,所谓闭包也。Swift 会为你管理在捕获过程中涉及到的所有内存操作。
|
||||
|
||||
> 注意:
|
||||
> 如果您不熟悉捕获(capturing)这个概念也不用担心,您可以在 [值捕获](#capturing_values) 章节对其进行详细了解。
|
||||
> 注意
|
||||
> 如果你不熟悉捕获(capturing)这个概念也不用担心,你可以在[值捕获](#capturing_values)章节对其进行详细了解。
|
||||
|
||||
在[函数](./06_Functions.html) 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
|
||||
在[函数](./06_Functions.html)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
|
||||
|
||||
* 全局函数是一个有名字但不会捕获任何值的闭包
|
||||
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
|
||||
@ -33,7 +43,7 @@ Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他
|
||||
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
|
||||
|
||||
* 利用上下文推断参数和返回值类型
|
||||
* 隐式返回单表达式闭包,即单表达式闭包可以省略`return`关键字
|
||||
* 隐式返回单表达式闭包,即单表达式闭包可以省略 `return` 关键字
|
||||
* 参数名称缩写
|
||||
* 尾随(Trailing)闭包语法
|
||||
|
||||
@ -41,54 +51,43 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进
|
||||
## 闭包表达式(Closure Expressions)
|
||||
|
||||
|
||||
[嵌套函数](./06_Functions.html#nested_function) 是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在您处理一些函数并需要将另外一些函数作为该函数的参数时。
|
||||
[嵌套函数](./06_Functions.html#nested_function)是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候编写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在你处理一些函数并需要将另外一些函数作为该函数的参数时。
|
||||
|
||||
闭包表达式是一种利用简洁语法构建内联闭包的方式。
|
||||
闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。
|
||||
下面闭包表达式的例子通过使用几次迭代展示了`sort(_:)`方法定义和语法优化的方式。
|
||||
每一次迭代都用更简洁的方式描述了相同的功能。
|
||||
*闭包表达式*是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。下面闭包表达式的例子通过使用几次迭代展示了 `sorted(by:)` 方法定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能。
|
||||
|
||||
<a name="the_sorted_function"></a>
|
||||
### sort 函数(The Sort Function)
|
||||
### sorted 方法(The Sorted Method)
|
||||
|
||||
Swift 标准库提供了名为`sort`的函数,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。
|
||||
一旦排序完成,`sort(_:)`方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被`sort(_:)`方法修改。
|
||||
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,`sorted(by:)` 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 `sorted(by:)` 方法修改。
|
||||
|
||||
下面的闭包表达式示例使用`sort(_:)`方法对一个String类型的数组进行字母逆序排序,以下是初始数组值:
|
||||
下面的闭包表达式示例使用 `sorted(by:)` 方法对一个 `String` 类型的数组进行字母逆序排序。以下是初始数组:
|
||||
|
||||
```swift
|
||||
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
|
||||
```
|
||||
|
||||
`sort(_:)`方法需要传入两个参数:
|
||||
`sorted(by:)` 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值*前面*,排序闭包函数需要返回`true`,反之返回`false`。
|
||||
|
||||
* 已知类型的数组
|
||||
* 闭包函数,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回`true`,反之返回`false`。
|
||||
该例子对一个 `String` 类型的数组进行排序,因此排序闭包函数类型需为 `(String, String) -> Bool`。
|
||||
|
||||
该例子对一个`String`类型的数组进行排序,因此排序闭包函数类型需为`(String, String) -> Bool`。
|
||||
|
||||
提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为`sort(_:)`方法的参数传入:
|
||||
提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为 `sorted(by:)` 方法的参数传入:
|
||||
|
||||
```swift
|
||||
func backwards(s1: String, s2: String) -> Bool {
|
||||
func backward(_ s1: String, _ s2: String) -> Bool {
|
||||
return s1 > s2
|
||||
}
|
||||
var reversed = names.sort(backwards)
|
||||
// reversed 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
|
||||
var reversedNames = names.sorted(by: backward)
|
||||
// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
|
||||
```
|
||||
|
||||
如果第一个字符串 (`s1`) 大于第二个字符串 (`s2`),`backwards`函数返回`true`,表示在新的数组中`s1`应该出现在`s2`前。
|
||||
对于字符串中的字符来说,“大于” 表示 “按照字母顺序较晚出现”。
|
||||
这意味着字母`"B"`大于字母`"A"`,字符串`"Tom"`大于字符串`"Tim"`。
|
||||
其将进行字母逆序排序,`"Barry"`将会排在`"Alex"`之前。
|
||||
如果第一个字符串(`s1`)大于第二个字符串(`s2`),`backward(_:_:)` 函数会返回 `true`,表示在新的数组中 `s1` 应该出现在 `s2` 前。对于字符串中的字符来说,“大于”表示“按照字母顺序较晚出现”。这意味着字母 `"B"` 大于字母 `"A"` ,字符串 `"Tom"` 大于字符串 `"Tim"`。该闭包将进行字母逆序排序,`"Barry"` 将会排在 `"Alex"` 之前。
|
||||
|
||||
然而,这是一个相当冗长的方式,本质上只是写了一个单表达式函数 (a > b)。
|
||||
在下面的例子中,利用闭合表达式语法可以更好的构造一个内联排序闭包。
|
||||
然而,以这种方式来编写一个实际上很简单的表达式(`a > b`),确实太过繁琐了。对于这个例子来说,利用闭包表达式语法可以更好地构造一个内联排序闭包。
|
||||
|
||||
<a name="closure_expression_syntax"></a>
|
||||
### 闭包表达式语法(Closure Expression Syntax)
|
||||
|
||||
闭包表达式语法有如下一般形式:
|
||||
闭包表达式语法有如下的一般形式:
|
||||
|
||||
```swift
|
||||
{ (parameters) -> returnType in
|
||||
@ -96,94 +95,80 @@ var reversed = names.sort(backwards)
|
||||
}
|
||||
```
|
||||
|
||||
闭包表达式语法可以使用常量、变量和`inout`类型作为参数,不提供默认值。
|
||||
也可以在参数列表的最后使用可变参数。
|
||||
元组也可以作为参数和返回值。
|
||||
闭包表达式的参数可以是inout参数,但不能设定默认值。也可以使用具名的可变参数(译者注:但是如果可变参数不放在参数列表的最后一位的话,调用闭包的时时编译器将报错。可参考[这里](http://stackoverflow.com/questions/39548852/swift-3-0-closure-expression-what-if-the-variadic-parameters-not-at-the-last-pl))。元组也可以作为参数和返回值。
|
||||
|
||||
下面的例子展示了之前`backwards`函数对应的闭包表达式版本的代码:
|
||||
下面的例子展示了之前 `backward(_:_:)` 函数对应的闭包表达式版本的代码:
|
||||
|
||||
```swift
|
||||
reversed = names.sort({ (s1: String, s2: String) -> Bool in
|
||||
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
|
||||
return s1 > s2
|
||||
})
|
||||
```
|
||||
|
||||
需要注意的是内联闭包参数和返回值类型声明与`backwards`函数类型声明相同。
|
||||
在这两种方式中,都写成了`(s1: String, s2: String) -> Bool`。
|
||||
然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
|
||||
需要注意的是内联闭包参数和返回值类型声明与 `backward(_:_:)` 函数类型声明相同。在这两种方式中,都写成了 `(s1: String, s2: String) -> Bool`。然而在内联闭包表达式中,函数和返回值类型都写在*大括号内*,而不是大括号外。
|
||||
|
||||
闭包的函数体部分由关键字`in`引入。
|
||||
该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
|
||||
闭包的函数体部分由关键字`in`引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
|
||||
|
||||
因为这个闭包的函数体部分如此短以至于可以将其改写成一行代码:
|
||||
由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )
|
||||
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
|
||||
```
|
||||
|
||||
这说明`sort(_:)`方法的整体调用保持不变,一对圆括号仍然包裹住了函数中整个参数集合。而其中一个参数现在变成了内联闭包(相比于`backwards`版本的代码)
|
||||
该例中 `sorted(by:)` 方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了内联闭包。
|
||||
|
||||
<a name="inferring_type_from_context"></a>
|
||||
### 根据上下文推断类型(Inferring Type From Context)
|
||||
|
||||
因为排序闭包函数是作为`sort(_:)`方法的参数进行传入的,Swift可以推断其参数和返回值的类型。
|
||||
`sorted`期望第二个参数是类型为`(String, String) -> Bool`的函数,因此实际上`String`,`String`和`Bool`类型并不需要作为闭包表达式定义中的一部分。
|
||||
因为所有的类型都可以被正确推断,返回箭头 (`->`) 和围绕在参数周围的括号也可以被省略:
|
||||
因为排序闭包函数是作为 `sorted(by:)` 方法的参数传入的,Swift 可以推断其参数和返回值的类型。`sorted(by:)` 方法被一个字符串数组调用,因此其参数必须是 `(String, String) -> Bool` 类型的函数。这意味着 `(String, String)` 和 `Bool` 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(`->`)和围绕在参数周围的括号也可以被省略:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { s1, s2 in return s1 > s2 } )
|
||||
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
|
||||
```
|
||||
|
||||
实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。
|
||||
实际上,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型。这意味着闭包作为函数或者方法的参数时,你几乎不需要利用完整格式构造内联闭包。
|
||||
|
||||
然而您仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则可以采用完整格式的闭包。而在`sort(_:)`方法这个例子里,闭包的目的就是排序,读者能够推测除这个闭包是用于字符串处理的,因为这个闭包是为了处理字符串数组的排序。
|
||||
尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
|
||||
|
||||
<a name="implicit_returns_from_single_expression_closures"></a>
|
||||
### 单表达式闭包隐式返回(Implicit Return From Single-Expression Clossures)
|
||||
### 单表达式闭包隐式返回(Implicit Returns From Single-Expression Closures)
|
||||
|
||||
单行表达式闭包可以通过隐藏`return`关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
|
||||
单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { s1, s2 in s1 > s2 } )
|
||||
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
|
||||
```
|
||||
|
||||
在这个例子中,`sort(_:)`方法的第二个参数函数类型明确了闭包必须返回一个`Bool`类型值。
|
||||
因为闭包函数体只包含了一个单一表达式 (`s1 > s2`),该表达式返回`Bool`类型值,因此这里没有歧义,`return`关键字可以省略。
|
||||
在这个例子中,`sorted(by:)` 方法的参数类型明确了闭包必须返回一个 `Bool` 类型值。因为闭包函数体只包含了一个单一表达式(`s1 > s2`),该表达式返回 `Bool` 类型值,因此这里没有歧义,`return` 关键字可以省略。
|
||||
|
||||
<a name="shorthand_argument_names"></a>
|
||||
### 参数名称缩写(Shorthand Argument Names)
|
||||
|
||||
Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过`$0`,`$1`,`$2`来顺序调用闭包的参数。
|
||||
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 `$0`,`$1`,`$2` 来顺序调用闭包的参数,以此类推。
|
||||
|
||||
如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。
|
||||
`in`关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
|
||||
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。`in`关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { $0 > $1 } )
|
||||
reversedNames = names.sorted(by: { $0 > $1 } )
|
||||
```
|
||||
|
||||
在这个例子中,`$0`和`$1`表示闭包中第一个和第二个`String`类型的参数。
|
||||
在这个例子中,`$0`和`$1`表示闭包中第一个和第二个 `String` 类型的参数。
|
||||
|
||||
<a name="operator_functions"></a>
|
||||
### 运算符函数(Operator Functions)
|
||||
<a name="operator_methods"></a>
|
||||
### 运算符方法(Operator Methods)
|
||||
|
||||
实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。
|
||||
Swift 的`String`类型定义了关于大于号 (`>`) 的字符串实现,其作为一个函数接受两个`String`类型的参数并返回`Bool`类型的值。
|
||||
而这正好与`sort(_:)`方法的第二个参数需要的函数类型相符合。
|
||||
因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:
|
||||
实际上还有一种更简短的方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断出你想使用大于号的字符串函数实现:
|
||||
|
||||
```swift
|
||||
reversed = names.sort(>)
|
||||
reversedNames = names.sorted(by: >)
|
||||
```
|
||||
|
||||
更多关于运算符表达式的内容请查看 [运算符函数](./24_Advanced_Operators.html#operator_functions)。
|
||||
更多关于运算符方法的内容请查看[运算符方法](./25_Advanced_Operators.html#operator_methods)。
|
||||
|
||||
<a name="trailing_closures"></a>
|
||||
## 尾随闭包(Trailing Closures)
|
||||
|
||||
|
||||
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
|
||||
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
|
||||
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用*尾随闭包*来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
|
||||
|
||||
```swift
|
||||
func someFunctionThatTakesAClosure(closure: () -> Void) {
|
||||
@ -191,33 +176,33 @@ func someFunctionThatTakesAClosure(closure: () -> Void) {
|
||||
}
|
||||
|
||||
// 以下是不使用尾随闭包进行函数调用
|
||||
someFunctionThatTakesAClosure({
|
||||
someFunctionThatTakesAClosure(closure: {
|
||||
// 闭包主体部分
|
||||
})
|
||||
|
||||
// 以下是使用尾随闭包进行函数调用
|
||||
someFunctionThatTakesAClosure() {
|
||||
// 闭包主体部分
|
||||
// 闭包主体部分
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把`()`省略掉。
|
||||
|
||||
在上例中作为`sorted`函数参数的字符串排序闭包可以改写为:
|
||||
在[闭包表达式语法](#closure_expression_syntax)一节中作为 `sorted(by:)` 方法参数的字符串排序闭包可以改写为:
|
||||
|
||||
```swift
|
||||
reversed = names.sort() { $0 > $1 }
|
||||
reversedNames = names.sorted() { $0 > $1 }
|
||||
```
|
||||
|
||||
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。
|
||||
举例来说,Swift 的`Array`类型有一个`map`方法,其获取一个闭包表达式作为其唯一参数。
|
||||
数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。
|
||||
具体的映射方式和返回值类型由闭包来指定。
|
||||
如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 `()` 省略掉:
|
||||
|
||||
当提供给数组闭包函数后,`map`方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。
|
||||
```swift
|
||||
reversedNames = names.sorted { $0 > $1 }
|
||||
```
|
||||
|
||||
下例介绍了如何在`map`方法中使用尾随闭包将`Int`类型数组`[16,58,510]`转换为包含对应`String`类型的数组`["OneSix", "FiveEight", "FiveOneZero"]`:
|
||||
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。举例来说,Swift 的 `Array` 类型有一个 `map(_:)` 方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。
|
||||
|
||||
当提供给数组的闭包应用于每个数组元素后,`map(_:)` 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
|
||||
|
||||
下例介绍了如何在 `map(_:)` 方法中使用尾随闭包将 `Int` 类型数组 `[16, 58, 510]` 转换为包含对应 `String` 类型的值的数组`["OneSix", "FiveEight", "FiveOneZero"]`:
|
||||
|
||||
```swift
|
||||
let digitNames = [
|
||||
@ -227,112 +212,90 @@ let digitNames = [
|
||||
let numbers = [16, 58, 510]
|
||||
```
|
||||
|
||||
如上代码创建了一个数字位和它们名字映射的英文版本字典。
|
||||
同时定义了一个准备转换为字符串的整型数组。
|
||||
如上代码创建了一个整型数位和它们英文版本名字相映射的字典。同时还定义了一个准备转换为字符串数组的整型数组。
|
||||
|
||||
您现在可以通过传递一个尾随闭包给`numbers`的`map`方法来创建对应的字符串版本数组。
|
||||
需要注意的是调用`numbers.map`不需要在`map`后面包含任何括号,因为其只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过尾随方式进行撰写:
|
||||
你现在可以通过传递一个尾随闭包给 `numbers` 数组的 `map(_:)` 方法来创建对应的字符串版本数组:
|
||||
|
||||
```swift
|
||||
let strings = numbers.map {
|
||||
(var number) -> String in
|
||||
(number) -> String in
|
||||
var number = number
|
||||
var output = ""
|
||||
while number > 0 {
|
||||
repeat {
|
||||
output = digitNames[number % 10]! + output
|
||||
number /= 10
|
||||
}
|
||||
} while number > 0
|
||||
return output
|
||||
}
|
||||
// strings 常量被推断为字符串类型数组,即 [String]
|
||||
// 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
|
||||
```
|
||||
|
||||
`map`在数组中为每一个元素调用了闭包表达式。
|
||||
您不需要指定闭包的输入参数`number`的类型,因为可以通过要映射的数组类型进行推断。
|
||||
`map(_:)` 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 `number` 的类型,因为可以通过要映射的数组类型进行推断。
|
||||
|
||||
闭包`number`参数被声明为一个变量参数(变量的具体描述请参看[常量参数和变量参数](./06_Functions.html#constant_and_variable_parameters)),因此可以在闭包函数体内对其进行修改。闭包表达式制定了返回类型为`String`,以表明存储映射值的新数组类型为`String`。
|
||||
在该例中,局部变量 `number` 的值由闭包中的 `number` 参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 `String`,以表明存储映射值的新数组类型为 `String`。
|
||||
|
||||
闭包表达式在每次被调用的时候创建了一个字符串并返回。
|
||||
其使用求余运算符 (number % 10) 计算最后一位数字并利用`digitNames`字典获取所映射的字符串。
|
||||
闭包表达式在每次被调用的时候创建了一个叫做 `output` 的字符串并返回。其使用求余运算符(`number % 10`)计算最后一位数字并利用 `digitNames` 字典获取所映射的字符串。这个闭包能够用于创建任意正整数的字符串表示。
|
||||
|
||||
> 注意:
|
||||
> 字典`digitNames`下标后跟着一个叹号 (!),因为字典下标返回一个可选值 (optional value),表明即使该 key 不存在也不会查找失败。
|
||||
> 在上例中,它保证了`number % 10`可以总是作为一个`digitNames`字典的有效下标 key。
|
||||
> 因此叹号可以用于强制解析 (force-unwrap) 存储在可选下标项中的`String`类型值。
|
||||
> 注意
|
||||
> 字典 `digitNames` 下标后跟着一个叹号(`!`),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定 `number % 10` 总是 `digitNames` 字典的有效下标,因此叹号可以用于强制解包 (force-unwrap) 存储在下标的可选类型的返回值中的`String`类型的值。
|
||||
|
||||
从`digitNames`字典中获取的字符串被添加到输出的前部,逆序建立了一个字符串版本的数字。
|
||||
(在表达式`number % 10`中,如果number为16,则返回6,58返回8,510返回0)。
|
||||
从 `digitNames` 字典中获取的字符串被添加到 `output` 的*前部*,逆序建立了一个字符串版本的数字。(在表达式 `number % 10` 中,如果 `number` 为 `16`,则返回 `6`,`58` 返回 `8`,`510` 返回 `0`。)
|
||||
|
||||
`number`变量之后除以10。
|
||||
因为其是整数,在计算过程中未除尽部分被忽略。
|
||||
因此 16变成了1,58变成了5,510变成了51。
|
||||
`number` 变量之后除以 `10`。因为其是整数,在计算过程中未除尽部分被忽略。因此 `16` 变成了 `1`,`58` 变成了 `5`,`510` 变成了 `51`。
|
||||
|
||||
整个过程重复进行,直到`number /= 10`为0,这时闭包会将字符串输出,而`map`函数则会将字符串添加到所映射的数组中。
|
||||
整个过程重复进行,直到 `number /= 10` 为 `0`,这时闭包会将字符串 `output` 返回,而 `map(_:)` 方法则会将字符串添加到映射数组中。
|
||||
|
||||
上例中尾随闭包语法在函数后整洁封装了具体的闭包功能,而不再需要将整个闭包包裹在`map`函数的括号内。
|
||||
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 `map(_:)` 方法的括号内。
|
||||
|
||||
<a name="capturing_values"></a>
|
||||
## 捕获值(Capturing Values)
|
||||
## 值捕获(Capturing Values)
|
||||
|
||||
闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
|
||||
|
||||
闭包可以在其定义的上下文中捕获常量或变量。
|
||||
即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
|
||||
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
|
||||
|
||||
Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。
|
||||
嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
|
||||
|
||||
下例为一个叫做`makeIncrementor`的函数,其包含了一个叫做`incrementor`嵌套函数。
|
||||
嵌套函数`incrementor`从上下文中捕获了两个值,`runningTotal`和`amount`。
|
||||
之后`makeIncrementor`将`incrementor`作为闭包返回。
|
||||
每次调用`incrementor`时,其会以`amount`作为增量增加`runningTotal`的值。
|
||||
举个例子,这有一个叫做 `makeIncrementor` 的函数,其包含了一个叫做 `incrementor` 的嵌套函数。嵌套函数 `incrementor()` 从上下文中捕获了两个值,`runningTotal` 和 `amount`。捕获这些值之后,`makeIncrementor` 将 `incrementor` 作为闭包返回。每次调用 `incrementor` 时,其会以 `amount` 作为增量增加 `runningTotal` 的值。
|
||||
|
||||
```swift
|
||||
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
|
||||
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
|
||||
var runningTotal = 0
|
||||
func incrementor() -> Int {
|
||||
func incrementer() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
return incrementor
|
||||
return incrementer
|
||||
}
|
||||
```
|
||||
|
||||
`makeIncrementor`返回类型为`() -> Int`。
|
||||
这意味着其返回的是一个函数,而不是一个简单类型值。
|
||||
该函数在每次调用时不接受参数只返回一个`Int`类型的值。
|
||||
关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.html#function_types_as_return_types)。
|
||||
`makeIncrementor` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.html#function_types_as_return_types)。
|
||||
|
||||
`makeIncrementor`函数定义了一个整型变量`runningTotal`(初始为0) 用来存储当前跑步总数。
|
||||
该值通过`incrementor`返回。
|
||||
`makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementor` 的返回值。
|
||||
|
||||
`makeIncrementor`有一个`Int`类型的参数,其外部命名为`forIncrement`, 内部命名为`amount`,表示每次`incrementor`被调用时`runningTotal`将要增加的量。
|
||||
`makeIncrementer(forIncrement:)` 有一个 `Int` 类型的参数,其外部参数名为 `forIncrement`,内部参数名为 `amount`,该参数表示每次 `incrementor` 被调用时 `runningTotal` 将要增加的量。`makeIncrementer` 函数还定义了一个嵌套函数 `incrementor`,用来执行实际的增加操作。该函数简单地使 `runningTotal` 增加 `amount`,并将其返回。
|
||||
|
||||
`incrementor`函数用来执行实际的增加操作。
|
||||
该函数简单地使`runningTotal`增加`amount`,并将其返回。
|
||||
|
||||
如果我们单独看这个函数,会发现看上去不同寻常:
|
||||
如果我们单独考虑嵌套函数 `incrementer()`,会发现它有些不同寻常:
|
||||
|
||||
```swift
|
||||
func incrementor() -> Int {
|
||||
func incrementer() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
```
|
||||
|
||||
`incrementer`函数并没有任何参数,但是在函数体内访问了`runningTotal`和`amount`变量。这是因为其通过捕获在包含它的函数体内已经存在的`runningTotal`和`amount`变量的引用(reference)而实现。捕捉了变量引用,保证了`runningTotal`和`amount`变量在调用完`makeIncrementer`后不会消失,并且保证了在下一次执行`incrementer`函数时,`runningTotal`可以继续增加。
|
||||
`incrementer()` 函数并没有任何参数,但是在函数体内访问了 `runningTotal` 和 `amount` 变量。这是因为它从外围函数捕获了 `runningTotal` 和 `amount` 变量的*引用*。捕获引用保证了 `runningTotal` 和 `amount` 变量在调用完 `makeIncrementer` 后不会消失,并且保证了在下一次执行 `incrementer` 函数时,`runningTotal` 依旧存在。
|
||||
|
||||
> 注意:
|
||||
> 为了优化,Swift可能会捕捉和保存一份对值的拷贝,如果这个值是不可变或是在闭包外的。
|
||||
> Swift同样负责被捕捉的所有变量的内存管理,包括释放不被需要的变量。
|
||||
> 注意
|
||||
> 为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。
|
||||
> Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
|
||||
|
||||
下面代码为一个使用`makeIncrementor`的例子:
|
||||
下面是一个使用 `makeIncrementor` 的例子:
|
||||
|
||||
```swift
|
||||
let incrementByTen = makeIncrementor(forIncrement: 10)
|
||||
```
|
||||
|
||||
该例子定义了一个叫做`incrementByTen`的常量,该常量指向一个每次调用会加10的`incrementor`函数。
|
||||
调用这个函数多次可以得到以下结果:
|
||||
该例子定义了一个叫做 `incrementByTen` 的常量,该常量指向一个每次调用会将其 `runningTotal` 变量增加 `10` 的 `incrementor` 函数。调用这个函数多次可以得到以下结果:
|
||||
|
||||
```swift
|
||||
incrementByTen()
|
||||
@ -343,34 +306,151 @@ incrementByTen()
|
||||
// 返回的值为30
|
||||
```
|
||||
|
||||
如果您创建了另一个`incrementor`,其会有一个属于自己的独立的`runningTotal`变量的引用。
|
||||
下面的例子中,`incrementBySevne`捕获了一个新的`runningTotal`变量,该变量和`incrementByTen`中捕获的变量没有任何联系:
|
||||
如果你创建了另一个 `incrementor`,它会有属于自己的引用,指向一个全新、独立的 `runningTotal` 变量:
|
||||
|
||||
```swift
|
||||
let incrementBySeven = makeIncrementor(forIncrement: 7)
|
||||
incrementBySeven()
|
||||
// 返回的值为7
|
||||
```
|
||||
|
||||
再次调用原来的 `incrementByTen` 会继续增加它自己的 `runningTotal` 变量,该变量和 `incrementBySeven` 中捕获的变量没有任何联系:
|
||||
|
||||
```swift
|
||||
incrementByTen()
|
||||
// 返回的值为40
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 如果您将闭包赋值给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例,您将创建一个在闭包和实例间的强引用环。
|
||||
> Swift 使用捕获列表来打破这种强引用环。更多信息,请参考 [闭包引起的循环强引用](./16_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。
|
||||
> 注意
|
||||
> 如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./16_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。
|
||||
|
||||
<a name="closures_are_reference_types"></a>
|
||||
## 闭包是引用类型(Closures Are Reference Types)
|
||||
|
||||
上面的例子中,`incrementBySeven`和`incrementByTen`是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。
|
||||
这是因为函数和闭包都是引用类型。
|
||||
上面的例子中,`incrementBySeven` 和 `incrementByTen` 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是*引用类型*。
|
||||
|
||||
无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。
|
||||
上面的例子中,`incrementByTen`指向闭包的引用是一个常量,而并非闭包内容本身。
|
||||
无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的*引用*。上面的例子中,指向闭包的引用 `incrementByTen` 是一个常量,而并非闭包内容本身。
|
||||
|
||||
这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:
|
||||
这也意味着如果你将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:
|
||||
|
||||
```swift
|
||||
let alsoIncrementByTen = incrementByTen
|
||||
alsoIncrementByTen()
|
||||
// 返回的值为50
|
||||
```
|
||||
|
||||
<a name="escaping_closures"></a>
|
||||
## 逃逸闭包(Escaping Closures)
|
||||
|
||||
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中*逃逸*。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 `@escaping`,用来指明这个闭包是允许“逃逸”出这个函数的。
|
||||
|
||||
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:
|
||||
|
||||
```swift
|
||||
var completionHandlers: [() -> Void] = []
|
||||
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
|
||||
completionHandlers.append(completionHandler)
|
||||
}
|
||||
```
|
||||
|
||||
`someFunctionWithEscapingClosure(_:)` 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 `@escaping`,就会得到一个编译错误。
|
||||
|
||||
将一个闭包标记为 `@escaping` 意味着你必须在闭包中显式地引用 `self`。比如说,在下面的代码中,传递到 `someFunctionWithEscapingClosure(_:)` 中的闭包是一个逃逸闭包,这意味着它需要显式地引用 `self`。相对的,传递到 `someFunctionWithNonescapingClosure(_:)` 中的闭包是一个非逃逸闭包,这意味着它可以隐式引用 `self`。
|
||||
|
||||
|
||||
```swift
|
||||
func someFunctionWithNonescapingClosure(closure: () -> Void) {
|
||||
closure()
|
||||
}
|
||||
|
||||
class SomeClass {
|
||||
var x = 10
|
||||
func doSomething() {
|
||||
someFunctionWithEscapingClosure { self.x = 100 }
|
||||
someFunctionWithNonescapingClosure { x = 200 }
|
||||
}
|
||||
}
|
||||
|
||||
let instance = SomeClass()
|
||||
instance.doSomething()
|
||||
print(instance.x)
|
||||
// 打印出 "200"
|
||||
|
||||
completionHandlers.first?()
|
||||
print(instance.x)
|
||||
// 打印出 "100"
|
||||
```
|
||||
|
||||
<a name="autoclosures"></a>
|
||||
## 自动闭包(Autoclosures)
|
||||
|
||||
*自动闭包*是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
|
||||
|
||||
我们经常会*调用*采用自动闭包的函数,但是很少去*实现*这样的函数。举个例子来说,`assert(condition:message:file:line:)` 函数接受自动闭包作为它的 `condition` 参数和 `message` 参数;它的 `condition` 参数仅会在 debug 模式下被求值,它的 `message` 参数仅当 `condition` 参数为 `false` 时被计算求值。
|
||||
|
||||
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。下面的代码展示了闭包如何延时求值。
|
||||
|
||||
```swift
|
||||
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
|
||||
print(customersInLine.count)
|
||||
// 打印出 "5"
|
||||
|
||||
let customerProvider = { customersInLine.remove(at: 0) }
|
||||
print(customersInLine.count)
|
||||
// 打印出 "5"
|
||||
|
||||
print("Now serving \(customerProvider())!")
|
||||
// Prints "Now serving Chris!"
|
||||
print(customersInLine.count)
|
||||
// 打印出 "4"
|
||||
```
|
||||
|
||||
尽管在闭包的代码中,`customersInLine` 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,`customerProvider` 的类型不是 `String`,而是 `() -> String`,一个没有参数且返回值为 `String` 的函数。
|
||||
|
||||
将闭包作为参数传递给函数时,你能获得同样的延时求值行为。
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
|
||||
func serve(customer customerProvider: () -> String) {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
serve(customer: { customersInLine.remove(at: 0) } )
|
||||
// 打印出 "Now serving Alex!"
|
||||
```
|
||||
|
||||
上面的 `serve(customer:)` 函数接受一个返回顾客名字的显式的闭包。下面这个版本的 `serve(customer:)` 完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为 `@autoclosure` 来接收一个自动闭包。现在你可以将该函数当作接受 `String` 类型参数(而非闭包)的函数来调用。`customerProvider` 参数将自动转化为一个闭包,因为该参数被标记了 `@autoclosure` 特性。
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Ewa", "Barry", "Daniella"]
|
||||
func serve(customer customerProvider: @autoclosure () -> String) {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
serve(customer: customersInLine.remove(at: 0))
|
||||
// 打印出 "Now serving Ewa!"
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 过度使用 `autoclosures` 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
|
||||
|
||||
如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性的讲解见上面的[逃逸闭包](#escaping_closures)。
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Barry", "Daniella"]
|
||||
var customerProviders: [() -> String] = []
|
||||
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
|
||||
customerProviders.append(customerProvider)
|
||||
}
|
||||
collectCustomerProviders(customersInLine.remove(at: 0))
|
||||
collectCustomerProviders(customersInLine.remove(at: 0))
|
||||
|
||||
print("Collected \(customerProviders.count) closures.")
|
||||
// 打印出 "Collected 2 closures."
|
||||
for customerProvider in customerProviders {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
// 打印出 "Now serving Barry!"
|
||||
// 打印出 "Now serving Daniella!"
|
||||
```
|
||||
|
||||
在上面的代码中,`collectCustomerProviders(_:)` 函数并没有调用传入的 `customerProvider` 闭包,而是将闭包追加到了 `customerProviders` 数组中。这个数组定义在函数作用域范围外,这意味着数组内的闭包能够在函数返回之后被调用。因此,`customerProvider` 参数必须允许“逃逸”出函数作用域。
|
||||
|
||||
|
||||
@ -8,23 +8,30 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[futantan](https://github.com/futantan)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Channe](https://github.com/Channe)
|
||||
> 校对:[shanks](http://codebuild.me),
|
||||
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-13
|
||||
|
||||
本页内容包含:
|
||||
|
||||
- [枚举语法(Enumeration Syntax)](#enumeration_syntax)
|
||||
- [匹配枚举值与`Swith`语句(Matching Enumeration Values with a Switch Statement)](#matching_enumeration_values_with_a_switch_statement)
|
||||
- [相关值(Associated Values)](#associated_values)
|
||||
- [使用 Switch 语句匹配枚举值(Matching Enumeration Values with a Switch Statement)](#matching_enumeration_values_with_a_switch_statement)
|
||||
- [关联值(Associated Values)](#associated_values)
|
||||
- [原始值(Raw Values)](#raw_values)
|
||||
- [递归枚举(Recursive Enumerations)](#recursive_enumerations)
|
||||
|
||||
**枚举**定义了一个通用类型的一组相关值,使你可以在你的代码中以一种安全的方式来使用这些值。
|
||||
*枚举*为一组相关的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值。
|
||||
|
||||
如果你熟悉 C 语言,你就会知道,在 C 语言中枚举将枚举名和一个整型值相对应。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。
|
||||
如果你熟悉 C 语言,你会知道在 C 语言中,枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。
|
||||
|
||||
此外,枚举成员可以指定任何类型的相关值存储到枚举成员值中,就像其他语言中的联合体(unions)和变体(variants)。你可以定义一组通用的相关成员作为枚举的一部分,每一组都有不同的一组与它相关的适当类型的数值。
|
||||
此外,枚举成员可以指定任意类型的关联值存储到枚举成员中,就像其他语言中的联合体(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)。
|
||||
欲了解更多相关信息,请参见[属性(Properties)](./10_Properties.html),[方法(Methods)](./11_Methods.html),[构造过程(Initialization)](./14_Initialization.html),[扩展(Extensions)](./21_Extensions.html)和[协议(Protocols)](./22_Protocols.html)。
|
||||
|
||||
<a name="enumeration_syntax"></a>
|
||||
## 枚举语法
|
||||
@ -33,50 +40,50 @@
|
||||
|
||||
```swift
|
||||
enum SomeEnumeration {
|
||||
// 枚举定义放在这里
|
||||
// 枚举定义放在这里
|
||||
}
|
||||
```
|
||||
|
||||
下面是指南针四个方向的例子:
|
||||
下面是用枚举表示指南针四个方向的例子:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case North
|
||||
case South
|
||||
case East
|
||||
case West
|
||||
case North
|
||||
case South
|
||||
case East
|
||||
case West
|
||||
}
|
||||
```
|
||||
|
||||
枚举中定义的值(如 `North`,`South`,`East`和`West`)是这个枚举的**成员值**(或**成员**)。`case`关键词表示一行新的成员值将被定义。
|
||||
枚举中定义的值(如 `North`,`South`,`East`和`West`)是这个枚举的*成员值*(或*成员*)。你使用`case`关键字来定义一个新的枚举成员值。
|
||||
|
||||
> 注意:
|
||||
> 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoint`例子中,`North`,`South`,`East`和`West`不会隐式地赋值为`0`,`1`,`2`和`3`。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的`CompassPoint`类型。
|
||||
> 注意
|
||||
> 与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoint`例子中,`North`,`South`,`East`和`West`不会被隐式地赋值为`0`,`1`,`2`和`3`。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的`CompassPoint`类型。
|
||||
|
||||
多个成员值可以出现在同一行上,用逗号隔开:
|
||||
|
||||
```swift
|
||||
enum Planet {
|
||||
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
|
||||
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
|
||||
}
|
||||
```
|
||||
|
||||
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如`CompassPoint`和`Planet`)必须以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:
|
||||
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如`CompassPoint`和`Planet`)应该以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:
|
||||
|
||||
```swift
|
||||
var directionToHead = CompassPoint.West
|
||||
```
|
||||
|
||||
`directionToHead`的类型可以在它被`CompassPoint`的一个可能值初始化时推断出来。一旦`directionToHead`被声明为一个`CompassPoint`,你可以使用一个缩写语法(.)将其设置为另一个`CompassPoint`的值:
|
||||
`directionToHead`的类型可以在它被`CompassPoint`的某个值初始化时推断出来。一旦`directionToHead`被声明为`CompassPoint`类型,你可以使用更简短的点语法将其设置为另一个`CompassPoint`的值:
|
||||
|
||||
```swift
|
||||
directionToHead = .East
|
||||
```
|
||||
|
||||
当`directionToHead`的类型已知时,再次为其赋值可以省略枚举名。使用显式类型的枚举值可以让代码具有更好的可读性。
|
||||
当`directionToHead`的类型已知时,再次为其赋值可以省略枚举类型名。在使用具有显式类型的枚举值时,这种写法让代码具有更好的可读性。
|
||||
|
||||
<a name="matching_enumeration_values_with_a_switch_statement"></a>
|
||||
## 匹配枚举值和`Switch`语句
|
||||
## 使用 Switch 语句匹配枚举值
|
||||
|
||||
你可以使用`switch`语句匹配单个枚举值:
|
||||
|
||||
@ -99,11 +106,11 @@ switch directionToHead {
|
||||
|
||||
“判断`directionToHead`的值。当它等于`.North`,打印`“Lots of planets have a north”`。当它等于`.South`,打印`“Watch out for penguins”`。”
|
||||
|
||||
等等以此类推。
|
||||
……以此类推。
|
||||
|
||||
正如在[控制流(Control Flow)](./05_Control_Flow.html)中介绍的那样,在判断一个枚举类型的值时,`switch`语句必须穷举所有情况。如果忽略了`.West`这种情况,上面那段代码将无法通过编译,因为它没有考虑到`CompassPoint`的全部成员。强制性全部穷举的要求确保了枚举成员不会被意外遗漏。
|
||||
正如在[控制流(Control Flow)](./05_Control_Flow.html)中介绍的那样,在判断一个枚举类型的值时,`switch`语句必须穷举所有情况。如果忽略了`.West`这种情况,上面那段代码将无法通过编译,因为它没有考虑到`CompassPoint`的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
|
||||
|
||||
当不需要匹配每个枚举成员的时候,你可以提供一个默认`default`分支来涵盖所有未明确被提出的枚举成员:
|
||||
当不需要匹配每个枚举成员的时候,你可以提供一个`default`分支来涵盖所有未明确处理的枚举成员:
|
||||
|
||||
```swift
|
||||
let somePlanet = Planet.Earth
|
||||
@ -117,54 +124,54 @@ default:
|
||||
```
|
||||
|
||||
<a name="associated_values"></a>
|
||||
## 相关值(Associated Values)
|
||||
## 关联值(Associated Values)
|
||||
|
||||
上一小节的例子演示了如何定义(分类)枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并且在赋值之后查看这个值。不管怎样,如果有时候能够把其他类型的**相关值**和成员值一起存储起来会很有用。这能让你存储成员值之外的自定义信息,并且当你每次在代码中使用该成员时允许这个信息产生变化。
|
||||
上一小节的例子演示了如何定义和分类枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候能够把其他类型的*关联值*和成员值一起存储起来会很有用。这能让你连同成员值一起存储额外的自定义信息,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
|
||||
|
||||
你可以定义 Swift 的枚举存储任何类型的相关值,如果需要的话,每个成员的数据类型可以是各不相同的。枚举的这种特性跟其他语言中的可辨识联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
|
||||
你可以定义 Swift 枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
|
||||
|
||||
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有 UPC-A 格式的一维条形码,它使用数字 0 到 9。每一个条形码都有一个代表“数字系统”的数字,该数字后接 5 个代表“生产代码”的数字,接下来是5位“产品代码”。最后一个数字是“检查”位,用来验证代码是否被正确扫描:
|
||||
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用`0`到`9`的数字的 UPC-A 格式的一维条形码。每一个条形码都有一个代表“数字系统”的数字,该数字后接五位代表“厂商代码”的数字,接下来是五位代表“产品代码”的数字。最后一个数字是“检查”位,用来验证代码是否被正确扫描:
|
||||
|
||||
<img width="252" height="120" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_UPC_2x.png">
|
||||
|
||||
其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2953 个字符的字符串:
|
||||
其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2,953 个字符的字符串:
|
||||
|
||||
<img width="169" height="169" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_QR_2x.png">
|
||||
|
||||
对于库存跟踪系统来说,能够把 UPC-A 码作为四个整型值的元组,和把 QR 码作为一个任何长度的字符串存储起来是方便的。
|
||||
这便于库存跟踪系统用包含四个整型值的元组存储 UPC-A 码,以及用任意长度的字符串储存 QR 码。
|
||||
|
||||
在 Swift 中,使用如下方式定义两种商品条码的枚举:
|
||||
在 Swift 中,使用如下方式定义表示两种商品条形码的枚举:
|
||||
|
||||
```swift
|
||||
enum Barcode {
|
||||
case UPCA(Int, Int, Int, Int)
|
||||
case QRCode(String)
|
||||
case UPCA(Int, Int, Int, Int)
|
||||
case QRCode(String)
|
||||
}
|
||||
```
|
||||
|
||||
以上代码可以这么理解:
|
||||
|
||||
“定义一个名为`Barcode`的枚举类型,它可以是`UPCA`的一个相关值(`Int`,`Int`,`Int`,`Int`),或者是`QRCode`的一个字符串类型(`String`)相关值。”
|
||||
“定义一个名为`Barcode`的枚举类型,它的一个成员值是具有`(Int,Int,Int,Int)`类型关联值的`UPCA`,另一个成员值是具有`String`类型关联值的`QRCode`。”
|
||||
|
||||
这个定义不提供任何`Int`或`String`的实际值,它只是定义了,当`Barcode`常量和变量等于`Barcode.UPCA`或`Barcode.QRCode`时,相关值的类型。
|
||||
这个定义不提供任何`Int`或`String`类型的关联值,它只是定义了,当`Barcode`常量和变量等于`Barcode.UPCA`或`Barcode.QRCode`时,可以存储的关联值的类型。
|
||||
|
||||
然后可以使用任何一种条码类型创建新的条码,如:
|
||||
然后可以使用任意一种条形码类型创建新的条形码,例如:
|
||||
|
||||
```swift
|
||||
var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
|
||||
```
|
||||
|
||||
以上例子创建了一个名为`productBarcode`的变量,并且赋给它一个`Barcode.UPCA`的相关元组值`(8, 85909, 51226, 3)`。
|
||||
上面的例子创建了一个名为`productBarcode`的变量,并将`Barcode.UPCA`赋值给它,关联的元组值为`(8, 85909, 51226, 3)`。
|
||||
|
||||
同一个商品可以被分配给一个不同类型的条形码,如:
|
||||
同一个商品可以被分配一个不同类型的条形码,例如:
|
||||
|
||||
```swift
|
||||
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
|
||||
```
|
||||
|
||||
这时,原始的`Barcode.UPCA`和其整数值被新的`Barcode.QRCode`和其字符串值所替代。条形码的常量和变量可以存储一个`.UPCA`或者一个`.QRCode`(连同它的相关值),但是在任何指定时间只能存储其中之一。
|
||||
这时,原始的`Barcode.UPCA`和其整数关联值被新的`Barcode.QRCode`和其字符串关联值所替代。`Barcode`类型的常量和变量可以存储一个`.UPCA`或者一个`.QRCode`(连同它们的关联值),但是在同一时间只能存储这两个值中的一个。
|
||||
|
||||
像以前那样,不同的条形码类型可以使用一个 switch 语句来检查,然而这次相关值可以被提取作为 switch 语句的一部分。你可以在`switch`的 case 分支代码中提取每个相关值作为一个常量(用`let`前缀)或者作为一个变量(用`var`前缀)来使用:
|
||||
像先前那样,可以使用一个 switch 语句来检查不同的条形码类型。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在`switch`的 case 分支代码中提取每个关联值作为一个常量(用`let`前缀)或者作为一个变量(用`var`前缀)来使用:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
@ -176,7 +183,7 @@ case .QRCode(let productCode):
|
||||
// 输出 "QR code: ABCDEFGHIJKLMNOP."
|
||||
```
|
||||
|
||||
如果一个枚举成员的所有相关值被提取为常量,或者它们全部被提取为变量,为了简洁,你可以只放置一个`var`或者`let`标注在成员名称前:
|
||||
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个`let`或者`var`:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
@ -191,9 +198,9 @@ case let .QRCode(productCode):
|
||||
<a name="raw_values"></a>
|
||||
## 原始值(Raw Values)
|
||||
|
||||
在[相关值](#raw_values)小节的条形码例子中演示了一个枚举的成员如何声明它们存储不同类型的相关值。作为相关值的另一种选择,枚举成员可以被默认值(称为原始值)赋值,其中这些原始值具有相同的类型。
|
||||
在[关联值](#associated_values)小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
|
||||
|
||||
这里是一个枚举成员存储 ASCII 码的例子:
|
||||
这是一个使用 ASCII 码作为原始值的枚举:
|
||||
|
||||
```swift
|
||||
enum ASCIIControlCharacter: Character {
|
||||
@ -203,21 +210,22 @@ enum ASCIIControlCharacter: Character {
|
||||
}
|
||||
```
|
||||
|
||||
在这里,`ASCIIControlCharacter`的枚举类型的原始值类型被定义为字符型`Character`,并被设置了一些比较常见的 ASCII 控制字符。字符值的描述请详见[字符串和字符](./03_Strings_and_Characters.html)部分。
|
||||
枚举类型`ASCIIControlCharacter`的原始值类型被定义为`Character`,并设置了一些比较常见的 ASCII 控制字符。`Character`的描述详见[字符串和字符](./03_Strings_and_Characters.html)部分。
|
||||
|
||||
|
||||
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
|
||||
原始值可以是字符串,字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
|
||||
|
||||
>注意:
|
||||
>原始值和相关值是不相同的。当你开始在你的代码中定义枚举的时候原始值是被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终是相同的。相关值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次当你这么做得时候,它的值可以是不同的。
|
||||
> 注意
|
||||
> 原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。
|
||||
|
||||
<a name="implicitly_assigned_raw_values"></a>
|
||||
### 原始值的隐式赋值(Implicitly Assigned Raw Values)
|
||||
|
||||
在使用原始值为整数或者字符串类型的枚举时,不需要显式的为每一个成员赋值,这时,Swift将会自动为你赋值。
|
||||
在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。
|
||||
|
||||
例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
|
||||
例如,当使用整数作为原始值时,隐式赋值的值依次递增`1`。如果第一个枚举成员没有设置原始值,其原始值将为`0`。
|
||||
|
||||
下面的枚举是对之前`Planet`这个枚举的一个细化,利用原始整型值来表示每个 planet 在太阳系中的顺序:
|
||||
下面的枚举是对之前`Planet`这个枚举的一个细化,利用整型的原始值来表示每个行星在太阳系中的顺序:
|
||||
|
||||
```swift
|
||||
enum Planet: Int {
|
||||
@ -225,11 +233,11 @@ enum Planet: Int {
|
||||
}
|
||||
```
|
||||
|
||||
在上面的例子中,`Plant.Mercury`赋了初值`1`,`Planet.Venus`会拥有隐式赋值`2`,依次类推。
|
||||
在上面的例子中,`Plant.Mercury`的显式原始值为`1`,`Planet.Venus`的隐式原始值为`2`,依次类推。
|
||||
|
||||
当使用字符串作为枚举类型的初值时,每个枚举成员的隐式初值则为该成员的名称。
|
||||
当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。
|
||||
|
||||
下面的例子是`CompassPoint`枚举类型的精简版,使用字符串作为初值类型,隐式初始化为各个方向的名称:
|
||||
下面的例子是`CompassPoint`枚举的细化,使用字符串类型的原始值来表示各个方向的名称:
|
||||
|
||||
```swift
|
||||
enum CompassPoint: String {
|
||||
@ -237,7 +245,7 @@ enum CompassPoint: String {
|
||||
}
|
||||
```
|
||||
|
||||
上面例子中,`CompassPoint.South`拥有隐式初值`South`,依次类推。
|
||||
上面例子中,`CompassPoint.South`拥有隐式原始值`South`,依次类推。
|
||||
|
||||
使用枚举成员的`rawValue`属性可以访问该枚举成员的原始值:
|
||||
|
||||
@ -249,23 +257,24 @@ let sunsetDirection = CompassPoint.West.rawValue
|
||||
// sunsetDirection 值为 "West"
|
||||
```
|
||||
|
||||
### 使用原始值初始化枚举变量(Initializing from a Raw Value)
|
||||
<a name="initializing_from_a_raw_value"></a>
|
||||
### 使用原始值初始化枚举实例(Initializing from a Raw Value)
|
||||
|
||||
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法将原始值类型作为参数,返回值是枚举成员或`nil`。你可以使用这种初始化方法来创建一个新的枚举变量。
|
||||
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做`rawValue`的参数,参数类型即为原始值类型,返回值则是枚举成员或`nil`。你可以使用这个初始化方法来创建一个新的枚举实例。
|
||||
|
||||
这个例子通过原始值`7`从而创建枚举成员:
|
||||
这个例子利用原始值`7`创建了枚举成员`Uranus`:
|
||||
|
||||
```swift
|
||||
let possiblePlanet = Planet(rawValue: 7)
|
||||
// possiblePlanet 类型为 Planet? 值为 Planet.Uranus
|
||||
```
|
||||
|
||||
然而,并非所有可能的`Int`值都可以找到一个匹配的行星。正因为如此,构造函数可以返回一个**可选**的枚举成员。在上面的例子中,`possiblePlanet`是`Planet?`类型,或“可选的`Planet`”。
|
||||
然而,并非所有`Int`值都可以找到一个匹配的行星。因此,原始值构造器总是返回一个*可选*的枚举成员。在上面的例子中,`possiblePlanet`是`Planet?`类型,或者说“可选的`Planet`”。
|
||||
|
||||
>注意:
|
||||
>原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations#failable_initializers)
|
||||
> 注意
|
||||
> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations.html#failable_initializers)
|
||||
|
||||
如果你试图寻找一个位置为9的行星,通过参数为`rawValue`构造函数返回的可选`Planet`值将是`nil`:
|
||||
如果你试图寻找一个位置为`9`的行星,通过原始值构造器返回的可选`Planet`值将是`nil`:
|
||||
|
||||
```swift
|
||||
let positionToFind = 9
|
||||
@ -282,19 +291,15 @@ if let somePlanet = Planet(rawValue: positionToFind) {
|
||||
// 输出 "There isn't a planet at position 9
|
||||
```
|
||||
|
||||
这个范例使用可选绑定(optional binding),通过原始值`9`试图访问一个行星。`if let somePlanet = Planet(rawValue: 9)`语句获得一个可选`Planet`,如果可选`Planet`可以被获得,把`somePlanet`设置成该可选`Planet`的内容。在这个范例中,无法检索到位置为`9`的行星,所以`else`分支被执行。
|
||||
这个例子使用了可选绑定(optional binding),试图通过原始值`9`来访问一个行星。`if let somePlanet = Planet(rawValue: 9)`语句创建了一个可选`Planet`,如果可选`Planet`的值存在,就会赋值给`somePlanet`。在这个例子中,无法检索到位置为`9`的行星,所以`else`分支被执行。
|
||||
|
||||
<a name="recursive_enumerations"></a>
|
||||
## 递归枚举(Recursive Enumerations)
|
||||
|
||||
在对操作符进行描述的时候,使用枚举类型来对数据建模很方便,因为需要考虑的情况固定可枚举。操作符可以将两个由数字组成的算数表达式连接起来,例如,将`5`连接成复杂一些的表达式`5+4`。
|
||||
|
||||
算术表达式的一个重要特性是,表达式可以嵌套使用。例如,表达式`(5 + 4) * 2`乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也许要支持这种嵌套————这表示枚举类型需要支持递归。
|
||||
|
||||
`递归枚举(recursive enumeration)`是一种枚举类型,表示它的枚举中,有一个或多个枚举成员拥有该枚举的其他成员作为相关值。使用递归枚举时,编译器会插入一个中间层。你可以在枚举成员前加上`indirect`来表示这成员可递归。
|
||||
|
||||
例如,下面的例子中,枚举类型存储了简单的算数表达式:
|
||||
*递归枚举(recursive enumeration)*是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上`indirect`来表示该成员可递归。
|
||||
|
||||
例如,下面的例子中,枚举类型存储了简单的算术表达式:
|
||||
|
||||
```swift
|
||||
enum ArithmeticExpression {
|
||||
@ -304,7 +309,7 @@ enum ArithmeticExpression {
|
||||
}
|
||||
```
|
||||
|
||||
你也可以在枚举类型开头加上`indirect`关键字来表示它的所有成员都是可递归的:
|
||||
你也可以在枚举类型开头加上`indirect`关键字来表明它的所有成员都是可递归的:
|
||||
|
||||
```swift
|
||||
indirect enum ArithmeticExpression {
|
||||
@ -314,9 +319,16 @@ indirect enum ArithmeticExpression {
|
||||
}
|
||||
```
|
||||
|
||||
上面定义的枚举类型可以存储三种算数表达式:纯数字、两个表达式的相加、两个表达式相乘。`Addition` 和 `Multiplication`成员的相关值也是算数表达式————这些相关值使得嵌套表达式成为可能。
|
||||
上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员`Addition`和`Multiplication`的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。例如,表达式`(5 + 4) * 2`,乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也需要支持这种嵌套——这意味着枚举类型需要支持递归。下面的代码展示了使用`ArithmeticExpression `这个递归枚举创建表达式`(5 + 4) * 2`
|
||||
|
||||
递归函数可以很直观地使用具有递归性质的数据结构。例如,下面是一个计算算术表达式的函数:
|
||||
```swift
|
||||
let five = ArithmeticExpression.Number(5)
|
||||
let four = ArithmeticExpression.Number(4)
|
||||
let sum = ArithmeticExpression.Addition(five, four)
|
||||
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
|
||||
```
|
||||
|
||||
要操作具有递归性质的数据结构,使用递归函数是一种直截了当的方式。例如,下面是一个对算术表达式求值的函数:
|
||||
|
||||
```swift
|
||||
func evaluate(expression: ArithmeticExpression) -> Int {
|
||||
@ -329,12 +341,7 @@ func evaluate(expression: ArithmeticExpression) -> Int {
|
||||
return evaluate(left) * evaluate(right)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算 (5 + 4) * 2
|
||||
let five = ArithmeticExpression.Number(5)
|
||||
let four = ArithmeticExpression.Number(4)
|
||||
let sum = ArithmeticExpression.Addition(five, four)
|
||||
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
|
||||
|
||||
print(evaluate(product))
|
||||
// 输出 "18"
|
||||
```
|
||||
|
||||
@ -7,6 +7,12 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[SkyJean](https://github.com/SkyJean)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-29
|
||||
|
||||
> 2.2
|
||||
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [类和结构体对比](#comparing_classes_and_structures)
|
||||
@ -16,12 +22,12 @@
|
||||
- [字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为](#assignment_and_copy_behavior_for_strings_arrays_and_dictionaries)
|
||||
|
||||
|
||||
类和结构体是人们构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(常量、变量)和添加方法,从而扩展类和结构体的功能。
|
||||
*类*和*结构体*是人们构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(常量、变量)和添加方法,从而扩展类和结构体的功能。
|
||||
|
||||
与其他编程语言所不同的是,Swift 并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其它代码的外部接口。
|
||||
|
||||
> 注意:
|
||||
通常一个`类`的实例被称为`对象`。然而在Swift 中,类和结构体的关系要比在其他语言中更加的密切,本章中所讨论的大部分功能都可以用在类和结构体上。因此,我们会主要使用`实例`而不是`对象`。
|
||||
> 注意
|
||||
> 通常一个`类`的实例被称为`对象`。然而在 Swift 中,类和结构体的关系要比在其他语言中更加的密切,本章中所讨论的大部分功能都可以用在类和结构体上。因此,我们会主要使用`实例`而不是`对象`。
|
||||
|
||||
<a name="comparing_classes_and_structures"></a>
|
||||
###类和结构体对比
|
||||
@ -30,26 +36,27 @@ Swift 中类和结构体有很多共同点。共同处在于:
|
||||
|
||||
* 定义属性用于存储值
|
||||
* 定义方法用于提供功能
|
||||
* 定义附属脚本用于访问值
|
||||
* 定义下标操作使得可以通过下标语法来访问实例所包含的值
|
||||
* 定义构造器用于生成初始化值
|
||||
* 通过扩展以增加默认实现的功能
|
||||
* 实现协议以提供某种标准功能
|
||||
|
||||
更多信息请参见 [属性](./10_Properties.html),[方法](./11_Methods.html),[下标脚本](./12_Subscripts.html),[初始过程](./14_Initialization.html),[扩展](./20_Extensions.html),和[协议](./21_Protocols.html)。
|
||||
更多信息请参见[属性](./10_Properties.html),[方法](./11_Methods.html),[下标](./12_Subscripts.html),[构造过程](./14_Initialization.html),[扩展](./21_Extensions.html),和[协议](./22_Protocols.html)。
|
||||
|
||||
与结构体相比,类还有如下的附加功能:
|
||||
|
||||
* 继承允许一个类继承另一个类的特征
|
||||
* 类型转换允许在运行时检查和解释一个类实例的类型
|
||||
* 解构器允许一个类实例释放任何其所被分配的资源
|
||||
* 析构器允许一个类实例释放任何其所被分配的资源
|
||||
* 引用计数允许对一个类的多次引用
|
||||
|
||||
更多信息请参见[继承](./13_Inheritance.html),[类型转换](./20_Type_Casting.html),[析构过程](./15_Deinitialization),和[自动引用计数](./16_Automatic_Reference_Counting)。
|
||||
更多信息请参见[继承](./13_Inheritance.html),[类型转换](./19_Type_Casting.html),[析构过程](./15_Deinitialization.html),和[自动引用计数](./16_Automatic_Reference_Counting.html)。
|
||||
|
||||
> 注意:
|
||||
结构体总是通过被复制的方式在代码中传递,因此请不要使用引用计数。
|
||||
> 注意
|
||||
> 结构体总是通过被复制的方式在代码中传递,不使用引用计数。
|
||||
|
||||
### 定义
|
||||
<a name="definition_syntax"></a>
|
||||
### 定义语法
|
||||
|
||||
类和结构体有着类似的定义方式。我们通过关键字`class`和`struct`来分别表示类和结构体,并在一对大括号中定义它们的具体内容:
|
||||
|
||||
@ -62,8 +69,8 @@ struct SomeStructure {
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
在你每次定义一个新类或者结构体的时候,实际上你是有效地定义了一个新的 Swift 类型。因此请使用 `UpperCamelCase` 这种方式来命名(如 `SomeClass` 和`SomeStructure`等),以便符合标准Swift 类型的大写命名风格(如`String`,`Int`和`Bool`)。相反的,请使用`lowerCamelCase`这种方式为属性和方法命名(如`framerate`和`incrementCount`),以便和类区分。
|
||||
> 注意
|
||||
> 在你每次定义一个新类或者结构体的时候,实际上你是定义了一个新的 Swift 类型。因此请使用`UpperCamelCase`这种方式来命名(如`SomeClass`和`SomeStructure`等),以便符合标准 Swift 类型的大写命名风格(如`String`,`Int`和`Bool`)。相反的,请使用`lowerCamelCase`这种方式为属性和方法命名(如`framerate`和`incrementCount`),以便和类型名区分。
|
||||
|
||||
以下是定义结构体和定义类的示例:
|
||||
|
||||
@ -80,10 +87,11 @@ class VideoMode {
|
||||
}
|
||||
```
|
||||
|
||||
在上面的示例中我们定义了一个名为`Resolution`的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为`width`和`height`的存储属性。存储属性是捆绑和存储在类或结构体中的常量或变量。当这两个属性被初始化为整数`0`的时候,它们会被推断为`Int`类型。
|
||||
在上面的示例中我们定义了一个名为`Resolution`的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为`width`和`height`的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。当这两个属性被初始化为整数`0`的时候,它们会被推断为`Int`类型。
|
||||
|
||||
在上面的示例中我们还定义了一个名为`VideoMode`的类,用来描述一个视频显示器的特定模式。这个类包含了四个储存属性变量。第一个是`分辨率`,它被初始化为一个新的`Resolution`结构体的实例,具有`Resolution`的属性类型。新`VideoMode`实例同时还会初始化其它三个属性,它们分别是,初始值为`false`(意为“非隔行扫描视频”)的`interlaced`,回放帧率初始值为`0.0`的`frameRate`和值为可选`String`的`name`。`name`属性会被自动赋予一个默认值`nil`,意为“没有`name`值”,因为它是一个可选类型。
|
||||
在上面的示例中我们还定义了一个名为`VideoMode`的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是`分辨率`,它被初始化为一个新的`Resolution`结构体的实例,属性类型被推断为`Resolution`。新`VideoMode`实例同时还会初始化其它三个属性,它们分别是,初始值为`false`的`interlaced`,初始值为`0.0`的`frameRate`,以及值为可选`String`的`name`。`name`属性会被自动赋予一个默认值`nil`,意为“没有`name`值”,因为它是一个可选类型。
|
||||
|
||||
<a name="class_and_structure_instances"></a>
|
||||
### 类和结构体实例
|
||||
|
||||
`Resolution`结构体和`VideoMode`类的定义仅描述了什么是`Resolution`和`VideoMode`。它们并没有描述一个特定的分辨率(resolution)或者视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。
|
||||
@ -97,9 +105,10 @@ let someVideoMode = VideoMode()
|
||||
|
||||
结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如`Resolution()`或`VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.html)章节会对类和结构体的初始化进行更详细的讨论。
|
||||
|
||||
<a name="accessing_properties"></a>
|
||||
### 属性访问
|
||||
|
||||
通过使用*点语法*(*dot syntax*),你可以访问实例中所含有的属性。其语法规则是,实例名后面紧跟属性名,两者通过点号(.)连接:
|
||||
通过使用*点语法*(*dot syntax*),你可以访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者通过点号(`.`)连接:
|
||||
|
||||
```swift
|
||||
print("The width of someResolution is \(someResolution.width)")
|
||||
@ -115,7 +124,7 @@ print("The width of someVideoMode is \(someVideoMode.resolution.width)")
|
||||
// 输出 "The width of someVideoMode is 0"
|
||||
```
|
||||
|
||||
你也可以使用点语法为属性变量赋值:
|
||||
你也可以使用点语法为变量属性赋值:
|
||||
|
||||
```swift
|
||||
someVideoMode.resolution.width = 1280
|
||||
@ -123,10 +132,11 @@ print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
|
||||
// 输出 "The width of someVideoMode is now 1280"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
与 Objective-C 语言不同的是,Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了`someVideoMode`中`resolution`属性的`width`这个子属性,以上操作并不需要重新设置`resolution`属性。
|
||||
> 注意
|
||||
> 与 Objective-C 语言不同的是,Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了`someVideoMode`中`resolution`属性的`width`这个子属性,以上操作并不需要重新为整个`resolution`属性设置新值。
|
||||
|
||||
### 结构体类型的成员逐一构造器(Memberwise Initializers for Structure Types)
|
||||
<a name="memberwise_initializers_for_structure_types"></a>
|
||||
### 结构体类型的成员逐一构造器(Memberwise Initializers for Structure Types)
|
||||
|
||||
所有结构体都有一个自动生成的*成员逐一构造器*,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
|
||||
|
||||
@ -139,24 +149,24 @@ let vga = Resolution(width:640, height: 480)
|
||||
<a name="structures_and_enumerations_are_value_types"></a>
|
||||
## 结构体和枚举是值类型
|
||||
|
||||
*值类型*被赋予给一个变量、常量或者本身被传递给一个函数的时候,实际上操作的是其的*拷贝*。
|
||||
*值类型*被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被*拷贝*。
|
||||
|
||||
在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,并且都是以结构体的形式在后台所实现。
|
||||
在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,并且在底层都是以结构体的形式所实现。
|
||||
|
||||
在 Swift 中,所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。
|
||||
|
||||
请看下面这个示例,其使用了前一个示例中`Resolution`结构体:
|
||||
请看下面这个示例,其使用了前一个示例中的`Resolution`结构体:
|
||||
|
||||
```swift
|
||||
let hd = Resolution(width: 1920, height: 1080)
|
||||
var cinema = hd
|
||||
```
|
||||
|
||||
在以上示例中,声明了一个名为`hd`的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的`Resolution`实例。
|
||||
在以上示例中,声明了一个名为`hd`的常量,其值为一个初始化为全高清视频分辨率(`1920` 像素宽,`1080` 像素高)的`Resolution`实例。
|
||||
|
||||
然后示例中又声明了一个名为`cinema`的变量,其值为之前声明的`hd`。因为`Resolution`是一个结构体,所以`cinema`的值其实是`hd`的一个拷贝副本,而不是`hd`本身。尽管`hd`和`cinema`有着相同的宽(width)和高(height)属性,但是在后台中,它们是两个完全不同的实例。
|
||||
然后示例中又声明了一个名为`cinema`的变量,并将`hd`赋值给它。因为`Resolution`是一个结构体,所以`cinema`的值其实是`hd`的一个拷贝副本,而不是`hd`本身。尽管`hd`和`cinema`有着相同的宽(width)和高(height),但是在幕后它们是两个完全不同的实例。
|
||||
|
||||
下面,为了符合数码影院放映的需求(2048 像素宽,1080 像素高),`cinema`的`width`属性需要作如下修改:
|
||||
下面,为了符合数码影院放映的需求(`2048` 像素宽,`1080` 像素高),`cinema`的`width`属性需要作如下修改:
|
||||
|
||||
```swift
|
||||
cinema.width = 2048
|
||||
@ -172,11 +182,11 @@ print("cinema is now \(cinema.width) pixels wide")
|
||||
然而,初始的`hd`实例中`width`属性还是`1920`:
|
||||
|
||||
```swift
|
||||
print("hd is still \(hd.width ) pixels wide")
|
||||
print("hd is still \(hd.width) pixels wide")
|
||||
// 输出 "hd is still 1920 pixels wide"
|
||||
```
|
||||
|
||||
在将`hd`赋予给`cinema`的时候,实际上是将`hd`中所存储的`值(values)`进行拷贝,然后将拷贝的数据存储到新的`cinema`实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将`cinema`的`width`修改为`2048`并不会影响`hd`中的`width`的值。
|
||||
在将`hd`赋予给`cinema`的时候,实际上是将`hd`中所存储的值进行拷贝,然后将拷贝的数据存储到新的`cinema`实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将`cinema`的`width`修改为`2048`并不会影响`hd`中的`width`的值。
|
||||
|
||||
枚举也遵循相同的行为准则:
|
||||
|
||||
@ -193,12 +203,12 @@ if rememberedDirection == .West {
|
||||
// 输出 "The remembered direction is still .West"
|
||||
```
|
||||
|
||||
上例中`rememberedDirection`被赋予了`currentDirection`的值(value),实际上它被赋予的是值(value)的一个拷贝。赋值过程结束后再修改`currentDirection`的值并不影响`rememberedDirection`所储存的原始值(value)的拷贝。
|
||||
上例中`rememberedDirection`被赋予了`currentDirection`的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改`currentDirection`的值并不影响`rememberedDirection`所储存的原始值的拷贝。
|
||||
|
||||
<a name="classes_are_reference_types"></a>
|
||||
## 类是引用类型
|
||||
|
||||
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,操作的是引用,其并不是拷贝。因此,引用的是已存在的实例本身而不是其拷贝。
|
||||
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。
|
||||
|
||||
请看下面这个示例,其使用了之前定义的`VideoMode`类:
|
||||
|
||||
@ -210,9 +220,9 @@ tenEighty.name = "1080i"
|
||||
tenEighty.frameRate = 25.0
|
||||
```
|
||||
|
||||
以上示例中,声明了一个名为`tenEighty`的常量,其引用了一个`VideoMode`类的新实例。在之前的示例中,这个视频模式(video mode)被赋予了HD分辨率(1920*1080)的一个拷贝(`hd`)。同时设置为交错(interlaced),命名为`“1080i”`。最后,其帧率是`25.0`帧每秒。
|
||||
以上示例中,声明了一个名为`tenEighty`的常量,其引用了一个`VideoMode`类的新实例。在之前的示例中,这个视频模式(video mode)被赋予了HD分辨率(`1920`*`1080`)的一个拷贝(即`hd`实例)。同时设置为`interlaced`,命名为`“1080i”`。最后,其帧率是`25.0`帧每秒。
|
||||
|
||||
然后,`tenEighty` 被赋予名为`alsoTenEighty`的新常量,同时对`alsoTenEighty`的帧率进行修改:
|
||||
然后,`tenEighty`被赋予名为`alsoTenEighty`的新常量,同时对`alsoTenEighty`的帧率进行修改:
|
||||
|
||||
```swift
|
||||
let alsoTenEighty = tenEighty
|
||||
@ -221,25 +231,26 @@ alsoTenEighty.frameRate = 30.0
|
||||
|
||||
因为类是引用类型,所以`tenEight`和`alsoTenEight`实际上引用的是相同的`VideoMode`实例。换句话说,它们是同一个实例的两种叫法。
|
||||
|
||||
下面,通过查看`tenEighty`的`frameRate`属性,我们会发现它正确的显示了基本`VideoMode`实例的新帧率,其值为`30.0`:
|
||||
下面,通过查看`tenEighty`的`frameRate`属性,我们会发现它正确的显示了所引用的`VideoMode`实例的新帧率,其值为`30.0`:
|
||||
|
||||
```swift
|
||||
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
|
||||
// 输出 "The frameRate property of theEighty is now 30.0"
|
||||
```
|
||||
|
||||
需要注意的是`tenEighty`和`alsoTenEighty`被声明为*常量((constants)*而不是变量。然而你依然可以改变`tenEighty.frameRate`和`alsoTenEighty.frameRate`,因为这两个常量本身不会改变。它们并不`存储`这个`VideoMode`实例,在后台仅仅是对`VideoMode`实例的引用。所以,改变的是被引用的基础`VideoMode`的`frameRate`参数,而不改变常量的值。
|
||||
需要注意的是`tenEighty`和`alsoTenEighty`被声明为常量而不是变量。然而你依然可以改变`tenEighty.frameRate`和`alsoTenEighty.frameRate`,因为`tenEighty`和`alsoTenEighty`这两个常量的值并未改变。它们并不“存储”这个`VideoMode`实例,而仅仅是对`VideoMode`实例的引用。所以,改变的是被引用的`VideoMode`的`frameRate`属性,而不是引用`VideoMode`的常量的值。
|
||||
|
||||
<a name="identity_operators"></a>
|
||||
### 恒等运算符
|
||||
|
||||
因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
|
||||
因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
|
||||
|
||||
如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符:
|
||||
|
||||
* 等价于 ( === )
|
||||
* 不等价于 ( !== )
|
||||
* 等价于(`===`)
|
||||
* 不等价于(`!==`)
|
||||
|
||||
以下是运用这两个运算符检测两个常量或者变量是否引用同一个实例:
|
||||
运用这两个运算符检测两个常量或者变量是否引用同一个实例:
|
||||
|
||||
```swift
|
||||
if tenEighty === alsoTenEighty {
|
||||
@ -248,30 +259,31 @@ if tenEighty === alsoTenEighty {
|
||||
//输出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
|
||||
```
|
||||
|
||||
请注意```“等价于"```(用三个等号表示,===) 与```“等于"```(用两个等号表示,==)的不同:
|
||||
请注意,“等价于”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`)的不同:
|
||||
|
||||
* “等价于”表示两个类类型(class type)的常量或者变量引用同一个类实例。
|
||||
* “等于”表示两个实例的值“相等”或“相同”,判定时要遵照类设计者定义定义的评判标准,因此相比于“相等”,这是一种更加合适的叫法。
|
||||
* “等于”表示两个实例的值“相等”或“相同”,判定时要遵照设计者定义的评判标准,因此相对于“相等”来说,这是一种更加合适的叫法。
|
||||
|
||||
当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节[等价操作符](./24_Advanced_Operators.html#equivalence_operators)中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。
|
||||
当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节[等价操作符](./25_Advanced_Operators.html#equivalence_operators)中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。
|
||||
|
||||
<a name="pointers"></a>
|
||||
### 指针
|
||||
|
||||
如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。一个 Swift 常量或者变量引用一个引用类型的实例与 C 语言中的指针类似,不同的是并不直接指向内存中的某个地址,而且也不要求你使用星号(*)来表明你在创建一个引用。Swift 中这些引用与其它的常量或变量的定义方式相同。
|
||||
如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。一个引用某个引用类型实例的 Swift 常量或者变量,与 C 语言中的指针类似,但是并不直接指向某个内存地址,也不要求你使用星号(`*`)来表明你在创建一个引用。Swift 中的这些引用与其它的常量或变量的定义方式相同。
|
||||
|
||||
<a name="choosing_between_classes_and_structures"></a>
|
||||
## 类和结构体的选择
|
||||
|
||||
在你的代码中,你可以使用类和结构体来定义你的自定义数据类型。
|
||||
|
||||
然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者适用不同的任务。当你在考虑一个工程项目的数据构造和功能的时候,你需要决定每个数据构造是定义成类还是结构体。
|
||||
然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者适用不同的任务。当你在考虑一个工程项目的数据结构和功能的时候,你需要决定每个数据结构是定义成类还是结构体。
|
||||
|
||||
按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:
|
||||
|
||||
* 结构体的主要目的是用来封装少量相关简单数据值。
|
||||
* 有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用。
|
||||
* 任何在结构体中储存的值类型属性,也将会被拷贝,而不是被引用。
|
||||
* 结构体不需要去继承另一个已存在类型的属性或者行为。
|
||||
* 该数据结构的主要目的是用来封装少量相关简单数据值。
|
||||
* 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
|
||||
* 该数据结构中储存的值类型属性,也应该被拷贝,而不是被引用。
|
||||
* 该数据结构不需要去继承另一个既有类型的属性或者行为。
|
||||
|
||||
举例来说,以下情境中适合使用结构体:
|
||||
|
||||
@ -284,11 +296,9 @@ if tenEighty === alsoTenEighty {
|
||||
<a name="assignment_and_copy_behavior_for_strings_arrays_and_dictionaries"></a>
|
||||
## 字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为
|
||||
|
||||
Swift 中`字符串(String)`,`数组(Array)`和`字典(Dictionary)`类型均以结构体的形式实现。这意味着String,Array,Dictionary类型数据被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会发生拷贝行为(值传递方式)。
|
||||
Swift 中,许多基本类型,诸如`String`,`Array`和`Dictionary`类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。
|
||||
|
||||
Objective-C中`字符串(NSString)`,`数组(NSArray)`和`字典(NSDictionary)`类型均以类的形式实现,这与Swfit中以值传递方式是不同的。NSString,NSArray,NSDictionary在发生赋值或者传入函数(或方法)时,不会发生值拷贝,而是传递已存在实例的引用。
|
||||
Objective-C 中`NSString`,`NSArray`和`NSDictionary`类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。
|
||||
|
||||
|
||||
> 注意:
|
||||
以上是对于字符串、数组、字典和其它值的`拷贝`的描述。
|
||||
在你的代码中,拷贝好像是确实是在有拷贝行为的地方产生过。然而,在 Swift 的后台中,只有确有必要,`实际(actual)`拷贝才会被执行。Swift 管理所有的值拷贝以确保性能最优化的性能,所以你也没有必要去避免赋值以保证最优性能。(实际赋值由系统管理优化)
|
||||
> 注意
|
||||
> 以上是对字符串、数组、字典的“拷贝”行为的描述。在你的代码中,拷贝行为看起来似乎总会发生。然而,Swift 在幕后只在绝对必要时才执行实际的拷贝。Swift 管理所有的值拷贝以确保性能最优化,所以你没必要去回避赋值来保证性能最优化。
|
||||
|
||||
@ -1,427 +1,438 @@
|
||||
# 属性 (Properties)
|
||||
# 属性 (Properties)
|
||||
---
|
||||
|
||||
> 1.0
|
||||
> 翻译:[shinyzhu](https://github.com/shinyzhu)
|
||||
> 校对:[pp-prog](https://github.com/pp-prog) [yangsiy](https://github.com/yangsiy)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [存储属性(Stored Properties)](#stored_properties)
|
||||
- [计算属性(Computed Properties)](#computed_properties)
|
||||
- [属性观察器(Property Observers)](#property_observers)
|
||||
- [全局变量和局部变量(Global and Local Variables)](#global_and_local_variables)
|
||||
- [类型属性(Type Properties)](#type_properties)
|
||||
|
||||
*属性*将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。
|
||||
|
||||
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。
|
||||
|
||||
另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。
|
||||
|
||||
<a name="stored_properties"></a>
|
||||
## 存储属性
|
||||
|
||||
简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字`var`定义),也可以是*常量存储属性*(用关键字`let`定义)。
|
||||
|
||||
可以在定义存储属性的时候指定默认值,请参考[默认属性值](./14_Initialization.html#default_property_values)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[在初始化阶段修改常量存储属性](./14_Initialization.html#assigning_constant_properties_during_initialization)一节。
|
||||
|
||||
下面的例子定义了一个名为`FixedLengthRange`的结构体,它描述了一个在创建后无法修改值域宽度的区间:
|
||||
|
||||
```swift
|
||||
struct FixedLengthRange {
|
||||
var firstValue: Int
|
||||
let length: Int
|
||||
}
|
||||
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
|
||||
// 该区间表示整数0,1,2
|
||||
rangeOfThreeItems.firstValue = 6
|
||||
// 该区间现在表示整数6,7,8
|
||||
```
|
||||
|
||||
`FixedLengthRange`的实例包含一个名为`firstValue`的变量存储属性和一个名为`length`的常量存储属性。在上面的例子中,`length`在创建实例的时候被初始化,因为它是一个常量存储属性,所以之后无法修改它的值。
|
||||
|
||||
<a name="stored_properties_of_constant_structure_instances"></a>
|
||||
### 常量结构体的存储属性
|
||||
|
||||
如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使定义了变量存储属性:
|
||||
|
||||
```swift
|
||||
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
|
||||
// 该区间表示整数0,1,2,3
|
||||
rangeOfFourItems.firstValue = 6
|
||||
// 尽管 firstValue 是个变量属性,这里还是会报错
|
||||
```
|
||||
|
||||
因为`rangeOfFourItems`被声明成了常量(用`let`关键字),即使`firstValue`是一个变量属性,也无法再修改它了。
|
||||
|
||||
这种行为是由于结构体(struct)属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
|
||||
|
||||
属于*引用类型*的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。
|
||||
|
||||
<a name="lazy_stored_properties"></a>
|
||||
### 延迟存储属性
|
||||
|
||||
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用`lazy`来标示一个延迟存储属性。
|
||||
|
||||
> 注意:
|
||||
> 必须将延迟存储属性声明成变量(使用`var`关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
|
||||
|
||||
延迟属性很有用,当属性的值依赖于在实例的构造过程结束后才会知道具体值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时,可以只在需要的时候计算它。
|
||||
|
||||
下面的例子使用了延迟存储属性来避免复杂类中不必要的初始化。例子中定义了`DataImporter`和`DataManager`两个类,下面是部分代码:
|
||||
|
||||
```swift
|
||||
class DataImporter {
|
||||
/*
|
||||
DataImporter 是一个将外部文件中的数据导入的类。
|
||||
这个类的初始化会消耗不少时间。
|
||||
*/
|
||||
var fileName = "data.txt"
|
||||
// 这是提供数据导入功能
|
||||
}
|
||||
|
||||
class DataManager {
|
||||
lazy var importer = DataImporter()
|
||||
var data = [String]()
|
||||
// 这是提供数据管理功能
|
||||
}
|
||||
|
||||
let manager = DataManager()
|
||||
manager.data.append("Some data")
|
||||
manager.data.append("Some more data")
|
||||
// DataImporter 实例的 importer 属性还没有被创建
|
||||
```
|
||||
|
||||
`DataManager`类包含一个名为`data`的存储属性,初始值是一个空的字符串(`String`)数组。虽然没有写出全部代码,`DataManager`类的目的是管理和提供对这个字符串数组的访问。
|
||||
|
||||
`DataManager`的一个功能是从文件导入数据。该功能由`DataImporter`类提供,`DataImporter`完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。
|
||||
|
||||
`DataManager`也可能不从文件中导入数据就完成了管理数据的功能。所以当`DataManager`的实例被创建时,没必要创建一个`DataImporter`的实例,更明智的是当第一次用到`DataImporter`的时候才去创建它。
|
||||
|
||||
由于使用了`lazy`,`importer`属性只有在第一次被访问的时候才被创建。比如访问它的属性`fileName`时:
|
||||
|
||||
```swift
|
||||
print(manager.importer.fileName)
|
||||
// DataImporter 实例的 importer 属性现在被创建了
|
||||
// 输出 "data.txt”
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 如果一个被标记为`lazy`的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
|
||||
|
||||
<a name="stored_properties_and_instance_variables"></a>
|
||||
### 存储属性和实例变量
|
||||
|
||||
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。对于属性来说,也可以使用实例变量作为属性值的后端存储。
|
||||
|
||||
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。一个类型中属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方(类型定义中)定义。
|
||||
|
||||
<a name="computed_properties"></a>
|
||||
## 计算属性
|
||||
|
||||
除存储属性外,类、结构体和枚举可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set(newCenter) {
|
||||
origin.x = newCenter.x - (size.width / 2)
|
||||
origin.y = newCenter.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
var square = Rect(origin: Point(x: 0.0, y: 0.0),
|
||||
size: Size(width: 10.0, height: 10.0))
|
||||
let initialSquareCenter = square.center
|
||||
square.center = Point(x: 15.0, y: 15.0)
|
||||
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
|
||||
// 输出 "square.origin is now at (10.0, 10.0)”
|
||||
```
|
||||
|
||||
这个例子定义了 3 个结构体来描述几何形状:
|
||||
|
||||
- `Point`封装了一个`(x, y)`的坐标
|
||||
- `Size`封装了一个`width`和一个`height`
|
||||
- `Rect`表示一个有原点和尺寸的矩形
|
||||
|
||||
`Rect`也提供了一个名为`center`的计算属性。一个矩形的中心点可以从原点(`origin`)和尺寸(`size`)算出,所以不需要将它以显式声明的`Point`来保存。`Rect`的计算属性`center`提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
|
||||
|
||||
上述例子中创建了一个名为`square`的`Rect`实例,初始值原点是`(0, 0)`,宽度高度都是`10`。如下图中蓝色正方形所示。
|
||||
|
||||
`square`的`center`属性可以通过点运算符(`square.center`)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的`Point`来表示`square`的中心点。如代码所示,它正确返回了中心点`(5, 5)`。
|
||||
|
||||
`center`属性之后被设置了一个新的值`(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性`center`的值会调用它的 setter 来修改属性`origin`的`x`和`y`的值,从而实现移动正方形到新的位置。
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
|
||||
|
||||
<a name="shorthand_setter_declaration"></a>
|
||||
### 便捷 setter 声明
|
||||
|
||||
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称`newValue`。下面是使用了便捷 setter 声明的`Rect`结构体代码:
|
||||
|
||||
```swift
|
||||
struct AlternativeRect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set {
|
||||
origin.x = newValue.x - (size.width / 2)
|
||||
origin.y = newValue.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="readonly_computed_properties"></a>
|
||||
### 只读计算属性
|
||||
|
||||
只有 getter 没有 setter 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
|
||||
|
||||
> 注意:
|
||||
> 必须使用`var`关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let`关键字只用来声明常量属性,表示初始化后再也无法修改的值。
|
||||
|
||||
只读计算属性的声明可以去掉`get`关键字和花括号:
|
||||
|
||||
```swift
|
||||
struct Cuboid {
|
||||
var width = 0.0, height = 0.0, depth = 0.0
|
||||
var volume: Double {
|
||||
return width * height * depth
|
||||
}
|
||||
}
|
||||
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
|
||||
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
// 输出 "the volume of fourByFiveByTwo is 40.0"
|
||||
```
|
||||
|
||||
这个例子定义了一个名为`Cuboid`的结构体,表示三维空间的立方体,包含`width`、`height`和`depth`属性。结构体还有一个名为`volume`的只读计算属性用来返回立方体的体积。设置`volume`的值毫无意义,因为无法确定修改`width`、`height`和`depth`三者中的哪些值来匹配新的`volume`,从而造成歧义。然而,`Cuboid`提供一个只读计算属性来让外部用户直接获取体积是很有用的。
|
||||
|
||||
<a name="property_observers"></a>
|
||||
## 属性观察器
|
||||
|
||||
*属性观察器*监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。
|
||||
|
||||
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。属性重载请参考[重载](./13_Inheritance.html#overriding)。
|
||||
|
||||
> 注意:
|
||||
> 不需要为非重载的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。
|
||||
|
||||
可以为属性添加如下的一个或全部观察器:
|
||||
|
||||
- `willSet`在新的值被设置之前调用
|
||||
- `didSet`在新的值被设置之后立即调用
|
||||
|
||||
`willSet`观察器会将新的属性值作为常量参数传入,在`willSet`的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称`newValue`表示。
|
||||
|
||||
类似地,`didSet`观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名`oldValue`。
|
||||
|
||||
> 注意:
|
||||
> 父类的属性在子类的构造器中被赋值时,它在父类中的`willSet`和`didSet`观察器会被调用。
|
||||
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)。
|
||||
|
||||
这里是一个`willSet`和`didSet`的实际例子,其中定义了一个名为`StepCounter`的类,用来统计当人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
|
||||
|
||||
```swift
|
||||
class StepCounter {
|
||||
var totalSteps: Int = 0 {
|
||||
willSet(newTotalSteps) {
|
||||
print("About to set totalSteps to \(newTotalSteps)")
|
||||
}
|
||||
didSet {
|
||||
if totalSteps > oldValue {
|
||||
print("Added \(totalSteps - oldValue) steps")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let stepCounter = StepCounter()
|
||||
stepCounter.totalSteps = 200
|
||||
// About to set totalSteps to 200
|
||||
// Added 200 steps
|
||||
stepCounter.totalSteps = 360
|
||||
// About to set totalSteps to 360
|
||||
// Added 160 steps
|
||||
stepCounter.totalSteps = 896
|
||||
// About to set totalSteps to 896
|
||||
// Added 536 steps
|
||||
```
|
||||
|
||||
`StepCounter`类定义了一个`Int`类型的属性`totalSteps`,它是一个存储属性,包含`willSet`和`didSet`观察器。
|
||||
|
||||
当`totalSteps`设置新值的时候,它的`willSet`和`didSet`观察器都会被调用,甚至当新的值和现在的值完全相同也会调用。
|
||||
|
||||
例子中的`willSet`观察器将表示新值的参数自定义为`newTotalSteps`,这个观察器只是简单的将新的值输出。
|
||||
|
||||
`didSet`观察器在`totalSteps`的值改变后被调用,它把新的值和旧的值进行对比,如果总的步数增加了,就输出一个消息表示增加了多少步。`didSet`没有为旧的值提供自定义名称,所以默认值`oldValue`表示旧值的参数名。
|
||||
|
||||
> 注意:
|
||||
> 如果在一个属性的`didSet`观察器里为它赋值,这个值会替换该观察器之前设置的值。
|
||||
|
||||
<a name="global_and_local_variables"></a>
|
||||
##全局变量和局部变量
|
||||
|
||||
计算属性和属性观察器所描述的模式也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
|
||||
|
||||
前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它提供特定类型的存储空间,并允许读取和写入。
|
||||
|
||||
另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算的值而不是存储值,声明格式也完全一样。
|
||||
|
||||
> 注意:
|
||||
> 全局的常量或变量都是延迟计算的,跟[延迟存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记`lazy`特性。
|
||||
> 局部范围的常量或变量不会延迟计算。
|
||||
|
||||
<a name="type_properties"></a>
|
||||
##类型属性
|
||||
|
||||
实例的属性属于一个特定类型实例,每次类型实例化后都拥有自己的一套属性值,实例之间的属性相互独立。
|
||||
|
||||
也可以为类型本身定义属性,不管类型有多少个实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
|
||||
|
||||
类型属性用于定义特定类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
|
||||
|
||||
值类型的存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算属性一样只能定义成变量属性。
|
||||
|
||||
> 注意:
|
||||
> 跟实例的存储属性不同,必须给存储型类型属性指定默认值,因为类型本身无法在初始化过程中使用构造器给类型属性赋值。
|
||||
> 存储型类型属性是延迟初始化的(lazily initialized),它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行初始化一次,并且不需要对其使用 `lazy` 修饰符。
|
||||
|
||||
<a name="type_property_syntax"></a>
|
||||
###类型属性语法
|
||||
|
||||
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(*global*)静态变量定义的。但是在 Swift 编程语言中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
|
||||
|
||||
使用关键字`static`来定义类型属性。在为类(class)定义计算型类型属性时,可以使用关键字`class`来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法:
|
||||
|
||||
```swift
|
||||
struct SomeStructure {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
enum SomeEnumeration {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 6
|
||||
}
|
||||
}
|
||||
class SomeClass {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 27
|
||||
}
|
||||
class var overrideableComputedTypeProperty: Int {
|
||||
return 107
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟实例计算属性的语法类似。
|
||||
|
||||
<a name="querying_and_setting_type_properties"></a>
|
||||
###获取和设置类型属性的值
|
||||
|
||||
跟实例的属性一样,类型属性的访问也是通过点运算符来进行。但是,类型属性是通过类型本身来获取和设置,而不是通过实例。比如:
|
||||
|
||||
```swift
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 输出 "Some value."
|
||||
SomeStructure.storedTypeProperty = "Another value."
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 输出 "Another value.”
|
||||
print(SomeEnumeration.computedTypeProperty)
|
||||
// 输出 "6"
|
||||
print(SomeClass.computedTypeProperty)
|
||||
// 输出 "27"
|
||||
```
|
||||
|
||||
下面的例子定义了一个结构体,使用两个存储型类型属性来表示多个声道的声音电平值,每个声道有一个 0 到 10 之间的整数表示声音电平值。
|
||||
|
||||
后面的图表展示了如何联合使用两个声道来表示一个立体声的声音电平值。当声道的电平值是 0,没有一个灯会亮;当声道的电平值是 10,所有灯点亮。本图中,左声道的电平是 9,右声道的电平是 7。
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
|
||||
|
||||
上面所描述的声道模型使用`AudioChannel`结构体的实例来表示:
|
||||
|
||||
```swift
|
||||
struct AudioChannel {
|
||||
static let thresholdLevel = 10
|
||||
static var maxInputLevelForAllChannels = 0
|
||||
var currentLevel: Int = 0 {
|
||||
didSet {
|
||||
if currentLevel > AudioChannel.thresholdLevel {
|
||||
// 将新电平值设置为阀值
|
||||
currentLevel = AudioChannel.thresholdLevel
|
||||
}
|
||||
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
|
||||
// 存储当前电平值作为新的最大输入电平
|
||||
AudioChannel.maxInputLevelForAllChannels = currentLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
结构`AudioChannel`定义了 2 个存储型类型属性来实现上述功能。第一个是`thresholdLevel`,表示声音电平的最大上限阈值,它是一个取值为 10 的常量,对所有实例都可见,如果声音电平高于 10,则取最大上限值 10(见后面描述)。
|
||||
|
||||
第二个类型属性是变量存储型属性`maxInputLevelForAllChannels`,它用来表示所有`AudioChannel`实例的电平值的最大值,初始值是 0。
|
||||
|
||||
`AudioChannel`也定义了一个名为`currentLevel`的实例存储属性,表示当前声道现在的电平值,取值为 0 到 10。
|
||||
|
||||
属性`currentLevel`包含`didSet`属性观察器来检查每次新设置后的属性值,它有如下两个检查:
|
||||
|
||||
- 如果`currentLevel`的新值大于允许的阈值`thresholdLevel`,属性观察器将`currentLevel`的值限定为阈值`thresholdLevel`。
|
||||
- 如果前一个修正后的`currentLevel`值大于任何之前任意`AudioChannel`实例中的值,属性观察器将新值保存在静态类型属性`maxInputLevelForAllChannels`中。
|
||||
|
||||
> 注意:
|
||||
> 在第一个检查过程中,`didSet`属性观察器将`currentLevel`设置成了不同的值,但这时不会再次调用属性观察器。
|
||||
|
||||
可以使用结构体`AudioChannel`来创建表示立体声系统的两个声道`leftChannel`和`rightChannel`:
|
||||
|
||||
```swift
|
||||
var leftChannel = AudioChannel()
|
||||
var rightChannel = AudioChannel()
|
||||
```
|
||||
|
||||
如果将左声道的电平设置成 7,类型属性`maxInputLevelForAllChannels`也会更新成 7:
|
||||
|
||||
```swift
|
||||
leftChannel.currentLevel = 7
|
||||
print(leftChannel.currentLevel)
|
||||
// 输出 "7"
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出 "7"
|
||||
```
|
||||
|
||||
如果试图将右声道的电平设置成 11,则会将右声道的`currentLevel`修正到最大值 10,同时`maxInputLevelForAllChannels`的值也会更新到 10:
|
||||
|
||||
```swift
|
||||
rightChannel.currentLevel = 11
|
||||
print(rightChannel.currentLevel)
|
||||
// 输出 "10"
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出 "10"
|
||||
```
|
||||
> 1.0
|
||||
> 翻译:[shinyzhu](https://github.com/shinyzhu)
|
||||
> 校对:[pp-prog](https://github.com/pp-prog) [yangsiy](https://github.com/yangsiy)
|
||||
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
|
||||
|
||||
|
||||
> 2.1
|
||||
> 翻译:[buginux](https://github.com/buginux)
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-29
|
||||
|
||||
|
||||
> 2.2
|
||||
> 翻译:[saitjr](https://github.com/saitjr),2016-04-11,[SketchK](https://github.com/SketchK) 2016-05-13
|
||||
|
||||
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [存储属性(Stored Properties)](#stored_properties)
|
||||
- [计算属性(Computed Properties)](#computed_properties)
|
||||
- [属性观察器(Property Observers)](#property_observers)
|
||||
- [全局变量和局部变量(Global and Local Variables)](#global_and_local_variables)
|
||||
- [类型属性(Type Properties)](#type_properties)
|
||||
|
||||
*属性*将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。
|
||||
|
||||
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。
|
||||
|
||||
另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。
|
||||
|
||||
<a name="stored_properties"></a>
|
||||
## 存储属性
|
||||
|
||||
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字 `var` 定义),也可以是*常量存储属性*(用关键字 `let` 定义)。
|
||||
|
||||
可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.html#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.html#assigning_constant_properties_during_initialization)一节。
|
||||
|
||||
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的范围,且这个范围值在被创建后不能被修改.
|
||||
|
||||
```swift
|
||||
struct FixedLengthRange {
|
||||
var firstValue: Int
|
||||
let length: Int
|
||||
}
|
||||
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
|
||||
// 该区间表示整数0,1,2
|
||||
rangeOfThreeItems.firstValue = 6
|
||||
// 该区间现在表示整数6,7,8
|
||||
```
|
||||
|
||||
`FixedLengthRange` 的实例包含一个名为 `firstValue` 的变量存储属性和一个名为 `length` 的常量存储属性。在上面的例子中,`length` 在创建实例的时候被初始化,因为它是一个常量存储属性,所以之后无法修改它的值。
|
||||
|
||||
<a name="stored_properties_of_constant_structure_instances"></a>
|
||||
### 常量结构体的存储属性
|
||||
|
||||
如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行:
|
||||
|
||||
```swift
|
||||
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
|
||||
// 该区间表示整数0,1,2,3
|
||||
rangeOfFourItems.firstValue = 6
|
||||
// 尽管 firstValue 是个变量属性,这里还是会报错
|
||||
```
|
||||
|
||||
因为 `rangeOfFourItems` 被声明成了常量(用 `let` 关键字),即使 `firstValue` 是一个变量属性,也无法再修改它了。
|
||||
|
||||
这种行为是由于结构体(struct)属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
|
||||
|
||||
属于*引用类型*的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。
|
||||
|
||||
<a name="lazy_stored_properties"></a>
|
||||
### 延迟存储属性
|
||||
|
||||
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 `lazy` 来标示一个延迟存储属性。
|
||||
|
||||
> 注意
|
||||
> 必须将延迟存储属性声明成变量(使用 `var` 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
|
||||
|
||||
延迟属性很有用,当属性的值依赖于在实例的构造过程结束后才会知道影响值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时,可以只在需要的时候计算它。
|
||||
|
||||
下面的例子使用了延迟存储属性来避免复杂类中不必要的初始化。例子中定义了 `DataImporter` 和 `DataManager` 两个类,下面是部分代码:
|
||||
|
||||
```swift
|
||||
class DataImporter {
|
||||
/*
|
||||
DataImporter 是一个负责将外部文件中的数据导入的类。
|
||||
这个类的初始化会消耗不少时间。
|
||||
*/
|
||||
var fileName = "data.txt"
|
||||
// 这里会提供数据导入功能
|
||||
}
|
||||
|
||||
class DataManager {
|
||||
lazy var importer = DataImporter()
|
||||
var data = [String]()
|
||||
// 这里会提供数据管理功能
|
||||
}
|
||||
|
||||
let manager = DataManager()
|
||||
manager.data.append("Some data")
|
||||
manager.data.append("Some more data")
|
||||
// DataImporter 实例的 importer 属性还没有被创建
|
||||
```
|
||||
|
||||
`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串(`String`)数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。
|
||||
|
||||
`DataManager` 的一个功能是从文件导入数据。该功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。
|
||||
|
||||
`DataManager` 管理数据时也可能不从文件中导入数据。所以当 `DataManager` 的实例被创建时,没必要创建一个 `DataImporter` 的实例,更明智的做法是第一次用到 `DataImporter` 的时候才去创建它。
|
||||
|
||||
由于使用了 `lazy` ,`importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时:
|
||||
|
||||
```swift
|
||||
print(manager.importer.fileName)
|
||||
// DataImporter 实例的 importer 属性现在被创建了
|
||||
// 输出 "data.txt”
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 如果一个被标记为 `lazy` 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
|
||||
|
||||
<a name="stored_properties_and_instance_variables"></a>
|
||||
### 存储属性和实例变量
|
||||
|
||||
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为属性值的后端存储。
|
||||
|
||||
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方(类型定义中)定义。
|
||||
|
||||
<a name="computed_properties"></a>
|
||||
## 计算属性
|
||||
|
||||
除存储属性外,类、结构体和枚举可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set(newCenter) {
|
||||
origin.x = newCenter.x - (size.width / 2)
|
||||
origin.y = newCenter.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
var square = Rect(origin: Point(x: 0.0, y: 0.0),
|
||||
size: Size(width: 10.0, height: 10.0))
|
||||
let initialSquareCenter = square.center
|
||||
square.center = Point(x: 15.0, y: 15.0)
|
||||
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
|
||||
// 输出 "square.origin is now at (10.0, 10.0)”
|
||||
```
|
||||
|
||||
这个例子定义了 3 个结构体来描述几何形状:
|
||||
|
||||
- `Point` 封装了一个 `(x, y)` 的坐标
|
||||
- `Size` 封装了一个 `width` 和一个 `height`
|
||||
- `Rect` 表示一个有原点和尺寸的矩形
|
||||
|
||||
`Rect`也提供了一个名为`center` 的计算属性。一个矩形的中心点可以从原点(`origin`)和大小(`size`)算出,所以不需要将它以显式声明的 `Point` 来保存。`Rect` 的计算属性 `center` 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
|
||||
|
||||
上述例子中创建了一个名为 `square` 的 `Rect` 实例,初始值原点是 `(0, 0)`,宽度高度都是 `10`。如下图中蓝色正方形所示。
|
||||
|
||||
`square` 的 `center` 属性可以通过点运算符(`square.center`)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的 `Point` 来表示 `square` 的中心点。如代码所示,它正确返回了中心点 `(5, 5)`。
|
||||
|
||||
`center` 属性之后被设置了一个新的值 `(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性`center`的值会调用它的 setter 来修改属性 `origin` 的 `x` 和 `y` 的值,从而实现移动正方形到新的位置。
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
|
||||
|
||||
<a name="shorthand_setter_declaration"></a>
|
||||
### 便捷 setter 声明
|
||||
|
||||
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 `newValue`。下面是使用了便捷 setter 声明的 `Rect` 结构体代码:
|
||||
|
||||
```swift
|
||||
struct AlternativeRect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set {
|
||||
origin.x = newValue.x - (size.width / 2)
|
||||
origin.y = newValue.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="readonly_computed_properties"></a>
|
||||
### 只读计算属性
|
||||
|
||||
只有 getter 没有 setter 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
|
||||
|
||||
> 注意
|
||||
> 必须使用 `var` 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let` 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
|
||||
|
||||
只读计算属性的声明可以去掉 `get` 关键字和花括号:
|
||||
|
||||
```swift
|
||||
struct Cuboid {
|
||||
var width = 0.0, height = 0.0, depth = 0.0
|
||||
var volume: Double {
|
||||
return width * height * depth
|
||||
}
|
||||
}
|
||||
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
|
||||
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
// 输出 "the volume of fourByFiveByTwo is 40.0"
|
||||
```
|
||||
|
||||
这个例子定义了一个名为 `Cuboid` 的结构体,表示三维空间的立方体,包含 `width`、`height` 和 `depth` 属性。结构体还有一个名为 `volume` 的只读计算属性用来返回立方体的体积。为 `volume` 提供 setter 毫无意义,因为无法确定如何修改 `width`、`height` 和 `depth` 三者的值来匹配新的 `volume`。然而,`Cuboid` 提供一个只读计算属性来让外部用户直接获取体积是很有用的。
|
||||
|
||||
<a name="property_observers"></a>
|
||||
## 属性观察器
|
||||
|
||||
*属性观察器*监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
|
||||
|
||||
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 属性重写请参考[重写](./13_Inheritance.html#overriding)。
|
||||
|
||||
|
||||
可以为属性添加如下的一个或全部观察器:
|
||||
|
||||
- `willSet` 在新的值被设置之前调用
|
||||
- `didSet` 在新的值被设置之后立即调用
|
||||
|
||||
`willSet` 观察器会将新的属性值作为常量参数传入,在 `willSet` 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 `newValue` 表示。
|
||||
|
||||
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
|
||||
|
||||
> 注意
|
||||
> 父类的属性在子类的构造器中被赋值时,它在父类中的 `willSet` 和 `didSet` 观察器会被调用,随后才会调用子类的观察器。在父类初始化方法调用之前,子类给属性赋值时,观察器不会被调用。
|
||||
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)。
|
||||
|
||||
下面是一个 `willSet` 和 `didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
|
||||
|
||||
```swift
|
||||
class StepCounter {
|
||||
var totalSteps: Int = 0 {
|
||||
willSet(newTotalSteps) {
|
||||
print("About to set totalSteps to \(newTotalSteps)")
|
||||
}
|
||||
didSet {
|
||||
if totalSteps > oldValue {
|
||||
print("Added \(totalSteps - oldValue) steps")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let stepCounter = StepCounter()
|
||||
stepCounter.totalSteps = 200
|
||||
// About to set totalSteps to 200
|
||||
// Added 200 steps
|
||||
stepCounter.totalSteps = 360
|
||||
// About to set totalSteps to 360
|
||||
// Added 160 steps
|
||||
stepCounter.totalSteps = 896
|
||||
// About to set totalSteps to 896
|
||||
// Added 536 steps
|
||||
```
|
||||
|
||||
`StepCounter` 类定义了一个 `Int` 类型的属性 `totalSteps`,它是一个存储属性,包含 `willSet` 和 `didSet` 观察器。
|
||||
|
||||
当 `totalSteps` 被设置新值的时候,它的 `willSet` 和 `didSet` 观察器都会被调用,即使新值和当前值完全相同时也会被调用。
|
||||
|
||||
例子中的 `willSet` 观察器将表示新值的参数自定义为 `newTotalSteps`,这个观察器只是简单的将新的值输出。
|
||||
|
||||
`didSet` 观察器在 `totalSteps` 的值改变后被调用,它把新值和旧值进行对比,如果总步数增加了,就输出一个消息表示增加了多少步。`didSet` 没有为旧值提供自定义名称,所以默认值 `oldValue` 表示旧值的参数名。
|
||||
|
||||
>注意
|
||||
>
|
||||
>如果将属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。关于 in-out 参数详细的介绍,请参考[输入输出参数](../chapter3/05_Declarations.html#in-out_parameters)
|
||||
|
||||
<a name="global_and_local_variables"></a>
|
||||
##全局变量和局部变量
|
||||
|
||||
计算属性和属性观察器所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
|
||||
|
||||
前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
|
||||
|
||||
另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
|
||||
|
||||
> 注意
|
||||
> 全局的常量或变量都是延迟计算的,跟[延迟存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记`lazy`修饰符。
|
||||
> 局部范围的常量或变量从不延迟计算。
|
||||
|
||||
<a name="type_properties"></a>
|
||||
##类型属性
|
||||
|
||||
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
|
||||
|
||||
也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
|
||||
|
||||
类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
|
||||
|
||||
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
|
||||
|
||||
> 注意
|
||||
> 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
|
||||
> 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 `lazy` 修饰符。
|
||||
|
||||
<a name="type_property_syntax"></a>
|
||||
###类型属性语法
|
||||
|
||||
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(*global*)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
|
||||
|
||||
使用关键字 `static` 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 `class` 来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法:
|
||||
|
||||
```swift
|
||||
struct SomeStructure {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
enum SomeEnumeration {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 6
|
||||
}
|
||||
}
|
||||
class SomeClass {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 27
|
||||
}
|
||||
class var overrideableComputedTypeProperty: Int {
|
||||
return 107
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
|
||||
|
||||
<a name="querying_and_setting_type_properties"></a>
|
||||
###获取和设置类型属性的值
|
||||
|
||||
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实例。比如:
|
||||
|
||||
```swift
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 输出 "Some value."
|
||||
SomeStructure.storedTypeProperty = "Another value."
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 输出 "Another value.”
|
||||
print(SomeEnumeration.computedTypeProperty)
|
||||
// 输出 "6"
|
||||
print(SomeClass.computedTypeProperty)
|
||||
// 输出 "27"
|
||||
```
|
||||
|
||||
下面的例子定义了一个结构体,使用两个存储型类型属性来表示两个声道的音量,每个声道具有 `0` 到 `10` 之间的整数音量。
|
||||
|
||||
下图展示了如何把两个声道结合来模拟立体声的音量。当声道的音量是 `0`,没有一个灯会亮;当声道的音量是 `10`,所有灯点亮。本图中,左声道的音量是 `9`,右声道的音量是 `7`:
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
|
||||
|
||||
上面所描述的声道模型使用 `AudioChannel` 结构体的实例来表示:
|
||||
|
||||
```swift
|
||||
struct AudioChannel {
|
||||
static let thresholdLevel = 10
|
||||
static var maxInputLevelForAllChannels = 0
|
||||
var currentLevel: Int = 0 {
|
||||
didSet {
|
||||
if currentLevel > AudioChannel.thresholdLevel {
|
||||
// 将当前音量限制在阀值之内
|
||||
currentLevel = AudioChannel.thresholdLevel
|
||||
}
|
||||
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
|
||||
// 存储当前音量作为新的最大输入音量
|
||||
AudioChannel.maxInputLevelForAllChannels = currentLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
结构 `AudioChannel` 定义了 2 个存储型类型属性来实现上述功能。第一个是 `thresholdLevel`,表示音量的最大上限阈值,它是一个值为 `10` 的常量,对所有实例都可见,如果音量高于 `10`,则取最大上限值 `10`(见后面描述)。
|
||||
|
||||
第二个类型属性是变量存储型属性 `maxInputLevelForAllChannels`,它用来表示所有 `AudioChannel` 实例的最大音量,初始值是`0`。
|
||||
|
||||
`AudioChannel` 也定义了一个名为 `currentLevel` 的存储型实例属性,表示当前声道现在的音量,取值为 `0` 到 `10`。
|
||||
|
||||
属性 `currentLevel` 包含 `didSet` 属性观察器来检查每次设置后的属性值,它做如下两个检查:
|
||||
|
||||
- 如果 `currentLevel` 的新值大于允许的阈值 `thresholdLevel`,属性观察器将 `currentLevel` 的值限定为阈值 `thresholdLevel`。
|
||||
- 如果修正后的 `currentLevel` 值大于静态类型属性 `maxInputLevelForAllChannels` 的值,属性观察器就将新值保存在 `maxInputLevelForAllChannels` 中。
|
||||
|
||||
> 注意
|
||||
> 在第一个检查过程中,`didSet` 属性观察器将 `currentLevel` 设置成了不同的值,但这不会造成属性观察器被再次调用。
|
||||
|
||||
可以使用结构体 `AudioChannel` 创建两个声道 `leftChannel` 和 `rightChannel`,用以表示立体声系统的音量:
|
||||
|
||||
```swift
|
||||
var leftChannel = AudioChannel()
|
||||
var rightChannel = AudioChannel()
|
||||
```
|
||||
|
||||
如果将左声道的 `currentLevel` 设置成 `7`,类型属性 `maxInputLevelForAllChannels` 也会更新成 `7`:
|
||||
|
||||
```swift
|
||||
leftChannel.currentLevel = 7
|
||||
print(leftChannel.currentLevel)
|
||||
// 输出 "7"
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出 "7"
|
||||
```
|
||||
|
||||
如果试图将右声道的 `currentLevel` 设置成 `11`,它会被修正到最大值 `10`,同时 `maxInputLevelForAllChannels` 的值也会更新到 `10`:
|
||||
|
||||
```swift
|
||||
rightChannel.currentLevel = 11
|
||||
print(rightChannel.currentLevel)
|
||||
// 输出 "10"
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出 "10"
|
||||
```
|
||||
|
||||
@ -8,6 +8,12 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[DianQK](https://github.com/DianQK)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[DianQK](https://github.com/DianQK),[Realank](https://github.com/Realank) 校对:[shanks](http://codebuild.me),2016-01-18
|
||||
>
|
||||
> 2.2
|
||||
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [实例方法(Instance Methods)](#instance_methods)
|
||||
@ -15,7 +21,7 @@
|
||||
|
||||
**方法**是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。
|
||||
|
||||
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。
|
||||
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。
|
||||
|
||||
<a name="instance_methods"></a>
|
||||
## 实例方法 (Instance Methods)
|
||||
@ -28,16 +34,16 @@
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count = 0
|
||||
func increment() {
|
||||
++count
|
||||
}
|
||||
func incrementBy(amount: Int) {
|
||||
count += amount
|
||||
}
|
||||
func reset() {
|
||||
count = 0
|
||||
}
|
||||
var count = 0
|
||||
func increment() {
|
||||
count += 1
|
||||
}
|
||||
func incrementBy(amount: Int) {
|
||||
count += amount
|
||||
}
|
||||
func reset() {
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -51,37 +57,37 @@ class Counter {
|
||||
和调用属性一样,用点语法(dot syntax)调用实例方法:
|
||||
|
||||
```swift
|
||||
let counter = Counter()
|
||||
// 初始计数值是0
|
||||
counter.increment()
|
||||
// 计数值现在是1
|
||||
counter.incrementBy(5)
|
||||
// 计数值现在是6
|
||||
counter.reset()
|
||||
// 计数值现在是0
|
||||
let counter = Counter()
|
||||
// 初始计数值是0
|
||||
counter.increment()
|
||||
// 计数值现在是1
|
||||
counter.incrementBy(5)
|
||||
// 计数值现在是6
|
||||
counter.reset()
|
||||
// 计数值现在是0
|
||||
```
|
||||
|
||||
<a name="local_and_external_parameter"></a>
|
||||
### 方法的局部参数名称和外部参数名称(Local and External Parameter Names for Methods)
|
||||
### 方法的局部参数名称和外部参数名称 (Local and External Parameter Names for Methods)
|
||||
|
||||
函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.html#specifying_external_parameter_names)。方法参数也一样(因为方法就是函数,只是这个函数与某个类型相关联了)。
|
||||
|
||||
Swift 中的方法和 Objective-C 中的方法极其相似。像在 Objective-C 中一样,Swift 中方法的名称通常用一个介词指向方法的第一个参数,比如:`with`,`for`,`by`等等。前面的`Counter`类的例子中`incrementBy(_:)`方法就是这样的。介词的使用让方法在被调用时能像一个句子一样被解读。
|
||||
|
||||
具体来说,Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很相似。这个约定还让表达式方法在调用时不需要再限定参数名称。
|
||||
具体来说,Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很相似。这个约定还让富于表达性的方法在调用时不需要再限定参数名称。
|
||||
|
||||
看看下面这个`Counter`的另一个版本(它定义了一个更复杂的`incrementBy(_:)`方法):
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count: Int = 0
|
||||
func incrementBy(amount: Int, numberOfTimes: Int) {
|
||||
count += amount * numberOfTimes
|
||||
}
|
||||
var count: Int = 0
|
||||
func incrementBy(amount: Int, numberOfTimes: Int) {
|
||||
count += amount * numberOfTimes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`incrementBy(_:numverOfTimes:)`方法有两个参数: `amount`和`numberOfTimes`。默认情况下,Swift 只把`amount`当作一个局部名称,但是把`numberOfTimes`即看作局部名称又看作外部名称。下面调用这个方法:
|
||||
`incrementBy(_:numberOfTimes:)`方法有两个参数: `amount`和`numberOfTimes`。默认情况下,Swift 只把`amount`当作一个局部名称,但是把`numberOfTimes`即看作局部名称又看作外部名称。下面调用这个方法:
|
||||
|
||||
```swift
|
||||
let counter = Counter()
|
||||
@ -90,17 +96,16 @@ counter.incrementBy(5, numberOfTimes: 3)
|
||||
```
|
||||
|
||||
你不必为第一个参数值再定义一个外部变量名:因为从函数名`incrementBy(_numberOfTimes:)`已经能很清楚地看出它的作用。但是第二个参数,就要被一个外部参数名称所限定,以便在方法被调用时明确它的作用。
|
||||
这种默认行为使上面代码意味着:在 Swift 中定义方法使用了与 Objective-C 同样的语法风格,并且方法将以自然表达式的方式被调用。
|
||||
|
||||
上面描述的这种默认行为意味着在 Swift 中,定义方法使用了与 Objective-C 同样的语法风格,并且方法将以自然且富于表达性的方式被调用。
|
||||
|
||||
<a name="modifying_external_parameter_name_behavior_for_methods"></a>
|
||||
### 修改方法的外部参数名称(Modifying External Parameter Name Behavior for Methods)
|
||||
|
||||
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你可以自己添加一个显式的外部名称作为第一个参数的前缀来把这个局部名称当作外部名称使用。
|
||||
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你自己可以为第一个参数添加一个显式的外部名称。
|
||||
|
||||
相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(`_`)作为该参数的显式外部名称,这样做将覆盖默认行为。
|
||||
|
||||
|
||||
<a name="the_self_property"></a>
|
||||
### self 属性(The self Property)
|
||||
|
||||
@ -110,11 +115,11 @@ counter.incrementBy(5, numberOfTimes: 3)
|
||||
|
||||
```swift
|
||||
func increment() {
|
||||
self.count++
|
||||
self.count += 1
|
||||
}
|
||||
```
|
||||
|
||||
实际上,你不必在你的代码里面经常写`self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确的写`self`,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的`Counter`中已经示范了:`Counter`中的三个实例方法中都使用的是`count`(而不是`self.count`)。
|
||||
实际上,你不必在你的代码里面经常写`self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写`self`,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的`Counter`中已经示范了:`Counter`中的三个实例方法中都使用的是`count`(而不是`self.count`)。
|
||||
|
||||
使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用`self`属性来区分参数名称和属性名称。
|
||||
|
||||
@ -122,14 +127,14 @@ func increment() {
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
func isToTheRightOfX(x: Double) -> Bool {
|
||||
return self.x > x
|
||||
}
|
||||
var x = 0.0, y = 0.0
|
||||
func isToTheRightOfX(x: Double) -> Bool {
|
||||
return self.x > x
|
||||
}
|
||||
}
|
||||
let somePoint = Point(x: 4.0, y: 5.0)
|
||||
if somePoint.isToTheRightOfX(1.0) {
|
||||
print("This point is to the right of the line where x == 1.0")
|
||||
print("This point is to the right of the line where x == 1.0")
|
||||
}
|
||||
// 打印输出: This point is to the right of the line where x == 1.0
|
||||
```
|
||||
@ -139,19 +144,19 @@ if somePoint.isToTheRightOfX(1.0) {
|
||||
<a name="modifying_value_types_from_within_instance_methods"></a>
|
||||
### 在实例方法中修改值类型(Modifying Value Types from Within Instance Methods)
|
||||
|
||||
结构体和枚举是**值类型**。一般情况下,值类型的属性不能在它的实例方法中被修改。
|
||||
结构体和枚举是**值类型**。默认情况下,值类型的属性不能在它的实例方法中被修改。
|
||||
|
||||
但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择`变异(mutating)`这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。方法还可以给它隐含的`self`属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。
|
||||
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择`可变(mutating)`行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的`self`属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。
|
||||
|
||||
要使用`变异`方法, 将关键字`mutating` 放到方法的`func`关键字之前就可以了:
|
||||
要使用`可变`方法,将关键字`mutating` 放到方法的`func`关键字之前就可以了:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
}
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
}
|
||||
}
|
||||
var somePoint = Point(x: 1.0, y: 1.0)
|
||||
somePoint.moveByX(2.0, y: 3.0)
|
||||
@ -159,47 +164,47 @@ print("The point is now at (\(somePoint.x), \(somePoint.y))")
|
||||
// 打印输出: "The point is now at (3.0, 4.0)"
|
||||
```
|
||||
|
||||
上面的`Point`结构体定义了一个变异方法(mutating method)`moveByX(_:y:)`用来移动点。`moveByX`方法在被调用时修改了这个点,而不是返回一个新的点。方法定义时加上`mutating`关键字,这才让方法可以修改值类型的属性。
|
||||
上面的`Point`结构体定义了一个可变方法 `moveByX(_:y:)` 来移动`Point`实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了`mutating`关键字,从而允许修改属性。
|
||||
|
||||
注意:不能在结构体类型常量上调用变异方法,因为常量的属性不能被改变,即使想改变的是常量的变量属性也不行,详情参见[存储属性和实例变量](./10_Properties.html#global_and_local_variables):
|
||||
注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见[常量结构体的存储属性](./10_Properties.html#stored_properties_of_constant_structure_instances):
|
||||
|
||||
```swift
|
||||
let fixedPoint = Point(x: 3.0, y: 3.0)
|
||||
fixedPoint.moveByX(2.0, y: 3.0)
|
||||
// 这里将会抛出一个错误
|
||||
// 这里将会报告一个错误
|
||||
```
|
||||
|
||||
<a name="assigning_to_self_within_a_mutating_method"></a>
|
||||
### 在变异方法中给self赋值(Assigning to self Within a Mutating Method)
|
||||
### 在可变方法中给 self 赋值(Assigning to self Within a Mutating Method)
|
||||
|
||||
变异方法能够赋给隐含属性`self`一个全新的实例。上面`Point`的例子可以用下面的方式改写:
|
||||
可变方法能够赋给隐含属性`self`一个全新的实例。上面`Point`的例子可以用下面的方式改写:
|
||||
|
||||
```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)
|
||||
}
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
self = Point(x: x + deltaX, y: y + deltaY)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
新版的变异方法`moveByX(_:y:)`创建了一个新的结构(它的 x 和 y 的值都被设定为目标值)。调用这个版本的方法和调用上个版本的最终结果是一样的。
|
||||
新版的可变方法`moveByX(_:y:)`创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。
|
||||
|
||||
枚举的变异方法可以把`self`设置为相同的枚举类型中不同的成员:
|
||||
枚举的可变方法可以把`self`设置为同一枚举类型中不同的成员:
|
||||
|
||||
```swift
|
||||
enum TriStateSwitch {
|
||||
case Off, Low, High
|
||||
mutating func next() {
|
||||
switch self {
|
||||
case Off:
|
||||
self = Low
|
||||
case Low:
|
||||
self = High
|
||||
case High:
|
||||
self = Off
|
||||
case Off, Low, High
|
||||
mutating func next() {
|
||||
switch self {
|
||||
case Off:
|
||||
self = Low
|
||||
case Low:
|
||||
self = High
|
||||
case High:
|
||||
self = Off
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var ovenLight = TriStateSwitch.Low
|
||||
ovenLight.next()
|
||||
@ -208,34 +213,34 @@ ovenLight.next()
|
||||
// ovenLight 现在等于 .Off
|
||||
```
|
||||
|
||||
上面的例子中定义了一个三态开关的枚举。每次调用`next`方法时,开关在不同的电源状态(`Off`,`Low`,`High`)之前循环切换。
|
||||
上面的例子中定义了一个三态开关的枚举。每次调用`next()`方法时,开关在不同的电源状态(`Off`,`Low`,`High`)之间循环切换。
|
||||
|
||||
<a name="type_methods"></a>
|
||||
## 类型方法 (Type Methods)
|
||||
|
||||
实例方法是被类型的某个实例调用的方法。你也可以定义类型本身调用的方法,这种方法就叫做**类型方法**。声明结构体和枚举的类型方法,在方法的`func`关键字之前加上关键字`static`。类可能会用关键字`class`来允许子类重写父类的实现方法。
|
||||
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做**类型方法**(Type Methods)。在方法的`func`关键字之前加上关键字`static`,来指定类型方法。类还可以用关键字`class`来允许子类重写父类的方法实现。
|
||||
|
||||
> 注意:
|
||||
> 在 Objective-C 里面,你只能为 Objective-C 的类定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法:每一个类型方法都被它所支持的类型显式包含。
|
||||
> 注意
|
||||
> 在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。
|
||||
|
||||
类型方法和实例方法一样用点语法调用。但是,你是在类型层面上调用这个方法,而不是在实例层面上调用。下面是如何在`SomeClass`类上调用类型方法的例子:
|
||||
类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在`SomeClass`类上调用类型方法的例子:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
static func someTypeMethod() {
|
||||
// type method implementation goes here
|
||||
}
|
||||
class func someTypeMethod() {
|
||||
// type method implementation goes here
|
||||
}
|
||||
}
|
||||
SomeClass.someTypeMethod()
|
||||
```
|
||||
|
||||
在类型方法的方法体(body)中,`self`指向这个类型本身,而不是类型的某个实例。对于结构体和枚举来说,这意味着你可以用`self`来消除静态属性和静态方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
|
||||
在类型方法的方法体(body)中,`self`指向这个类型本身,而不是类型的某个实例。这意味着你可以用`self`来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
|
||||
|
||||
一般来说,任何未限定的方法和属性名称,将会来自于本类中另外的类型级别的方法和属性。一个类型方法可以调用本类中另一个类型方法的名称,而无需在方法名称前面加上类型名称的前缀。同样,结构体和枚举的类型方法也能够直接通过静态属性的名称访问静态属性,而不需要类型名称前缀。
|
||||
一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。
|
||||
|
||||
下面的例子定义了一个名为`LevelTracker`结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息。
|
||||
|
||||
游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker`结构体用静态属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。
|
||||
游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker`结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。
|
||||
|
||||
```swift
|
||||
struct LevelTracker {
|
||||
@ -258,11 +263,11 @@ struct LevelTracker {
|
||||
}
|
||||
```
|
||||
|
||||
`LevelTracker`监测玩家的已解锁的最高等级。这个值被存储在静态属性`highestUnlockedLevel`中。
|
||||
`LevelTracker`监测玩家已解锁的最高等级。这个值被存储在类型属性`highestUnlockedLevel`中。
|
||||
|
||||
`LevelTracker`还定义了两个类型方法与`highestUnlockedLevel`配合工作。第一个类型方法是`unlockLevel`:一旦新等级被解锁,它会更新`highestUnlockedLevel`的值。第二个类型方法是`levelIsUnlocked`:如果某个给定的等级已经被解锁,它将返回`true`。(注意:尽管我们没有使用类似`LevelTracker.highestUnlockedLevel`的写法,这个类型方法还是能够访问静态属性`highestUnlockedLevel`)
|
||||
`LevelTracker`还定义了两个类型方法与`highestUnlockedLevel`配合工作。第一个类型方法是`unlockLevel`,一旦新等级被解锁,它会更新`highestUnlockedLevel`的值。第二个类型方法是`levelIsUnlocked`,如果某个给定的等级已经被解锁,它将返回`true`。(注意,尽管我们没有使用类似`LevelTracker.highestUnlockedLevel`的写法,这个类型方法还是能够访问类型属性`highestUnlockedLevel`)
|
||||
|
||||
除了静态属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测玩家当前的等级。
|
||||
除了类型属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测每个玩家当前的等级。
|
||||
|
||||
为了便于管理`currentLevel`属性,`LevelTracker`定义了实例方法`advanceToLevel`。这个方法会在更新`currentLevel`之前检查所请求的新等级是否已经解锁。`advanceToLevel`方法返回布尔值以指示是否能够设置`currentLevel`。
|
||||
|
||||
@ -270,19 +275,19 @@ struct LevelTracker {
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var tracker = LevelTracker()
|
||||
let playerName: String
|
||||
func completedLevel(level: Int) {
|
||||
LevelTracker.unlockLevel(level + 1)
|
||||
tracker.advanceToLevel(level + 1)
|
||||
}
|
||||
init(name: String) {
|
||||
playerName = name
|
||||
}
|
||||
var tracker = LevelTracker()
|
||||
let playerName: String
|
||||
func completedLevel(level: Int) {
|
||||
LevelTracker.unlockLevel(level + 1)
|
||||
tracker.advanceToLevel(level + 1)
|
||||
}
|
||||
init(name: String) {
|
||||
playerName = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Player`类创建一个新的`LevelTracker`实例来监测这个用户的进度。它提供了`completedLevel`方法:一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了`advanceToLevel`返回的布尔值,因为之前调用`LevelTracker.unlockLevel`时就知道了这个等级已经被解锁了)。
|
||||
`Player`类创建一个新的`LevelTracker`实例来监测这个用户的进度。它提供了`completedLevel`方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了`advanceToLevel`返回的布尔值,因为之前调用`LevelTracker.unlockLevel`时就知道了这个等级已经被解锁了)。
|
||||
|
||||
你还可以为一个新的玩家创建一个`Player`的实例,然后看这个玩家完成等级一时发生了什么:
|
||||
|
||||
@ -293,14 +298,14 @@ print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
|
||||
// 打印输出:highest unlocked level is now 2
|
||||
```
|
||||
|
||||
如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么这次设置玩家当前等级的尝试将会失败:
|
||||
如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败:
|
||||
|
||||
```swift
|
||||
player = Player(name: "Beto")
|
||||
if player.tracker.advanceToLevel(6) {
|
||||
print("player is now on level 6")
|
||||
print("player is now on level 6")
|
||||
} else {
|
||||
print("level 6 has not yet been unlocked")
|
||||
print("level 6 has not yet been unlocked")
|
||||
}
|
||||
// 打印输出:level 6 has not yet been unlocked
|
||||
```
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# 下标脚本(Subscripts)
|
||||
# 下标(Subscripts)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
@ -6,104 +6,109 @@
|
||||
> 校对:[zq54zquan](https://github.com/zq54zquan)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[shanksyang](https://github.com/shanksyang)
|
||||
> 翻译+校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.1
|
||||
> 翻译+校对:[shanks](http://codebuild.me),[Realank](https://github.com/Realank)
|
||||
|
||||
> 2.2
|
||||
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
|
||||
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [下标脚本语法](#subscript_syntax)
|
||||
- [下标脚本用法](#subscript_usage)
|
||||
- [下标脚本选项](#subscript_options)
|
||||
- [下标语法](#subscript_syntax)
|
||||
- [下标用法](#subscript_usage)
|
||||
- [下标选项](#subscript_options)
|
||||
|
||||
*下标脚本* 可以定义在类(Class)、结构体(structure)和枚举(enumeration)这些目标中,可以认为是访问集合(collection),列表(list)或序列(sequence的快捷方式,使用下标脚本的索引设置和获取值,不需要再调用实例的特定的赋值和访问方法。举例来说,用下标脚本访问一个数组(Array)实例中的元素可以这样写 `someArray[index]` ,访问字典(Dictionary)实例中的元素可以这样写 `someDictionary[key]`。
|
||||
*下标* (subscripts)可以定义在类(class)、结构体(structure)和枚举(enumeration)中,是访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个`Array`实例中的元素可以写作`someArray[index]`,访问`Dictionary`实例中的元素可以写作`someDictionary[key]`。
|
||||
|
||||
对于同一个目标可以定义多个下标脚本,通过索引值类型的不同来进行重载,下标脚本不限于单个纬度,你可以定义多个入参的下标脚本满足自定义类型的需求。
|
||||
|
||||
> 译者:这里附属脚本重载在本小节中原文并没有任何演示
|
||||
一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
|
||||
|
||||
<a name="subscript_syntax"></a>
|
||||
## 下标脚本语法
|
||||
## 下标语法
|
||||
|
||||
下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。语法类似于实例方法和计算型属性的混合。与定义实例方法类似,定义下标脚本使用`subscript`关键字,显式声明入参(一个或多个)和返回类型。与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的getter和setter:
|
||||
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用`subscript`关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
get {
|
||||
// 返回与入参匹配的Int类型的值
|
||||
// 返回一个适当的 Int 类型的值
|
||||
}
|
||||
|
||||
set(newValue) {
|
||||
// 执行赋值操作
|
||||
// 执行适当的赋值操作
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`newValue`的类型必须和下标脚本定义的返回类型相同。与计算型属性相同的是set的入参声明`newValue`就算不写,在set代码块中依然可以使用默认的`newValue`这个变量来访问新赋的值。
|
||||
`newValue`的类型和下标的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`)。如果不指定参数,setter 会提供一个名为`newValue`的默认参数。
|
||||
|
||||
与只读计算型属性一样,可以直接将原本应该写在`get`代码块中的代码写在`subscript`中:
|
||||
如同只读计算型属性,可以省略只读下标的`get`关键字:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
// 返回与入参匹配的Int类型的值
|
||||
// 返回一个适当的 Int 类型的值
|
||||
}
|
||||
```
|
||||
|
||||
下面代码演示了一个在`TimesTable`结构体中使用只读下标脚本的用法,该结构体用来展示传入整数的*n*倍。
|
||||
下面代码演示了只读下标的实现,这里定义了一个`TimesTable`结构体,用来表示传入整数的乘法表:
|
||||
|
||||
```swift
|
||||
struct TimesTable {
|
||||
let multiplier: Int
|
||||
subscript(index: Int) -> Int {
|
||||
return multiplier * index
|
||||
return multiplier * index
|
||||
}
|
||||
}
|
||||
let threeTimesTable = TimesTable(multiplier: 3)
|
||||
print("3的6倍是\(threeTimesTable[6])")
|
||||
// 输出 "3的6倍是18"
|
||||
print("six times three is \(threeTimesTable[6])")
|
||||
// 输出 "six times three is 18"
|
||||
```
|
||||
|
||||
在上例中,通过`TimesTable`结构体创建了一个用来表示索引值三倍的实例。数值`3`作为结构体`构造函数`入参初始化实例成员`multiplier`。
|
||||
在上例中,创建了一个`TimesTable`实例,用来表示整数`3`的乘法表。数值`3`被传递给结构体的构造函数,作为实例成员`multiplier`的值。
|
||||
|
||||
你可以通过下标脚本来得到结果,比如`threeTimesTable[6]`。这条语句访问了`threeTimesTable`的第六个元素,返回`6`的`3`倍即`18`。
|
||||
你可以通过下标访问`threeTimesTable`实例,例如上面演示的`threeTimesTable[6]`。这条语句查询了`3`的乘法表中的第六个元素,返回`3`的`6`倍即`18`。
|
||||
|
||||
>注意:
|
||||
> `TimesTable`例子是基于一个固定的数学公式。它并不适合对`threeTimesTable[someIndex]`进行赋值操作,这也是为什么附属脚本只定义为只读的原因。
|
||||
> 注意
|
||||
> `TimesTable`例子基于一个固定的数学公式,对`threeTimesTable[someIndex]`进行赋值操作并不合适,因此下标定义为只读的。
|
||||
|
||||
<a name="subscript_usage"></a>
|
||||
## 下标脚本用法
|
||||
## 下标用法
|
||||
|
||||
根据使用场景不同下标脚本也具有不同的含义。通常下标脚本是用来访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以在你自己特定的类或结构体中自由的实现下标脚本来提供合适的功能。
|
||||
下标的确切含义取决于使用场景。下标通常作为访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。
|
||||
|
||||
例如,Swift 的字典(Dictionary)实现了通过下标脚本来对其实例中存放的值进行存取操作。在下标脚本中使用和字典索引相同类型的值,并且把一个字典值类型的值赋值给这个下标脚本来为字典设值:
|
||||
例如,Swift 的`Dictionary`类型实现下标用于对其实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
|
||||
|
||||
```swift
|
||||
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
numberOfLegs["bird"] = 2
|
||||
```
|
||||
|
||||
上例定义一个名为`numberOfLegs`的变量并用一个字典字面量初始化出了包含三对键值的字典实例。`numberOfLegs`的字典存放值类型推断为`[String:Int]`。字典实例创建完成之后通过下标脚本的方式将整型值`2`赋值到字典实例的索引为`bird`的位置中。
|
||||
上例定义一个名为`numberOfLegs`的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs`字典的类型被推断为`[String: Int]`。字典创建完成后,该例子通过下标将`String`类型的键`bird`和`Int`类型的值`2`添加到字典中。
|
||||
|
||||
更多关于字典(Dictionary)下标脚本的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)
|
||||
更多关于`Dictionary`下标的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)
|
||||
|
||||
> 注意:
|
||||
> Swift 中字典的附属脚本实现中,在`get`部分返回值是`Int?`,上例中的`numberOfLegs`字典通过附属脚本返回的是一个`Int?`或者说“可选的int”,不是每个字典的索引都能得到一个整型值,对于没有设过值的索引的访问返回的结果就是`nil`;同样想要从字典实例中删除某个索引下的值也只需要给这个索引赋值为`nil`即可。
|
||||
> 注意
|
||||
> Swift 的`Dictionary`类型的下标接受并返回可选类型的值。上例中的`numberOfLegs`字典通过下标返回的是一个`Int?`或者说“可选的int”。`Dictionary`类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为`nil`即可。
|
||||
|
||||
<a name="subscript_options"></a>
|
||||
## 下标脚本选项
|
||||
## 下标选项
|
||||
|
||||
下标脚本允许任意数量的入参索引,并且每个入参类型也没有限制。下标脚本的返回值也可以是任何类型。下标脚本可以使用变量参数和可变参数,但使用写入读出(in-out)参数或给参数设置默认值都是不允许的。
|
||||
下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但不能使用输入输出参数,也不能给参数设置默认值。
|
||||
|
||||
一个类或结构体可以根据自身需要提供多个下标脚本实现,在定义下标脚本时通过入参的类型进行区分,使用下标脚本时会自动匹配合适的下标脚本实现运行,这就是*下标脚本的重载*。
|
||||
一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标,这就是*下标的重载*。
|
||||
|
||||
一个下标脚本入参是最常见的情况,但只要有合适的场景也可以定义多个下标脚本入参。如下例定义了一个`Matrix`结构体,将呈现一个`Double`类型的二维矩阵。`Matrix`结构体的下标脚本需要两个整型参数:
|
||||
虽然接受单一入参的下标是最常见的,但也可以根据情况定义接受多个入参的下标。例如下例定义了一个`Matrix`结构体,用于表示一个`Double`类型的二维矩阵。`Matrix`结构体的下标接受两个整型参数:
|
||||
|
||||
```swift
|
||||
struct Matrix {
|
||||
let rows: Int, columns: Int
|
||||
var grid: [Double]
|
||||
init(rows: Int, columns: Int) {
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(count: rows * columns, repeatedValue: 0.0)
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(count: rows * columns, repeatedValue: 0.0)
|
||||
}
|
||||
func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
@ -121,7 +126,7 @@ struct Matrix {
|
||||
}
|
||||
```
|
||||
|
||||
`Matrix`提供了一个两个入参的构造方法,入参分别是`rows`和`columns`,创建了一个足够容纳`rows * columns`个数的`Double`类型数组。通过传入数组长度和初始值0.0到数组的一个构造器,将`Matrix`中每个元素初始值0.0。关于数组的构造方法和析构方法请参考[创建一个空数组](./04_Collection_Types.html#creating_an_empty_array)。
|
||||
`Matrix`提供了一个接受两个入参的构造方法,入参分别是`rows`和`columns`,创建了一个足够容纳`rows * columns`个`Double`类型的值的数组。通过传入数组长度和初始值`0.0`到数组的构造器,将矩阵中每个位置的值初始化为`0.0`。关于数组的这种构造方法请参考[创建一个空数组](./04_Collection_Types.html#creating_an_empty_array)。
|
||||
|
||||
你可以通过传入合适的`row`和`column`的数量来构造一个新的`Matrix`实例:
|
||||
|
||||
@ -129,32 +134,22 @@ struct Matrix {
|
||||
var matrix = Matrix(rows: 2, columns: 2)
|
||||
```
|
||||
|
||||
上例中创建了一个新的两行两列的`Matrix`实例。在阅读顺序从左上到右下的`Matrix`实例中的数组实例`grid`是矩阵二维数组的扁平化存储:
|
||||
上例中创建了一个`Matrix`实例来表示两行两列的矩阵。该`Matrix`实例的`grid`数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
|
||||
|
||||
```swift
|
||||
// 示意图
|
||||
grid = [0.0, 0.0, 0.0, 0.0]
|
||||

|
||||
|
||||
col0 col1
|
||||
row0 [0.0, 0.0,
|
||||
row1 0.0, 0.0]
|
||||
```
|
||||
|
||||
将值赋给带有`row`和`column`下标脚本的`matrix`实例表达式可以完成赋值操作,下标脚本入参使用逗号分割
|
||||
将`row`和`column`的值传入下标来为矩阵设值,下标的入参使用逗号分隔:
|
||||
|
||||
```swift
|
||||
matrix[0, 1] = 1.5
|
||||
matrix[1, 0] = 3.2
|
||||
```
|
||||
|
||||
上面两条语句分别`让matrix`的右上值为 1.5,坐下值为 3.2:
|
||||
上面两条语句分别调用下标的 setter 将矩阵右上角位置(即`row`为`0`、`column`为`1`的位置)的值设置为`1.5`,将矩阵左下角位置(即`row`为`1`、`column`为`0`的位置)的值设置为`3.2`:
|
||||
|
||||
```swift
|
||||
[0.0, 1.5,
|
||||
3.2, 0.0]
|
||||
```
|
||||

|
||||
|
||||
`Matrix`下标脚本的`getter`和`setter`中同时调用了下标脚本入参的`row`和`column`是否有效的判断。为了方便进行断言,`Matrix`包含了一个名为`indexIsValidForRow(_:column:)`的成员方法,用来确认入参的`row`或`column`值是否会造成数组越界:
|
||||
`Matrix`下标的 getter 和 setter 中都含有断言,用来检查下标入参`row`和`column`的值是否有效。为了方便进行断言,`Matrix`包含了一个名为`indexIsValidForRow(_:column:)`的便利方法,用来检查入参`row`和`column`的值是否在矩阵范围内:
|
||||
|
||||
```swift
|
||||
func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
@ -162,9 +157,9 @@ func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
}
|
||||
```
|
||||
|
||||
断言在下标脚本越界时触发:
|
||||
断言在下标越界时触发:
|
||||
|
||||
```swift
|
||||
let someValue = matrix[2, 2]
|
||||
// 断言将会触发,因为 [2, 2] 已经超过了matrix的最大长度
|
||||
// 断言将会触发,因为 [2, 2] 已经超过了 matrix 的范围
|
||||
```
|
||||
|
||||
@ -1,35 +1,38 @@
|
||||
# 继承(Inheritance)
|
||||
-------------------
|
||||
|
||||
-------------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[Hawstein](https://github.com/Hawstein)
|
||||
> 校对:[menlongsheng](https://github.com/menlongsheng)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[shanksyang](https://github.com/shanksyang)
|
||||
> 2.0,2.1
|
||||
> 翻译+校对:[shanks](http://codebuild.me)
|
||||
>
|
||||
> 2.2
|
||||
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [定义一个基类(Base class)](#defining_a_base_class)
|
||||
- [定义一个基类(Defining a Base Class)](#defining_a_base_class)
|
||||
- [子类生成(Subclassing)](#subclassing)
|
||||
- [重写(Overriding)](#overriding)
|
||||
- [防止重写](#preventing_overrides)
|
||||
- [防止重写(Preventing Overrides)](#preventing_overrides)
|
||||
|
||||
一个类可以*继承(inherit)*另一个类的方法(methods),属性(properties)和其它特性。当一个类继承其它类时,继承类叫*子类(subclass)*,被继承类叫*超类(或父类,superclass)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。
|
||||
|
||||
在 Swift 中,类可以调用和访问超类的方法,属性和下标脚本(subscripts),并且可以重写(override)这些方法,属性和下标脚本来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
|
||||
在 Swift 中,类可以调用和访问超类的方法,属性和下标(subscripts),并且可以重写(override)这些方法,属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
|
||||
|
||||
可以为类中继承来的属性添加属性观察器(property observers),这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性(stored property)还是计算型属性(computed property)。
|
||||
|
||||
<a name="defining_a_base_class"></a>
|
||||
## 定义一个基类(Base class)
|
||||
## 定义一个基类(Defining a Base Class)
|
||||
|
||||
不继承于其它类的类,称之为*基类(base calss)*。
|
||||
不继承于其它类的类,称之为*基类(base class)*。
|
||||
|
||||
> 注意:
|
||||
> 注意
|
||||
Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
|
||||
|
||||
下面的例子定义了一个叫`Vehicle`的基类。这个基类声明了一个名为`currentSpeed `,默认值是0.0的存储属性(属性类型推断为`Double `)。`currentSpeed `属性的值被一个`String` 类型的只读计算型属性`description`使用,用来创建车辆的描述。
|
||||
下面的例子定义了一个叫`Vehicle`的基类。这个基类声明了一个名为`currentSpeed `,默认值是`0.0`的存储属性(属性类型推断为`Double`)。`currentSpeed`属性的值被一个`String`类型的只读计算型属性`description`使用,用来创建车辆的描述。
|
||||
|
||||
`Vehicle`基类也定义了一个名为`makeNoise`的方法。这个方法实际上不为`Vehicle`实例做任何事,但之后将会被`Vehicle`的子类定制:
|
||||
|
||||
@ -45,34 +48,35 @@ class Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
您可以用初始化语法创建一个`Vehicle `的新实例,即类名后面跟一个空括号:
|
||||
您可以用初始化语法创建一个`Vehicle`的新实例,即类名后面跟一个空括号:
|
||||
|
||||
```swift
|
||||
let someVehicle = Vehicle()
|
||||
```
|
||||
|
||||
现在已经创建了一个`Vehicle`的新实例,你可以访问它的`description`属性来打印车辆的当前速度。
|
||||
现在已经创建了一个`Vehicle`的新实例,你可以访问它的`description`属性来打印车辆的当前速度:
|
||||
|
||||
```swift
|
||||
print("Vehicle: \(someVehicle.description)")
|
||||
// Vehicle: traveling at 0.0 miles per hour
|
||||
```
|
||||
|
||||
`Vehicle`类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要改进它能够描述一个更加具体的车辆类。
|
||||
`Vehicle`类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要完善它从而能够描述一个更加具体类型的车辆。
|
||||
|
||||
<a name="subclassing"></a>
|
||||
## 子类生成(Subclassing)
|
||||
|
||||
*子类生成(Subclassing)*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以优化或改变它。你还可以为子类添加新的特性。
|
||||
*子类生成(Subclassing)*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。
|
||||
|
||||
为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeSuperclass {
|
||||
// 类的定义
|
||||
// 这里是子类的定义
|
||||
}
|
||||
```
|
||||
|
||||
下一个例子,定义一个叫`Bicycle`的子类,继承成父类`Vehicle`
|
||||
下一个例子,定义一个叫`Bicycle`的子类,继承成父类`Vehicle`:
|
||||
|
||||
```swift
|
||||
class Bicycle: Vehicle {
|
||||
@ -80,18 +84,18 @@ class Bicycle: Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
新的`Bicycle`类自动获得`Vehicle`类的所有特性,比如 `currentSpeed `和`description`属性,还有它的`makeNoise`方法。
|
||||
新的`Bicycle`类自动获得`Vehicle`类的所有特性,比如`currentSpeed`和`description`属性,还有它的`makeNoise()`方法。
|
||||
|
||||
除了它所继承的特性,`Bicycle`类还定义了一个默认值为`false`的存储型属性`hasBasket`(属性推断为`Bool`)。
|
||||
|
||||
默认情况下,你创建任何新的`Bicycle`实例将不会有一个篮子,创建该实例之后,你可以为特定的`Bicycle`实例设置`hasBasket `属性为`ture`:
|
||||
默认情况下,你创建任何新的`Bicycle`实例将不会有一个篮子(即`hasBasket`属性默认为`false`),创建该实例之后,你可以为特定的`Bicycle`实例设置`hasBasket`属性为`ture`:
|
||||
|
||||
```swift
|
||||
let bicycle = Bicycle()
|
||||
bicycle.hasBasket = true
|
||||
```
|
||||
|
||||
你还可以修改`Bicycle `实例所继承的`currentSpeed `属性,和查询实例所继承的`description `属性:
|
||||
你还可以修改`Bicycle`实例所继承的`currentSpeed`属性,和查询实例所继承的`description`属性:
|
||||
|
||||
```swift
|
||||
bicycle.currentSpeed = 15.0
|
||||
@ -99,7 +103,7 @@ print("Bicycle: \(bicycle.description)")
|
||||
// Bicycle: traveling at 15.0 miles per hour
|
||||
```
|
||||
|
||||
子类还可以继续被其它类继承,下面的示例为`Bicycle `创建了一个名为`Tandem `(双人自行车)的子类:
|
||||
子类还可以继续被其它类继承,下面的示例为`Bicycle`创建了一个名为`Tandem`(双人自行车)的子类:
|
||||
|
||||
```swift
|
||||
class Tandem: Bicycle {
|
||||
@ -107,9 +111,9 @@ class Tandem: Bicycle {
|
||||
}
|
||||
```
|
||||
|
||||
`Tandem`从`Bicycle`继承了所有的属性与方法,这又使它同时继承了`Vehicle`的所有属性与方法。`Tandem`也增加了一个新的叫做`currentNumberOfPassengers`的存储型属性,默认值为0。
|
||||
`Tandem`从`Bicycle`继承了所有的属性与方法,这又使它同时继承了`Vehicle`的所有属性与方法。`Tandem`也增加了一个新的叫做`currentNumberOfPassengers`的存储型属性,默认值为`0`。
|
||||
|
||||
如果你创建了一个`Tandem`的实例,你可以使用它所有的新属性和继承的属性,还能查询从`Vehicle`继承来的只读属性`description `:
|
||||
如果你创建了一个`Tandem`的实例,你可以使用它所有的新属性和继承的属性,还能查询从`Vehicle`继承来的只读属性`description`:
|
||||
|
||||
```swift
|
||||
let tandem = Tandem()
|
||||
@ -123,27 +127,27 @@ print("Tandem: \(tandem.description)")
|
||||
<a name="overriding"></a>
|
||||
## 重写(Overriding)
|
||||
|
||||
子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标脚本(subscript)提供自己定制的实现(implementation)。我们把这种行为叫*重写(overriding)*。
|
||||
子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标(subscript)提供自己定制的实现(implementation)。我们把这种行为叫*重写(overriding)*。
|
||||
|
||||
如果要重写某个特性,你需要在重写定义的前面加上`override`关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少`override`关键字的重写都会在编译时被诊断为错误。
|
||||
|
||||
`override`关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
|
||||
|
||||
### 访问超类的方法,属性及下标脚本
|
||||
### 访问超类的方法,属性及下标
|
||||
|
||||
当你在子类中重写超类的方法,属性或下标脚本时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以优化已有实现的行为,或在一个继承来的变量中存储一个修改过的值。
|
||||
当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。
|
||||
|
||||
在合适的地方,你可以通过使用`super`前缀来访问超类版本的方法,属性或下标脚本:
|
||||
在合适的地方,你可以通过使用`super`前缀来访问超类版本的方法,属性或下标:
|
||||
|
||||
* 在方法`someMethod`的重写实现中,可以通过`super.someMethod()`来调用超类版本的`someMethod`方法。
|
||||
* 在方法`someMethod()`的重写实现中,可以通过`super.someMethod()`来调用超类版本的`someMethod()`方法。
|
||||
* 在属性`someProperty`的 getter 或 setter 的重写实现中,可以通过`super.someProperty`来访问超类版本的`someProperty`属性。
|
||||
* 在下标脚本的重写实现中,可以通过`super[someIndex]`来访问超类版本中的相同下标脚本。
|
||||
* 在下标的重写实现中,可以通过`super[someIndex]`来访问超类版本中的相同下标。
|
||||
|
||||
### 重写方法
|
||||
|
||||
在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
|
||||
|
||||
下面的例子定义了`Vehicle`的一个新的子类,叫`Train `,它重写了从`Vehicle`类继承来的`makeNoise `方法:
|
||||
下面的例子定义了`Vehicle`的一个新的子类,叫`Train`,它重写了从`Vehicle`类继承来的`makeNoise()`方法:
|
||||
|
||||
```swift
|
||||
class Train: Vehicle {
|
||||
@ -153,28 +157,28 @@ class Train: Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
如果你创建一个`Train `的新实例,并调用了它的`makeNoise `方法,你就会发现`Train `版本的方法被调用:
|
||||
如果你创建一个`Train`的新实例,并调用了它的`makeNoise()`方法,你就会发现`Train`版本的方法被调用:
|
||||
|
||||
```swift
|
||||
let train = Train()
|
||||
train.makeNoise()
|
||||
// prints "Choo Choo"
|
||||
// 打印 "Choo Choo"
|
||||
```
|
||||
|
||||
### 重写属性
|
||||
|
||||
你可以重写继承来的实例属性或类属性,提供自己定制的getter和setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
|
||||
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
|
||||
|
||||
#### 重写属性的Getters和Setters
|
||||
#### 重写属性的 Getters 和 Setters
|
||||
|
||||
你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
|
||||
|
||||
你可以将一个继承来的只读属性重写为一个读写属性,只需要你在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
|
||||
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
|
||||
|
||||
> 注意:
|
||||
> 注意
|
||||
如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过`super.someProperty`来返回继承来的值,其中`someProperty`是你要重写的属性的名字。
|
||||
|
||||
以下的例子定义了一个新类,叫`Car`,它是`Vehicle `的子类。这个类引入了一个新的存储型属性叫做`gear `,默认为整数1。`Car`类重写了继承自`Vehicle `的description属性,提供自定义的,包含当前档位的描述:
|
||||
以下的例子定义了一个新类,叫`Car`,它是`Vehicle`的子类。这个类引入了一个新的存储型属性叫做`gear`,默认值为整数`1`。`Car`类重写了继承自`Vehicle`的`description`属性,提供包含当前档位的自定义描述:
|
||||
|
||||
```swift
|
||||
class Car: Vehicle {
|
||||
@ -185,9 +189,9 @@ class Car: Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
重写的`description `属性,首先要调用`super.description`返回`Vehicle`类的`description`属性。之后,`Car `类版本的`description`在末尾增加了一些额外的文本来提供关于当前档位的信息。
|
||||
重写的`description`属性首先要调用`super.description`返回`Vehicle`类的`description`属性。之后,`Car`类版本的`description`在末尾增加了一些额外的文本来提供关于当前档位的信息。
|
||||
|
||||
如果你创建了`Car `的实例并且设置了它的`gear`和`currentSpeed`属性,你可以看到它的`description`返回了`Car`中定义的`description`:
|
||||
如果你创建了`Car`的实例并且设置了它的`gear`和`currentSpeed`属性,你可以看到它的`description`返回了`Car`中的自定义描述:
|
||||
|
||||
```swift
|
||||
let car = Car()
|
||||
@ -197,12 +201,14 @@ print("Car: \(car.description)")
|
||||
// Car: traveling at 25.0 miles per hour in gear 3
|
||||
```
|
||||
|
||||
<a name="overriding_property_observers"></a>
|
||||
#### 重写属性观察器(Property Observer)
|
||||
|
||||
你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
|
||||
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
|
||||
|
||||
> 注意:
|
||||
你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet`或`didSet`实现是不恰当。此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。
|
||||
> 注意
|
||||
你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet`或`didSet`实现是不恰当。
|
||||
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。
|
||||
|
||||
下面的例子定义了一个新类叫`AutomaticCar`,它是`Car`的子类。`AutomaticCar`表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位:
|
||||
|
||||
@ -216,7 +222,7 @@ class AutomaticCar: Car {
|
||||
}
|
||||
```
|
||||
|
||||
当你设置`AutomaticCar`的`currentSpeed `属性,属性的`didSet`观察器就会自动地设置`gear`属性,为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以10,然后向下取得最接近的整数值,最后加1来得到档位`gear`的值。例如,速度为10.0时,挡位为1;速度为35.0时,挡位为4:
|
||||
当你设置`AutomaticCar`的`currentSpeed`属性,属性的`didSet`观察器就会自动地设置`gear`属性,为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以`10`,然后向下取得最接近的整数值,最后加`1`来得到档位`gear`的值。例如,速度为`35.0`时,挡位为`4`:
|
||||
|
||||
```swift
|
||||
let automatic = AutomaticCar()
|
||||
@ -228,8 +234,8 @@ print("AutomaticCar: \(automatic.description)")
|
||||
<a name="preventing_overrides"></a>
|
||||
## 防止重写
|
||||
|
||||
你可以通过把方法,属性或下标脚本标记为*`final`*来防止它们被重写,只需要在声明关键字前加上`final`特性即可。(例如:`final var`, `final func`, `final class func`, 以及 `final subscript`)
|
||||
你可以通过把方法,属性或下标标记为*`final`*来防止它们被重写,只需要在声明关键字前加上`final`修饰符即可(例如:`final var`,`final func`,`final class func`,以及`final subscript`)。
|
||||
|
||||
如果你重写了`final`方法,属性或下标脚本,在编译时会报错。在类扩展中的方法,属性或下标脚本也可以在扩展的定义里标记为 final。
|
||||
如果你重写了带有`final`标记的方法,属性或下标,在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final 的。
|
||||
|
||||
你可以通过在关键字`class`前添加`final`特性(`final class`)来将整个类标记为 final 的,这样的类是不可被继承的,任何子类试图继承此类时,在编译时会报错。
|
||||
你可以通过在关键字`class`前添加`final`修饰符(`final class`)来将整个类标记为 final 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,12 +8,18 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[chenmingbiao](https://github.com/chenmingbiao)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-31
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [析构过程原理](#how_deinitialization_works)
|
||||
- [析构器操作](#deinitializers_in_action)
|
||||
- [析构器实践](#deinitializers_in_action)
|
||||
|
||||
析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字`deinit`来标示,类似于构造器要用`init`来标示。
|
||||
*析构器*只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字`deinit`来标示,类似于构造器要用`init`来标示。
|
||||
|
||||
<a name="how_deinitialization_works"></a>
|
||||
##析构过程原理
|
||||
@ -28,22 +34,22 @@ deinit {
|
||||
}
|
||||
```
|
||||
|
||||
析构器是在实例释放发生前被自动调用。析构器是不允许被主动调用的。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
|
||||
析构器是在实例释放发生前被自动调用。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
|
||||
|
||||
因为直到实例的析构器被调用时,实例才会被释放,所以析构器可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
|
||||
因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
|
||||
|
||||
<a name="deinitializers_in_action"></a>
|
||||
##析构器操作
|
||||
##析构器实践
|
||||
|
||||
这是一个析构器操作的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是`Bank`和`Player`。`Bank`结构体管理一个虚拟货币的流通,在这个流通中我们设定`Bank`永远不可能拥有超过 10,000 的硬币,而且在游戏中有且只能有一个`Bank`存在,因此`Bank`结构体在实现时会带有静态属性和静态方法来存储和管理其当前的状态。
|
||||
这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是`Bank`和`Player`。`Bank`类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个`Bank`存在,因此`Bank`用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。
|
||||
|
||||
```swift
|
||||
struct Bank {
|
||||
class Bank {
|
||||
static var coinsInBank = 10_000
|
||||
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
|
||||
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
|
||||
coinsInBank -= numberOfCoinsToVend
|
||||
return numberOfCoinsToVend
|
||||
static func vendCoins(numberOfCoinsRequested: Int) -> Int {
|
||||
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
|
||||
coinsInBank -= numberOfCoinsToVend
|
||||
return numberOfCoinsToVend
|
||||
}
|
||||
static func receiveCoins(coins: Int) {
|
||||
coinsInBank += coins
|
||||
@ -51,13 +57,13 @@ struct Bank {
|
||||
}
|
||||
```
|
||||
|
||||
`Bank`根据它的`coinsInBank`属性来跟踪当前它拥有的硬币数量。`Bank`还提供两个方法——`vendCoins(_:)`和`receiveCoins(_:)`,分别用来处理硬币的分发和收集。
|
||||
`Bank`使用`coinsInBank`属性来跟踪它当前拥有的硬币数量。`Bank`还提供了两个方法,`vendCoins(_:)`和`receiveCoins(_:)`,分别用来处理硬币的分发和收集。
|
||||
|
||||
`vendCoins(_:)`方法在bank对象分发硬币之前检查是否有足够的硬币。如果没有足够多的硬币,`Bank`会返回一个比请求时要小的数字(如果没有硬币留在bank对象中就返回 0)。`vendCoins`方法声明`numberOfCoinsToVend`为一个变量参数,这样就可以在方法体的内部修改数字,而不需要定义一个新的变量。`vendCoins`方法返回一个整型值,表明了提供的硬币的实际数目。
|
||||
`vendCoins(_:)`方法在`Bank`对象分发硬币之前检查是否有足够的硬币。如果硬币不足,`Bank`对象会返回一个比请求时小的数字(如果`Bank`对象中没有硬币了就返回`0`)。`vendCoins`方法返回一个整型值,表示提供的硬币的实际数量。
|
||||
|
||||
`receiveCoins`方法只是将bank对象的硬币存储和接收到的硬币数目相加,再保存回bank对象。
|
||||
`receiveCoins(_:)`方法只是将`Bank`对象接收到的硬币数目加回硬币存储中。
|
||||
|
||||
`Player`类描述了游戏中的一个玩家。每一个 player 在任何时刻都有一定数量的硬币存储在他们的钱包中。这通过 player 的`coinsInPurse`属性来体现:
|
||||
`Player`类描述了游戏中的一个玩家。每一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中。这通过玩家的`coinsInPurse`属性来表示:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
@ -74,22 +80,21 @@ class Player {
|
||||
}
|
||||
```
|
||||
|
||||
每个`Player`实例在初始化的过程中,都从`Bank`对象获取指定数量的硬币。如果没有足够的硬币可用,`Player`实例可能会收到比指定数量少的硬币.
|
||||
|
||||
每个`Player`实例构造时都会设定由硬币组成的启动额度值,这些硬币在bank对象初始化的过程中得到。如果在bank对象中没有足够的硬币可用,`Player`实例可能收到比指定数目少的硬币。
|
||||
|
||||
`Player`类定义了一个`winCoins(_:)`方法,该方法从bank对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构器,这个析构器在`Player`实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返回给bank对象:
|
||||
`Player`类定义了一个`winCoins(_:)`方法,该方法从`Bank`对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构器,这个析构器在`Player`实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给`Bank`对象:
|
||||
|
||||
```swift
|
||||
var playerOne: Player? = Player(coins: 100)
|
||||
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
|
||||
// 输出 "A new player has joined the game with 100 coins"
|
||||
print("There are now \(Bank.coinsInBank) coins left in the bank")
|
||||
// 输出 "There are now 9900 coins left in the bank"
|
||||
// 打印 "A new player has joined the game with 100 coins"
|
||||
print("There are now \(Bank.coinsInBank) coins left in the bank")
|
||||
// 打印 "There are now 9900 coins left in the bank"
|
||||
```
|
||||
|
||||
一个新的`Player`实例被创建时会设定有 100 个硬币(如果bank对象中硬币的数目足够)。这`个Player`实例存储在一个名为`playerOne`的可选`Player`变量中。这里使用一个可选变量,是因为玩家可以随时离开游戏。设置为可选使得你可以跟踪当前是否有玩家在游戏中。
|
||||
创建一个`Player`实例的时候,会向`Bank`对象请求 100 个硬币,如果有足够的硬币可用的话。这个`Player`实例存储在一个名为`playerOne`的可选类型的变量中。这里使用了一个可选类型的变量,因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。
|
||||
|
||||
因为`playerOne`是可选的,所以用一个感叹号(`!`)作为修饰符,每当其`winCoins(_:)`方法被调用时,`coinsInPurse`属性就会被访问并打印出它的默认硬币数目。
|
||||
因为`playerOne`是可选的,所以访问其`coinsInPurse`属性来打印钱包中的硬币数量时,使用感叹号(`!`)来解包:
|
||||
|
||||
```swift
|
||||
playerOne!.winCoins(2_000)
|
||||
@ -99,14 +104,14 @@ print("The bank now only has \(Bank.coinsInBank) coins left")
|
||||
// 输出 "The bank now only has 7900 coins left"
|
||||
```
|
||||
|
||||
这里,player 已经赢得了 2,000 硬币,所以player 的钱包现在有 2,100 硬币,而bank对象只剩余 7,900 硬币。
|
||||
这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而`Bank`对象只剩余 7,900 枚硬币。
|
||||
|
||||
```swift
|
||||
playerOne = nil
|
||||
print("PlayerOne has left the game")
|
||||
// 输出 "PlayerOne has left the game"
|
||||
// 打印 "PlayerOne has left the game"
|
||||
print("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`实例,因此为了清空它占用的内存从而释放它。在这发生前,其析构器会被自动调用,从而使其硬币被返回到bank对象中。
|
||||
玩家现在已经离开了游戏。这通过将可选类型的`playerOne`变量设置为`nil`来表示,意味着“没有`Player`实例”。当这一切发生时,`playerOne`变量对`Player`实例的引用被破坏了。没有其它属性或者变量引用`Player`实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。
|
||||
|
||||
@ -8,6 +8,13 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[Channe](https://github.com/Channe)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Channe](https://github.com/Channe)
|
||||
> 校对:[shanks](http://codebuild.me),[Realank](https://github.com/Realank) ,2016-01-23
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [自动引用计数的工作机制](#how_arc_works)
|
||||
@ -19,15 +26,15 @@
|
||||
|
||||
Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
|
||||
|
||||
然而,在少数情况下,ARC 为了能帮助你管理内存,需要更多的关于你的代码之间关系的信息。本章描述了这些情况,并且为你示范怎样启用 ARC 来管理你的应用程序的内存。
|
||||
然而在少数情况下,为了能帮助你管理内存,ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。
|
||||
|
||||
> 注意:
|
||||
> 注意
|
||||
引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
|
||||
|
||||
<a name="how_arc_works"></a>
|
||||
## 自动引用计数的工作机制
|
||||
|
||||
当你每次创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。内存中会包含实例的类型信息,以及这个实例所有相关属性的值。
|
||||
当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
|
||||
|
||||
此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
|
||||
|
||||
@ -35,7 +42,7 @@ Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程
|
||||
|
||||
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
|
||||
|
||||
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢的保持住,只要强引用还在,实例是不允许被销毁的。
|
||||
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
|
||||
|
||||
<a name="arc_in_action"></a>
|
||||
## 自动引用计数实践
|
||||
@ -72,7 +79,7 @@ reference1 = Person(name: "John Appleseed")
|
||||
// prints "John Appleseed is being initialized”
|
||||
```
|
||||
|
||||
应当注意到当你调用`Person`类的构造函数的时候,"John Appleseed is being initialized”会被打印出来。由此可以确定构造函数被执行。
|
||||
应当注意到当你调用`Person`类的构造函数的时候,`“John Appleseed is being initialized”`会被打印出来。由此可以确定构造函数被执行。
|
||||
|
||||
由于`Person`类的新实例被赋值给了`reference1`变量,所以`reference1`到`Person`类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证`Person`实例被保持在内存中不被销毁。
|
||||
|
||||
@ -92,11 +99,11 @@ reference1 = nil
|
||||
reference2 = nil
|
||||
```
|
||||
|
||||
在你清楚地表明不再使用这个`Person`实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它。
|
||||
在你清楚地表明不再使用这个`Person`实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:
|
||||
|
||||
```swift
|
||||
reference3 = nil
|
||||
// prints "John Appleseed is being deinitialized"
|
||||
// 打印 “John Appleseed is being deinitialized”
|
||||
```
|
||||
|
||||
<a name="strong_reference_cycles_between_class_instances"></a>
|
||||
@ -104,11 +111,11 @@ reference3 = nil
|
||||
|
||||
在上面的例子中,ARC 会跟踪你所新创建的`Person`实例的引用数量,并且会在`Person`实例不再被需要时销毁它。
|
||||
|
||||
然而,我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
|
||||
然而,我们可能会写出一个类实例的强引用数永远不能变成`0`的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
|
||||
|
||||
你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。具体的过程在[解决类实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances)中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。
|
||||
|
||||
下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person`和`Apartment`,用来建模公寓和它其中的居民:
|
||||
下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person`和`Apartment`,用来建模公寓和它其中的居民:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
@ -130,7 +137,7 @@ class Apartment {
|
||||
|
||||
每一个`Person`实例有一个类型为`String`,名字为`name`的属性,并有一个可选的初始化为`nil`的`apartment`属性。`apartment`属性是可选的,因为一个人并不总是拥有公寓。
|
||||
|
||||
类似的,每个`Apartment`实例有一个叫`number`,类型为`Int`的属性,并有一个可选的初始化为`nil`的`tenant`属性。`tenant`属性是可选的,因为一栋公寓并不总是有居民。
|
||||
类似的,每个`Apartment`实例有一个叫`unit`,类型为`String`的属性,并有一个可选的初始化为`nil`的`tenant`属性。`tenant`属性是可选的,因为一栋公寓并不总是有居民。
|
||||
|
||||
这两个类都定义了析构函数,用以在类实例被析构的时候输出信息。这让你能够知晓`Person`和`Apartment`的实例是否像预期的那样被销毁。
|
||||
|
||||
@ -163,7 +170,7 @@ unit4A!.tenant = john
|
||||
|
||||

|
||||
|
||||
不幸的是,这两个实例关联后会产生一个循环强引用。`Person`实例现在有了一个指向`Apartment`实例的强引用,而`Apartment`实例也有了一个指向`Person`实例的强引用。因此,当你断开`john`和`unit4A`变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁:
|
||||
不幸的是,这两个实例关联后会产生一个循环强引用。`Person`实例现在有了一个指向`Apartment`实例的强引用,而`Apartment`实例也有了一个指向`Person`实例的强引用。因此,当你断开`john`和`unit4A`变量所持有的强引用时,引用计数并不会降为`0`,实例也不会被 ARC 销毁:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
@ -187,13 +194,14 @@ Swift 提供了两种办法用来解决你在使用类的属性时所遇到的
|
||||
|
||||
对于生命周期中会变为`nil`的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为`nil`的实例,使用无主引用。
|
||||
|
||||
<a name="weak_references"></a>
|
||||
### 弱引用
|
||||
|
||||
弱引用不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上`weak`关键字表明这是一个弱引用。
|
||||
|
||||
在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用,在[无主引用](#2)中有描述。在上面`Apartment`的例子中,一个公寓的生命周期中,有时是没有“居民”的,因此适合使用弱引用来解决循环强引用。
|
||||
在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用,在[无主引用](#unowned_references)中有描述。在上面`Apartment`的例子中,一个公寓的生命周期中,有时是没有“居民”的,因此适合使用弱引用来解决循环强引用。
|
||||
|
||||
> 注意:
|
||||
> 注意
|
||||
> 弱引用必须被声明为变量,表明其值能在运行时被修改。弱引用不能被声明为常量。
|
||||
|
||||
因为弱引用可以没有值,你必须将每一个弱引用声明为可选类型。在 Swift 中,推荐使用可选类型描述可能没有值的类型。
|
||||
@ -237,7 +245,7 @@ unit4A!.tenant = john
|
||||
|
||||

|
||||
|
||||
`Person`实例依然保持对`Apartment`实例的强引用,但是`Apartment`实例只是对`Person`实例的弱引用。这意味着当你断开`john`变量所保持的强引用时,再也没有指向`Person`实例的强引用了:
|
||||
`Person`实例依然保持对`Apartment`实例的强引用,但是`Apartment`实例只持有对`Person`实例的弱引用。这意味着当你断开`john`变量所保持的强引用时,再也没有指向`Person`实例的强引用了:
|
||||
|
||||

|
||||
|
||||
@ -245,7 +253,7 @@ unit4A!.tenant = john
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// prints "John Appleseed is being deinitialized"
|
||||
// 打印 “John Appleseed is being deinitialized”
|
||||
```
|
||||
|
||||
唯一剩下的指向`Apartment`实例的强引用来自于变量`unit4A`。如果你断开这个强引用,再也没有指向`Apartment`实例的强引用了:
|
||||
@ -256,27 +264,23 @@ john = nil
|
||||
|
||||
```swift
|
||||
unit4A = nil
|
||||
// prints "Apartment 4A is being deinitialized"
|
||||
// 打印 “Apartment 4A is being deinitialized”
|
||||
```
|
||||
|
||||
上面的两段代码展示了变量`john`和`unit4A`在被赋值为`nil`后,`Person`实例和`Apartment`实例的析构函数都打印出“销毁”的信息。这证明了引用循环被打破了。
|
||||
|
||||
<!--
|
||||
NOTE
|
||||
In systems that use garbage collection, weak pointers are sometimes used to implement a simple caching mechanism because objects with no strong references are deallocated only when memory pressure triggers garbage collection. However, with ARC, values are deallocated as soon as their last strong reference is removed, making weak references unsuitable for such a purpose.
|
||||
-->
|
||||
>注意:
|
||||
在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被删除,就会被立即销毁,这导致弱引用并不适合上面的用途。
|
||||
|
||||
> 注意
|
||||
在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。
|
||||
|
||||
<a name="2"></a>
|
||||
<a name="unowned_references"></a>
|
||||
### 无主引用
|
||||
|
||||
和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型(non-optional type)。你可以在声明属性或者变量时,在前面加上关键字`unowned`表示这是一个无主引用。
|
||||
|
||||
由于无主引用是非可选类型,你不需要在使用它的时候将它展开。无主引用总是可以被直接访问。不过 ARC 无法在实例被销毁后将无主引用设为`nil`,因为非可选类型的变量不允许被赋值为`nil`。
|
||||
|
||||
> 注意:
|
||||
>如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,你必须确保引用始终指向一个未销毁的实例。
|
||||
> 注意
|
||||
> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,你必须确保引用始终指向一个未销毁的实例。
|
||||
> 还需要注意的是如果你试图访问实例已经被销毁的无主引用,Swift 确保程序会直接崩溃,而不会发生无法预期的行为。所以你应当避免这样的事情发生。
|
||||
|
||||
下面的例子定义了两个类,`Customer`和`CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。
|
||||
@ -310,16 +314,16 @@ class CreditCard {
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> `CreditCard`类的`number`属性被定义为`UInt64`类型而不是`Int`类型,以确保`number`属性的存储量在32位和64位系统上都能足够容纳16位的卡号。
|
||||
> 注意
|
||||
> `CreditCard`类的`number`属性被定义为`UInt64`类型而不是`Int`类型,以确保`number`属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。
|
||||
|
||||
下面的代码片段定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为`nil`。
|
||||
下面的代码片段定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为`nil`:
|
||||
|
||||
```swift
|
||||
var john: Customer?
|
||||
```
|
||||
|
||||
现在你可以创建`Customer`类的实例,用它初始化`CreditCard`实例,并将新创建的`CreditCard`实例赋值为客户的`card`属性。
|
||||
现在你可以创建`Customer`类的实例,用它初始化`CreditCard`实例,并将新创建的`CreditCard`实例赋值为客户的`card`属性:
|
||||
|
||||
```swift
|
||||
john = Customer(name: "John Appleseed")
|
||||
@ -340,13 +344,12 @@ john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// prints "John Appleseed is being deinitialized"
|
||||
// prints "Card #1234567890123456 is being deinitialized"
|
||||
// 打印 “John Appleseed is being deinitialized”
|
||||
// 打印 ”Card #1234567890123456 is being deinitialized”
|
||||
```
|
||||
|
||||
最后的代码展示了在`john`变量被设为`nil`后`Customer`实例和`CreditCard`实例的构造函数都打印出了“销毁”的信息。
|
||||
|
||||
|
||||
<a name="unowned_references_and_implicitly_unwrapped_optional_properties"></a>
|
||||
###无主引用以及隐式解析可选属性
|
||||
|
||||
@ -384,36 +387,36 @@ class City {
|
||||
}
|
||||
```
|
||||
|
||||
为了建立两个类的依赖关系,`City`的构造函数有一个`Country`实例的参数,并且将实例保存为`country`属性。
|
||||
为了建立两个类的依赖关系,`City`的构造函数接受一个`Country`实例作为参数,并且将实例保存到`country`属性。
|
||||
|
||||
`Country`的构造函数调用了`City`的构造函数。然而,只有`Country`的实例完全初始化完后,`Country`的构造函数才能把`self`传给`City`的构造函数。(在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述)
|
||||
`Country`的构造函数调用了`City`的构造函数。然而,只有`Country`的实例完全初始化后,`Country`的构造函数才能把`self`传给`City`的构造函数。(在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述)
|
||||
|
||||
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将`Country`的`capitalCity`属性声明为隐式解析可选类型的属性。这表示像其他可选类型一样,`capitalCity`属性的默认值为`nil`,但是不需要展开它的值就能访问它。(在[隐式解析可选类型](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述)
|
||||
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将`Country`的`capitalCity`属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,`capitalCity`属性的默认值为`nil`,但是不需要展开它的值就能访问它。(在[隐式解析可选类型](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述)
|
||||
|
||||
由于`capitalCity`默认值为`nil`,一旦`Country`的实例在构造函数中给`name`属性赋值后,整个初始化过程就完成了。这代表一旦`name`属性被赋值后,`Country`的构造函数就能引用并传递隐式的`self`。`Country`的构造函数在赋值`capitalCity`时,就能将`self`作为参数传递给`City`的构造函数。
|
||||
由于`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)")
|
||||
// prints "Canada's capital city is called Ottawa"
|
||||
// 打印 “Canada's capital city is called Ottawa”
|
||||
```
|
||||
|
||||
在上面的例子中,使用隐式解析可选值的意义在于满足了两个类构造函数的需求。`capitalCity`属性在初始化完成后,能像非可选值一样使用和存取同时还避免了循环强引用。
|
||||
在上面的例子中,使用隐式解析可选值意味着满足了类的构造函数的两个构造阶段的要求。`capitalCity`属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
|
||||
|
||||
<a name="strong_reference_cycles_for_closures"></a>
|
||||
##闭包引起的循环强引用
|
||||
|
||||
前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。
|
||||
|
||||
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例。这个闭包体中可能访问了实例的某个属性,例如`self.someProperty`,或者闭包中调用了实例的某个方法,例如`self.someMethod`。这两种情况都导致了闭包 “捕获" `self`,从而产生了循环强引用。
|
||||
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如`self.someProperty`,或者闭包中调用了实例的某个方法,例如`self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。
|
||||
|
||||
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你也把一个引用赋值给了这个闭包。实质上,这跟之前的问题是一样的-两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。
|
||||
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。
|
||||
|
||||
Swift 提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表(closuer capture list)。同样的,在学习如何用闭包捕获列表破坏循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。
|
||||
Swift 提供了一种优雅的方法来解决这个问题,称之为`闭包捕获列表`(closure capture list)。同样的,在学习如何用闭包捕获列表打破循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。
|
||||
|
||||
下面的例子为你展示了当一个闭包引用了`self`后是如何产生一个循环强引用的。例子中定义了一个叫`HTMLElement`的类,用一种简单的模型表示 HTML 中的一个单独的元素:
|
||||
下面的例子为你展示了当一个闭包引用了`self`后是如何产生一个循环强引用的。例子中定义了一个叫`HTMLElement`的类,用一种简单的模型表示 HTML 文档中的一个单独的元素:
|
||||
|
||||
```swift
|
||||
class HTMLElement {
|
||||
@ -441,51 +444,49 @@ class HTMLElement {
|
||||
}
|
||||
```
|
||||
|
||||
`HTMLElement`类定义了一个`name`属性来表示这个元素的名称,例如代表段落的"p",或者代表换行的"br"。`HTMLElement`还定义了一个可选属性`text`,用来设置和展现 HTML 元素的文本。
|
||||
`HTMLElement`类定义了一个`name`属性来表示这个元素的名称,例如代表段落的`“p”`,或者代表换行的`“br”`。`HTMLElement`还定义了一个可选属性`text`,用来设置 HTML 元素呈现的文本。
|
||||
|
||||
除了上面的两个属性,`HTMLElement`还定义了一个`lazy`属性`asHTML`。这个属性引用了一个将`name`和`text`组合成 HTML 字符串片段的闭包。该属性是`Void -> String`类型,或者可以理解为“一个没有参数,返回`String`的函数”。
|
||||
|
||||
默认情况下,闭包赋值给了`asHTML`属性,这个闭包返回一个代表 HTML 标签的字符串。如果`text`值存在,该标签就包含可选值`text`;如果`text`不存在,该标签就不包含文本。对于段落元素,根据`text`是`"some text"`还是`nil`,闭包会返回"`<p>some text</p>`"或者"`<p />`"。
|
||||
默认情况下,闭包赋值给了`asHTML`属性,这个闭包返回一个代表 HTML 标签的字符串。如果`text`值存在,该标签就包含可选值`text`;如果`text`不存在,该标签就不包含文本。对于段落元素,根据`text`是`“some text”`还是`nil`,闭包会返回`"<p>some text</p>"`或者`"<p />"`。
|
||||
|
||||
可以像实例方法那样去命名、使用`asHTML`属性。然而,由于`asHTML`是闭包而不是实例方法,如果你想改变特定元素的 HTML 处理的话,可以用自定义的闭包来取代默认值。
|
||||
|
||||
<!--
|
||||
For example, the asHTML property could be set to a closure that defaults to some text if the text property is nil, in order to prevent the representation from returning an empty HTML tag:
|
||||
-->
|
||||
例如,可以将一个闭包赋值给`asHTML`属性,这个闭包能在文本属性是 nil 时用默认文本,这是为了避免返回一个空的 `HTML` 标签:
|
||||
```swift
|
||||
let heading = HTMLElement(name: "h1")
|
||||
let defaultText = "some default text"
|
||||
heading.asHTML = {
|
||||
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
|
||||
}
|
||||
print(heading.asHTML())
|
||||
// prints "<h1>some default text</h1>"
|
||||
```
|
||||
可以像实例方法那样去命名、使用`asHTML`属性。然而,由于`asHTML`是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。
|
||||
|
||||
> 注意:
|
||||
`asHTML`声明为`lazy`属性,因为只有当元素确实需要处理为HTML输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问`lazy`属性。
|
||||
例如,可以将一个闭包赋值给`asHTML`属性,这个闭包能在`text`属性是`nil`时使用默认文本,这是为了避免返回一个空的 HTML 标签:
|
||||
|
||||
`HTMLElement`类只提供一个构造函数,通过`name`和`text`(如果有的话)参数来初始化一个元素。该类也定义了一个析构函数,当`HTMLElement`实例被销毁时,打印一条消息。
|
||||
```swift
|
||||
let heading = HTMLElement(name: "h1")
|
||||
let defaultText = "some default text"
|
||||
heading.asHTML = {
|
||||
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
|
||||
}
|
||||
print(heading.asHTML())
|
||||
// 打印 “<h1>some default text</h1>”
|
||||
```
|
||||
|
||||
下面的代码展示了如何用`HTMLElement`类创建实例并打印消息。
|
||||
> 注意
|
||||
`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())
|
||||
// prints"hello, world"
|
||||
// 打印 “<p>hello, world</p>”
|
||||
```
|
||||
|
||||
>注意:
|
||||
上面的`paragraph`变量定义为`可选HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。
|
||||
> 注意
|
||||
上面的`paragraph`变量定义为可选类型的`HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。
|
||||
|
||||
不幸的是,上面写的`HTMLElement`类产生了类实例和`asHTML`默认值的闭包之间的循环强引用。循环强引用如下图所示:
|
||||
不幸的是,上面写的`HTMLElement`类产生了类实例和作为`asHTML`默认值的闭包之间的循环强引用。循环强引用如下图所示:
|
||||
|
||||

|
||||
|
||||
实例的`asHTML`属性持有闭包的强引用。但是,闭包在其闭包体内使用了`self`(引用了`self.name`和`self.text`),因此闭包捕获了`self`,这意味着闭包又反过来持有了`HTMLElement`实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。
|
||||
|
||||
>注意:
|
||||
> 注意
|
||||
虽然闭包多次使用了`self`,它只捕获`HTMLElement`实例的一个强引用。
|
||||
|
||||
如果设置`paragraph`变量为`nil`,打破它持有的`HTMLElement`实例的强引用,`HTMLElement`实例和它的闭包都不会被销毁,也是因为循环强引用:
|
||||
@ -494,26 +495,27 @@ print(paragraph!.asHTML())
|
||||
paragraph = nil
|
||||
```
|
||||
|
||||
注意`HTMLElementdeinitializer`中的消息并没有被打印,证明了`HTMLElement`实例并没有被销毁。
|
||||
注意,`HTMLElement`的析构函数中的消息并没有被打印,证明了`HTMLElement`实例并没有被销毁。
|
||||
|
||||
<a name="resolving_strong_reference_cycles_for_closures"></a>
|
||||
##解决闭包引起的循环强引用
|
||||
|
||||
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
|
||||
|
||||
>注意:
|
||||
> 注意
|
||||
Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod()`(而不只是`someProperty`或`someMethod()`)。这提醒你可能会一不小心就捕获了`self`。
|
||||
|
||||
<a name="defining_a_capture_list"></a>
|
||||
###定义捕获列表
|
||||
|
||||
捕获列表中的每一项都由一对元素组成,一个元素是`weak`或`unowned`关键字,另一个元素是类实例的引用(如`self`)或初始化过的变量(如`delegate = self.delegate!`)。这些项在方括号中用逗号分开。
|
||||
捕获列表中的每一项都由一对元素组成,一个元素是`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
|
||||
// closure body goes here
|
||||
// 这里是闭包的函数体
|
||||
}
|
||||
```
|
||||
|
||||
@ -522,17 +524,18 @@ lazy var someClosure: (Int, String) -> String = {
|
||||
```swift
|
||||
lazy var someClosure: Void -> String = {
|
||||
[unowned self, weak delegate = self.delegate!] in
|
||||
// closure body goes here
|
||||
// 这里是闭包的函数体
|
||||
}
|
||||
```
|
||||
|
||||
<a name="weak_and_unowned_references"></a>
|
||||
###弱引用和无主引用
|
||||
|
||||
在闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
|
||||
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为`无主引用`。
|
||||
|
||||
相反的,在被捕获的引用可能会变为`nil`时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为`nil`。这使我们可以在闭包体内检查它们是否存在。
|
||||
相反的,在被捕获的引用可能会变为`nil`时,将闭包内的捕获定义为`弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为`nil`。这使我们可以在闭包体内检查它们是否存在。
|
||||
|
||||
>注意:
|
||||
> 注意
|
||||
如果被捕获的引用绝对不会变为`nil`,应该用无主引用,而不是弱引用。
|
||||
|
||||
前面的`HTMLElement`例子中,无主引用是正确的解决循环强引用的方法。这样编写`HTMLElement`类来避免循环强引用:
|
||||
@ -564,23 +567,25 @@ class HTMLElement {
|
||||
}
|
||||
```
|
||||
|
||||
上面的`HTMLElement`实现和之前的实现一致,除了在`asHTML`闭包中多了一个捕获列表。这里,捕获列表是`[unowned self]`,表示“用无主引用而不是强引用来捕获`self`”。
|
||||
上面的`HTMLElement`实现和之前的实现一致,除了在`asHTML`闭包中多了一个捕获列表。这里,捕获列表是`[unowned self]`,表示“将`self`捕获为无主引用而不是强引用”。
|
||||
|
||||
和之前一样,我们可以创建并打印`HTMLElement`实例:
|
||||
|
||||
```swift
|
||||
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
|
||||
print(paragraph!.asHTML())
|
||||
// prints "<p>hello, world</p>"
|
||||
// 打印 “<p>hello, world</p>”
|
||||
```
|
||||
|
||||
使用捕获列表后引用关系如下图所示:
|
||||
|
||||

|
||||
|
||||
这一次,闭包以无主引用的形式捕获`self`,并不会持有`HTMLElement`实例的强引用。如果将`paragraph`赋值为`nil`,`HTMLElement`实例将会被销毁,并能看到它的析构函数打印出的消息。
|
||||
这一次,闭包以无主引用的形式捕获`self`,并不会持有`HTMLElement`实例的强引用。如果将`paragraph`赋值为`nil`,`HTMLElement`实例将会被销毁,并能看到它的析构函数打印出的消息:
|
||||
|
||||
```swift
|
||||
paragraph = nil
|
||||
// prints "p is being deinitialized"
|
||||
// 打印 “p is being deinitialized”
|
||||
```
|
||||
|
||||
你可以查看[捕获列表](../chapter3/04_Expressions.html)章节,获取更多关于捕获列表的信息。
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# 可空链式调用(Optional Chaining)
|
||||
# 可选链式调用(Optional Chaining)
|
||||
|
||||
-----------------
|
||||
|
||||
@ -9,23 +9,39 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[lyojo](https://github.com/lyojo)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-31
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-15
|
||||
|
||||
可空链式调用(Optional Chaining)是一种可以请求和调用属性、方法及下标的过程,它的可空性体现于请求或调用的目标当前可能为空(nil)。如果可空的目标有值,那么调用就会成功;如果选择的目标为空(nil),那么这种调用将返回空(nil)。多个连续的调用可以被链接在一起形成一个调用链,如果其中任何一个节点为空(nil)将导致整个链调用失败。
|
||||
本页包含内容:
|
||||
|
||||
>
|
||||
注意:
|
||||
Swift 的可空链式调用和 Objective-C 中的消息为空有些相像,但是 Swift 可以使用在任意类型中,并且能够检查调用是否成功。
|
||||
- [使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)
|
||||
- [为可选链式调用定义模型类](#defining_model_classes_for_optional_chaining)
|
||||
- [通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)
|
||||
- [通过可选链式调用调用方法](#calling_methods_through_optional_chaining)
|
||||
- [通过可选链式调用访问下标](#accessing_subscripts_through_optional_chaining)
|
||||
- [连接多层可选链式调用](#linking_multiple_levels_of_chaining)
|
||||
- [在方法的可选返回值上进行可选链式调用](#chaining_on_methods_with_optional_return_values)
|
||||
|
||||
可选链式调用(Optional Chaining)是一种可以在当前值可能为`nil`的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是`nil`,那么调用将返回`nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为`nil`,整个调用链都会失败,即返回`nil`。
|
||||
|
||||
> 注意
|
||||
Swift 的可选链式调用和 Objective-C 中向`nil`发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
|
||||
|
||||
<a name="optional_chaining_as_an_alternative_to_forced_unwrapping"></a>
|
||||
##使用可空链式调用来强制展开
|
||||
通过在想调用非空的属性、方法、或下标的可空值(optional value)后面放一个问号,可以定义一个可空链。这一点很像在可空值后面放一个叹号(!)来强制展开其中值。它们的主要的区别在于当可空值为空时可空链式只是调用失败,然而强制展开将会触发运行时错误。
|
||||
## 使用可选链式调用代替强制展开
|
||||
|
||||
为了反映可空链式调用可以在空对象(nil)上调用,不论这个调用的属性、方法、下标等返回的值是不是可空值,它的返回结果都是一个可空值。你可以利用这个返回值来判断你的可空链式调用是否调用成功,如果调用有返回值则说明调用成功,返回`nil`则说明调用失败。
|
||||
通过在想调用的属性、方法、或下标的可选值(optional value)后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。
|
||||
|
||||
特别地,可空链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可空类型值。当可空链式调用成功时,一个本应该返回`Int`的类型的结果将会返回`Int?`类型。
|
||||
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回`nil`则说明调用失败。
|
||||
|
||||
下面几段代码将解释可空链式调用和强制展开的不同。
|
||||
首先定义两个类`Person`和`Residence`。
|
||||
特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是`Int`类型,则会变为`Int?`类型。
|
||||
|
||||
下面几段代码将解释可选链式调用和强制展开的不同。
|
||||
|
||||
首先定义两个类`Person`和`Residence`:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
@ -37,24 +53,24 @@ class Residence {
|
||||
}
|
||||
```
|
||||
|
||||
`Residence`有一个`Int`类型的属性`numberOfRooms`,其默认值为1。`Person`具有一个可空的`residence`属性,其类型为`Residence?`。
|
||||
|
||||
如果创建一个新的`Person`实例,因为它的`residence`属性是可空的,`john`属性将初始化为`nil`:
|
||||
`Residence`有一个`Int`类型的属性`numberOfRooms`,其默认值为`1`。`Person`具有一个可选的`residence`属性,其类型为`Residence?`。
|
||||
|
||||
假如你创建了一个新的`Person`实例,它的`residence`属性由于是是可选型而将初始化为`nil`,在下面的代码中,`john`有一个值为`nil`的`residence`属性:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
```
|
||||
|
||||
如果使用叹号(!)强制展开获得这个`john`的`residence`属性中的`numberOfRooms`值,会触发运行时错误,因为这时没有可以展开的`residence`:
|
||||
如果使用叹号(`!`)强制展开获得这个`john`的`residence`属性中的`numberOfRooms`值,会触发运行时错误,因为这时`residence`没有可以展开的值:
|
||||
|
||||
```swift
|
||||
let roomCount = john.residence!.numberOfRooms
|
||||
// this triggers a runtime error
|
||||
// 这会引发运行时错误
|
||||
```
|
||||
|
||||
`john.residence`非空的时候,上面的调用成功,并且把`roomCount`设置为`Int`类型的房间数量。正如上面说到的,当`residence`为空的时候上面这段代码会触发运行时错误。
|
||||
`john.residence`为非`nil`值的时候,上面的调用会成功,并且把`roomCount`设置为`Int`类型的房间数量。正如上面提到的,当`residence`为`nil`的时候上面这段代码会触发运行时错误。
|
||||
|
||||
可空链式调用提供了一种另一种访问`numberOfRooms`的方法,使用问号(?)来代替原来叹号(!)的位置:
|
||||
可选链式调用提供了另一种访问`numberOfRooms`的方式,使用问号(`?`)来替代原来的叹号(`!`):
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
@ -62,22 +78,22 @@ if let roomCount = john.residence?.numberOfRooms {
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// prints "Unable to retrieve the number of rooms."
|
||||
// 打印 “Unable to retrieve the number of rooms.”
|
||||
```
|
||||
|
||||
在`residence`后面添加问号之后,Swift就会在`residence`不为空的情况下访问`numberOfRooms`。
|
||||
在`residence`后面添加问号之后,Swift 就会在`residence`不为`nil`的情况下访问`numberOfRooms`。
|
||||
|
||||
因为访问`numberOfRooms`有可能失败,可空链式调用会返回`Int?`类型,或称为“可空的Int”。如上例所示,当`residence`为`nil`的时候,可空的`Int`将会为`nil`,表明无法访问`numberOfRooms`。
|
||||
因为访问`numberOfRooms`有可能失败,可选链式调用会返回`Int?`类型,或称为“可选的 `Int`”。如上例所示,当`residence`为`nil`的时候,可选的`Int`将会为`nil`,表明无法访问`numberOfRooms`。访问成功时,可选的`Int`值会通过可选绑定展开,并赋值给非可选类型的`roomCount`常量。
|
||||
|
||||
要注意的是,即使`numberOfRooms`是不可空的`Int`时,这一点也成立。只要是通过可空链式调用就意味着最后`numberOfRooms`返回一个`Int?`而不是`Int`。
|
||||
要注意的是,即使`numberOfRooms`是非可选的`Int`时,这一点也成立。只要使用可选链式调用就意味着`numberOfRooms`会返回一个`Int?`而不是`Int`。
|
||||
|
||||
通过赋给`john.residence`一个`Residence`的实例变量:
|
||||
可以将一个`Residence`的实例赋给`john.residence`,这样它就不再是`nil`了:
|
||||
|
||||
```swift
|
||||
john.residence = Residence()
|
||||
```
|
||||
|
||||
这样`john.residence`不为`nil`了。现在就可以正常访问`john.residence.numberOfRooms`,其值为默认的1,类型为`Int?`:
|
||||
`john.residence`现在包含一个实际的`Residence`实例,而不再是`nil`。如果你试图使用先前的可选链式调用访问`numberOfRooms`,它现在将返回值为`1`的`Int?`类型的值:
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
@ -85,15 +101,17 @@ if let roomCount = john.residence?.numberOfRooms {
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// prints "John's residence has 1 room(s)."
|
||||
// 打印 “John's residence has 1 room(s).”
|
||||
```
|
||||
|
||||
##为可空链式调用定义模型类
|
||||
通过使用可空链式调用可以调用多层属性,方法,和下标。这样可以通过各种模型向下访问各种子属性。并且判断能否访问子属性的属性,方法或下标。
|
||||
<a name="defining_model_classes_for_optional_chaining"></a>
|
||||
## 为可选链式调用定义模型类
|
||||
|
||||
下面这段代码定义了四个模型类,这些例子包括多层可空链式调用。为了方便说明,在`Person`和`Residence`的基础上增加了`Room`和`Address`,以及相关的属性,方法以及下标。
|
||||
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。
|
||||
|
||||
Person类定义基本保持不变:
|
||||
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在`Person`和`Residence`的基础上增加了`Room`类和`Address`类,以及相关的属性、方法以及下标。
|
||||
|
||||
`Person`类的定义基本保持不变:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
@ -101,7 +119,7 @@ class Person {
|
||||
}
|
||||
```
|
||||
|
||||
`Residence`类比之前复杂些,增加了一个`Room`类型的空数组`room`:
|
||||
`Residence`类比之前复杂些,增加了一个名为`rooms`的变量属性,该属性被初始化为`[Room]`类型的空数组:
|
||||
|
||||
```swift
|
||||
class Residence {
|
||||
@ -124,9 +142,15 @@ class Residence {
|
||||
}
|
||||
```
|
||||
|
||||
现在`Residence`有了一个存储`Room`类型的数组,`numberOfRooms`属性需要计算,而不是作为单纯的变量。计算后的`numberOfRooms`返回`rooms`数组的`count`属性值。现在的`Residence`还提供访问`rooms`数组的快捷方式, 通过可读写的下标来访问指定位置的数组元素。此外,还提供`printNumberOfRooms`方法,这个方法的作用就是输出这个房子中房间的数量。最后,`Residence`定义了一个可空属性`address`,其类型为`Address?`。`Address`类的定义在下面会说明。
|
||||
现在`Residence`有了一个存储`Room`实例的数组,`numberOfRooms`属性被实现为计算型属性,而不是存储型属性。`numberOfRooms`属性简单地返回`rooms`数组的`count`属性的值。
|
||||
|
||||
类`Room`是一个简单类,只包含一个属性`name`,以及一个初始化函数:
|
||||
`Residence`还提供了访问`rooms`数组的快捷方式,即提供可读写的下标来访问`rooms`数组中指定位置的元素。
|
||||
|
||||
此外,`Residence`还提供了`printNumberOfRooms()`方法,这个方法的作用是打印`numberOfRooms`的值。
|
||||
|
||||
最后,`Residence`还定义了一个可选属性`address`,其类型为`Address?`。`Address`类的定义在下面会说明。
|
||||
|
||||
`Room`类是一个简单类,其实例被存储在`rooms`数组中。该类只包含一个属性`name`,以及一个用于将该属性设置为适当的房间名的初始化函数:
|
||||
|
||||
```swift
|
||||
class Room {
|
||||
@ -135,7 +159,7 @@ class Room {
|
||||
}
|
||||
```
|
||||
|
||||
最后一个类是`Address`,这个类有三个`String?`类型的可空属性。`buildingName`以及`buildingNumber`属性表示建筑的名称和号码,用来表示某个特定的建筑。第三个属性表示建筑所在街道的名称:
|
||||
最后一个类是`Address`,这个类有三个`String?`类型的可选属性。`buildingName`以及`buildingNumber`属性分别表示某个大厦的名称和号码,第三个属性`street`表示大厦所在街道的名称:
|
||||
|
||||
```swift
|
||||
class Address {
|
||||
@ -145,8 +169,8 @@ class Address {
|
||||
func buildingIdentifier() -> String? {
|
||||
if buildingName != nil {
|
||||
return buildingName
|
||||
} else if buildingNumber != nil {
|
||||
return buildingNumber
|
||||
} else if buildingNumber != nil && street != nil {
|
||||
return "\(buildingNumber) \(street)"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -154,12 +178,14 @@ class Address {
|
||||
}
|
||||
```
|
||||
|
||||
类`Address`提供`buildingIdentifier()`方法,返回值为`String?`。 如果`buildingName`不为空则返回`buildingName`, 如果`buildingNumber`不为空则返回`buildingNumber`。如果这两个属性都为空则返回`nil`。
|
||||
`Address`类提供了`buildingIdentifier()`方法,返回值为`String?`。 如果`buildingName`有值则返回`buildingName`。或者,如果`buildingNumber`和`street`均有值则返回`buildingNumber`。否则,返回`nil`。
|
||||
|
||||
##通过可空链式调用访问属性
|
||||
正如[使用可空链式调用来强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可空链式调用访问属性的可空值,并且判断访问是否成功。
|
||||
<a name="accessing_properties_through_optional_chaining"></a>
|
||||
## 通过可选链式调用访问属性
|
||||
|
||||
下面的代码创建了一个`Person`实例,然后访问`numberOfRooms`属性:
|
||||
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
||||
|
||||
下面的代码创建了一个`Person`实例,然后像之前一样,尝试访问`numberOfRooms`属性:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
@ -168,12 +194,12 @@ if let roomCount = john.residence?.numberOfRooms {
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// prints "Unable to retrieve the number of rooms."
|
||||
// 打印 “Unable to retrieve the number of rooms.”
|
||||
```
|
||||
|
||||
因为`john.residence`为`nil`,所以毫无疑问这个可空链式调用失败。
|
||||
因为`john.residence`为`nil`,所以这个可选链式调用依旧会像先前一样失败。
|
||||
|
||||
通过可空链式调用来设定属性值:
|
||||
还可以通过可选链式调用来设置属性值:
|
||||
|
||||
```swift
|
||||
let someAddress = Address()
|
||||
@ -182,11 +208,31 @@ someAddress.street = "Acacia Road"
|
||||
john.residence?.address = someAddress
|
||||
```
|
||||
|
||||
在这个例子中,通过`john.residence`来设定`address`属性也是不行的,因为`john.residence`为`nil`。
|
||||
在这个例子中,通过`john.residence`来设定`address`属性也会失败,因为`john.residence`当前为`nil`。
|
||||
|
||||
##通过可空链式调用来调用方法
|
||||
可以通过可空链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
|
||||
`Residence`中的`printNumberOfRooms()`方法输出当前的`numberOfRooms`值:
|
||||
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建`Address`实例,然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行。
|
||||
|
||||
```swift
|
||||
func createAddress() -> Address {
|
||||
print("Function was called.")
|
||||
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
|
||||
return someAddress
|
||||
}
|
||||
john.residence?.address = createAddress()
|
||||
```
|
||||
|
||||
没有任何打印消息,可以看出`createAddress()`函数并未被执行。
|
||||
|
||||
<a name="calling_methods_through_optional_chaining"></a>
|
||||
## 通过可选链式调用调用方法
|
||||
|
||||
可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
|
||||
|
||||
`Residence`类中的`printNumberOfRooms()`方法打印当前的`numberOfRooms`值,如下所示:
|
||||
|
||||
```swift
|
||||
func printNumberOfRooms() {
|
||||
@ -194,9 +240,9 @@ func printNumberOfRooms() {
|
||||
}
|
||||
```
|
||||
|
||||
这个方法没有返回值。但是没有返回值的方法隐式返回`Void`类型,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回()或者空的元组。
|
||||
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型`Void`,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回`()`,或者说空的元组。
|
||||
|
||||
如果在可空值上通过可空链式调用来调用这个方法,这个方法的返回类型为`Void?`,而不是`Void`,因为通过可空链式调用得到的返回值都是可空的。这样我们就可以使用`if`语句来判断能否成功调用`printNumberOfRooms()`方法,即使方法本身没有定义返回值。通过返回值是否为`nil`可以判断调用是否成功:
|
||||
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是`Void?`,而不是`Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用`if`语句来判断能否成功调用`printNumberOfRooms()`方法,即使方法本身没有定义返回值。通过判断返回值是否为`nil`可以判断调用是否成功:
|
||||
|
||||
```swift
|
||||
if john.residence?.printNumberOfRooms() != nil {
|
||||
@ -204,10 +250,10 @@ if john.residence?.printNumberOfRooms() != nil {
|
||||
} else {
|
||||
print("It was not possible to print the number of rooms.")
|
||||
}
|
||||
// prints "It was not possible to print the number of rooms."
|
||||
// 打印 “It was not possible to print the number of rooms.”
|
||||
```
|
||||
|
||||
同样的,可以判断通过可空链式调用来给属性赋值是否成功。在上面的例子中,我们尝试给`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 {
|
||||
@ -215,17 +261,18 @@ if (john.residence?.address = someAddress) != nil {
|
||||
} else {
|
||||
print("It was not possible to set the address.")
|
||||
}
|
||||
// prints "It was not possible to set the address."
|
||||
// 打印 “It was not possible to set the address.”
|
||||
```
|
||||
|
||||
<a name="accessing_subscripts_through_optional_chaining"></a>
|
||||
## 通过可选链式调用访问下标
|
||||
|
||||
##通过可空链式调用来访问下标
|
||||
通过可空链式调用,我们可以用下标来对可空值进行读取或写入,并且判断下标调用是否成功。
|
||||
>
|
||||
注意:
|
||||
当通过可空链式调用访问可空值的下标的时候,应该将问号放在下标方括号的前面而不是后面。可空链式调用的问号一般直接跟在可空表达式的后面。
|
||||
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
|
||||
|
||||
下面这个例子用下标访问`john.residence`中`rooms`数组中第一个房间的名称,因为`john.residence`为`nil`,所以下标调用毫无疑问失败了:
|
||||
> 注意
|
||||
通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
|
||||
|
||||
下面这个例子用下标访问`john.residence`属性存储的`Residence`实例的`rooms`数组中的第一个房间的名称,因为`john.residence`为`nil`,所以下标调用失败了:
|
||||
|
||||
```swift
|
||||
if let firstRoomName = john.residence?[0].name {
|
||||
@ -233,22 +280,20 @@ if let firstRoomName = john.residence?[0].name {
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// prints "Unable to retrieve the first room name."
|
||||
// 打印 “Unable to retrieve the first room name.”
|
||||
```
|
||||
|
||||
在这个例子中,问号直接放在`john.residence`的后面,并且在方括号的前面,因为`john.residence`是可选值。
|
||||
|
||||
在这个例子中,问号直接放在`john.residence`的后面,并且在方括号的前面,因为`john.residence`是可空值。
|
||||
|
||||
类似的,可以通过下标,用可空链式调用来赋值:
|
||||
类似的,可以通过下标,用可选链式调用来赋值:
|
||||
|
||||
```swift
|
||||
john.residence?[0] = Room(name: "Bathroom")
|
||||
```
|
||||
|
||||
|
||||
这次赋值同样会失败,因为`residence`目前是`nil`。
|
||||
|
||||
如果你创建一个`Residence`实例,添加一些`Room`实例并赋值给`john.residence`,那就可以通过可选链和下标来访问数组中的元素:
|
||||
如果你创建一个`Residence`实例,并为其`rooms`数组添加一些`Room`实例,然后将`Residence`实例赋值给`john.residence`,那就可以通过可选链和下标来访问数组中的元素:
|
||||
|
||||
```swift
|
||||
let johnsHouse = Residence()
|
||||
@ -261,36 +306,40 @@ if let firstRoomName = john.residence?[0].name {
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// prints "The first room name is Living Room."
|
||||
// 打印 “The first room name is Living Room.”
|
||||
```
|
||||
|
||||
##访问可空类型的下标:
|
||||
如果下标返回可空类型值,比如Swift中`Dictionary`的`key`下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值:
|
||||
<a name="accessing_subscripts_of_optional_type"></a>
|
||||
### 访问可选类型的下标
|
||||
|
||||
如果下标返回可选类型值,比如 Swift 中`Dictionary`类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
|
||||
|
||||
```swift
|
||||
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
|
||||
testScores["Dave"]?[0] = 91
|
||||
testScores["Bev"]?[0]++
|
||||
testScores["Bev"]?[0] += 1
|
||||
testScores["Brian"]?[0] = 72
|
||||
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
|
||||
// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81]
|
||||
```
|
||||
|
||||
上面的例子中定义了一个`testScores`数组,包含了两个键值对, 把`String`类型的`key`映射到一个整形数组。这个例子用可空链式调用把“Dave”数组中第一个元素设为91,把”Bev”数组的第一个元素+1,然后尝试把”Brian”数组中的第一个元素设为72。前两个调用是成功的,因为这两个`key`存在。但是key“Brian”在字典中不存在,所以第三个调用失败。
|
||||
上面的例子中定义了一个`testScores`数组,包含了两个键值对,把`String`类型的键映射到一个`Int`值的数组。这个例子用可选链式调用把`"Dave"`数组中第一个元素设为`91`,把`"Bev"`数组的第一个元素`+1`,然后尝试把`"Brian"`数组中的第一个元素设为`72`。前两个调用成功,因为`testScores`字典中包含`"Dave"`和`"Bev"`这两个键。但是`testScores`字典中没有`"Brian"`这个键,所以第三个调用失败。
|
||||
|
||||
##多层链接
|
||||
可以通过多个链接多个可空链式调用来向下访问属性,方法以及下标。但是多层可空链式调用不会添加返回值的可空性。
|
||||
<a name="linking_multiple_levels_of_chaining"></a>
|
||||
## 连接多层可选链式调用
|
||||
|
||||
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。
|
||||
|
||||
也就是说:
|
||||
|
||||
+ 如果你访问的值不是可空的,通过可空链式调用将会放回可空值。
|
||||
+ 如果你访问的值已经是可空的,通过可空链式调用不会变得“更”可空。
|
||||
+ 如果你访问的值不是可选的,可选链式调用将会返回可选值。
|
||||
+ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。
|
||||
|
||||
因此:
|
||||
|
||||
+ 通过可空链式调用访问一个`Int`值,将会返回`Int?`,不过进行了多少次可空链式调用。
|
||||
+ 类似的,通过可空链式调用访问`Int?`值,并不会变得更加可空。
|
||||
+ 通过可选链式调用访问一个`Int`值,将会返回`Int?`,无论使用了多少层可选链式调用。
|
||||
+ 类似的,通过可选链式调用访问`Int?`值,依旧会返回`Int?`值,并不会返回`Int??`。
|
||||
|
||||
下面的例子访问`john`中的`residence`中的`address`中的`street`属性。这里使用了两层可空链式调用,`residence`以及`address`,这两个都是可空值。
|
||||
下面的例子尝试访问`john`中的`residence`属性中的`address`属性中的`street`属性。这里使用了两层可选链式调用,`residence`以及`address`都是可选值:
|
||||
|
||||
```swift
|
||||
if let johnsStreet = john.residence?.address?.street {
|
||||
@ -298,14 +347,14 @@ if let johnsStreet = john.residence?.address?.street {
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// prints "Unable to retrieve the address."
|
||||
// 打印 “Unable to retrieve the address.”
|
||||
```
|
||||
|
||||
`john.residence`包含`Residence`实例,但是`john.residence.address`为`nil`。因此,不能访问`john.residence?.address?.street`。
|
||||
`john.residence`现在包含一个有效的`Residence`实例。然而,`john.residence.address`的值当前为`nil`。因此,调用`john.residence?.address?.street`会失败。
|
||||
|
||||
需要注意的是,上面的例子中,`street`的属性为`String?`。`john.residence?.address?.street`的返回值也依然是`String?`,即使已经进行了两次可空的链式调用。
|
||||
需要注意的是,上面的例子中,`street`的属性为`String?`。`john.residence?.address?.street`的返回值也依然是`String?`,即使已经使用了两层可选链式调用。
|
||||
|
||||
如果把`john.residence.address`指向一个实例,并且为`address`中的`street`属性赋值,我们就能过通过可空链式调用来访问`street`属性。
|
||||
如果为`john.residence.address`赋值一个`Address`实例,并且为`address`中的`street`属性设置一个有效值,我们就能过通过可选链式调用来访问`street`属性:
|
||||
|
||||
```swift
|
||||
let johnsAddress = Address()
|
||||
@ -318,24 +367,26 @@ if let johnsStreet = john.residence?.address?.street {
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// prints "John's street name is Laurel Street."
|
||||
// 打印 “John's street name is Laurel Street.”
|
||||
```
|
||||
|
||||
在上面的例子中,因为`john.residence`是一个可用的`Residence`实例,所以对`john.residence`的`address`属性赋值成功。
|
||||
在上面的例子中,因为`john.residence`包含一个有效的`Residence`实例,所以对`john.residence`的`address`属性赋值将会成功。
|
||||
|
||||
##对返回可空值的函数进行链接
|
||||
上面的例子说明了如何通过可空链式调用来获取可空属性值。我们还可以通过可空链式调用来调用返回可空值的方法,并且可以继续对可空值进行链接。
|
||||
<a name="chaining_on_methods_with_optional_return_values"></a>
|
||||
## 在方法的可选返回值上进行可选链式调用
|
||||
|
||||
在下面的例子中,通过可空链式调用来调用`Address`的`buildingIdentifier()`方法。这个方法返回`String?`类型。正如上面所说,通过可空链式调用的方法的最终返回值还是`String?`:
|
||||
上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。
|
||||
|
||||
在下面的例子中,通过可选链式调用来调用`Address`的`buildingIdentifier()`方法。这个方法返回`String?`类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是`String?`类型:
|
||||
|
||||
```swift
|
||||
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
||||
print("John's building identifier is \(buildingIdentifier).")
|
||||
}
|
||||
// prints "John's building identifier is The Larches."
|
||||
// 打印 “John's building identifier is The Larches.”
|
||||
```
|
||||
|
||||
如果要进一步对方法的返回值进行可空链式调用,在方法`buildingIdentifier()`的圆括号后面加上问号:
|
||||
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
|
||||
|
||||
```swift
|
||||
if let beginsWithThe =
|
||||
@ -346,9 +397,8 @@ if let beginsWithThe =
|
||||
print("John's building identifier does not begin with \"The\".")
|
||||
}
|
||||
}
|
||||
// prints "John's building identifier begins with "The"."
|
||||
// 打印 “John's building identifier begins with "The".”
|
||||
```
|
||||
|
||||
>
|
||||
注意:
|
||||
在上面的例子中在,在方法的圆括号后面加上问号是因为`buildingIdentifier()`的返回值是可空值,而不是方法本身是可空的。
|
||||
> 注意
|
||||
在上面的例子中,在方法的圆括号后面加上问号是因为你要在`buildingIdentifier()`方法的可选返回值上进行可选链式调用,而不是方法本身。
|
||||
|
||||
@ -3,63 +3,75 @@
|
||||
|
||||
> 2.1
|
||||
> 翻译+校对:[lyojo](https://github.com/lyojo) [ray16897188](https://github.com/ray16897188) 2015-10-23
|
||||
> 校对:[shanks](http://codebuild.me) 2015-10-24
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-15
|
||||
|
||||
> 定稿:[shanks](http://codebuild.me) 2015-10-24
|
||||
本页包含内容:
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [表示并抛出错误](#representing_and_throwing_errors)
|
||||
- [处理错误](#handling_errors)
|
||||
- [表示并抛出错误](#representing_and_throwing_errors)
|
||||
- [处理错误](#handling_errors)
|
||||
- [指定清理操作](#specifying_cleanup_actions)
|
||||
|
||||
*错误处理(Error handling)*是响应错误以及从错误中恢复的过程。swift提供了在运行对可恢复错误抛出,捕获,传送和操作的高级支持。
|
||||
*错误处理(Error handling)*是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一流支持。
|
||||
|
||||
某些操作并不能总是保证执行所有代码都可以执行或总会产出有用的结果。可选类型用来表示值可能为空,但是当执行失败的时候,通常要去了解此次失败是由何引起,你的代码就可以做出与之相应的反应。
|
||||
某些操作无法保证总是执行完所有代码或总是生成有用的结果。可选类型可用来表示值缺失,但是当某个操作失败时,最好能得知失败的原因,从而可以作出相应的应对。
|
||||
|
||||
举个例子,假如有个从磁盘上的某个文件读取以并做数据处理的任务,该任务会有多种可能失败的情形,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些错误情形可以让程序解决并处理一部分错误,然后把它解决不了的错误报告给用户。
|
||||
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。
|
||||
|
||||
>
|
||||
注意:
|
||||
Swift中的错误处理涉及到错误处理模式,这会用到Cocoa和Objective-C中的`NSError`。关于这个class的更多信息请参见:[ Using Swift with Cocoa and Objective-C (Swift 2.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)中的[错误处理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。
|
||||
> 注意
|
||||
Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的`NSError`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 2.2)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的[错误处理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。
|
||||
|
||||
<a name="representing_and_throwing_errors"></a>
|
||||
##表示并抛出错误
|
||||
在Swift中,错误用遵循`ErrorType`协议类型的值来表示。这个空协议表示一种可以用做错误处理的类型。
|
||||
Swift的枚举类型尤为适合塑造一组相关的错误情形(error conditions),枚举的关联值(assiciated values)还可以提供额外信息,表示某些错误情形的性质。比如你可以这样表示在一个游戏中操作自动贩卖机会出现的错误情形:
|
||||
|
||||
在 Swift 中,错误用符合`ErrorType`协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
|
||||
|
||||
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态:
|
||||
|
||||
```swift
|
||||
enum VendingMachineError: ErrorType {
|
||||
case InvalidSelection //选择无效
|
||||
case InsufficientFunds(coinsNeeded: Int) //金额不足
|
||||
case OutOfStock //缺货
|
||||
case InvalidSelection //选择无效
|
||||
case InsufficientFunds(coinsNeeded: Int) //金额不足
|
||||
case OutOfStock //缺货
|
||||
}
|
||||
```
|
||||
抛出一个错误会让你对所发生的意外情况做出提示,表示正常的执行流程不能被执行下去。抛出错误使用`throws`关键字。比如下面的代码抛出一个错误,提示贩卖机还需要5个硬币:
|
||||
|
||||
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用`throw`关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要`5`个硬币:
|
||||
|
||||
```swift
|
||||
throw VendingMachineError.InsufficientFunds(coinsNeeded: 5)
|
||||
```
|
||||
|
||||
<a name="handling_errors"></a>
|
||||
##处理错误
|
||||
某个错误被抛出时,那个地方的某部分代码必须要负责处理这个错误 - 比如纠正这个问题、尝试另外一种方式、或是给用户提示这个错误。
|
||||
Swift中有 4 种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用`do-catch`语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面相应小节都有描述。
|
||||
当一个函数抛出一个错误时,你的程序流程会发生改变,所以关键是你要迅速的标识出代码中抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数,方法,或者构造器之前,加上`try`关键字 - 或者`try?`或者`try!`的变体。这些关键字在下面的小节中有具体讲解。
|
||||
>注意
|
||||
>Swift中的错误处理和其他语言中的用`try`,`catch`和`throw`的异常处理(exception handling)很像。和其他语言中(包括Objective-C)的异常处理不同的是,Swift中的错误处理并不涉及堆栈解退(Stack unwinding),这是一个计算昂贵(computationally expensive)的过程。就此而言,`throw`语句的性能特性是可以和`return`语句相当的。
|
||||
|
||||
###用throwing函数传递错误
|
||||
用`throws`关键字标来识一个可抛出错误的函数,方法或是构造器。在函数声明中的参数列表之后加上`throws`。一个标识了`throws`的函数被称作*throwing函数*。如果这个函数还有返回值类型,`throws`关键词需要写在箭头(->)的前面。
|
||||
某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。
|
||||
|
||||
Swift 中有`4`种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用`do-catch`语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面的小节中都有描述。
|
||||
|
||||
当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上`try`关键字,或者`try?`或`try!`这种变体。这些关键字在下面的小节中有具体讲解。
|
||||
|
||||
> 注意
|
||||
> Swift 中的错误处理和其他语言中用`try`,`catch`和`throw`进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw`语句的性能特性是可以和`return`语句相媲美的。
|
||||
|
||||
<a name="propagating_errors_using_throwing_functions"></a>
|
||||
### 用 throwing 函数传递错误
|
||||
|
||||
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上`throws`关键字。一个标有`throws`关键字的函数被称作*throwing 函数*。如果这个函数指明了返回值类型,`throws`关键词需要写在箭头(`->`)的前面。
|
||||
|
||||
```swift
|
||||
func canThrowErrors() throws -> String
|
||||
func cannotThrowErrors() -> String
|
||||
```
|
||||
一个throwing函数从其内部抛出错误,并传递到该函数被调用时所在的区域中。
|
||||
>注意
|
||||
只有throwing函数可以传递错误。任何在某个非throwing函数内部抛出的错误只能在此函数内部处理
|
||||
|
||||
下面的例子中`VendingMechine`类有一个`vend(itemNamed:)`方法,如果需要的物品不存在,缺货或者花费超过了已投入金额,该方法就会抛出一个相称的`VendingMachineError`。
|
||||
一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
|
||||
|
||||
> 注意
|
||||
只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
|
||||
|
||||
下面的例子中,`VendingMechine`类有一个`vend(itemNamed:)`方法,如果请求的物品不存在、缺货或者投入金额小于物品价格,该方法就会抛出一个相应的`VendingMachineError`:
|
||||
|
||||
```swift
|
||||
struct Item {
|
||||
@ -79,7 +91,7 @@ class VendingMachine {
|
||||
}
|
||||
|
||||
func vend(itemNamed name: String) throws {
|
||||
guard var item = inventory[name] else {
|
||||
guard let item = inventory[name] else {
|
||||
throw VendingMachineError.InvalidSelection
|
||||
}
|
||||
|
||||
@ -92,16 +104,19 @@ class VendingMachine {
|
||||
}
|
||||
|
||||
coinsDeposited -= item.price
|
||||
--item.count
|
||||
inventory[name] = item
|
||||
|
||||
var newItem = item
|
||||
newItem.count -= 1
|
||||
inventory[name] = newItem
|
||||
|
||||
dispenseSnack(name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`vend(itemNamed:)`方法的实现使用了`guard`语句,确保在购买某个零食所需的条件有任一不被满足时能够尽早退出此方法并抛出相匹配的错误。由于`throw`语句会立即将程序控制转移,所以某件物品只有在所有条件都满足时才会被售出。
|
||||
在`vend(itemNamed:)`方法的实现中使用了`guard`语句来提前退出方法,确保在购买某个物品所需的条件中,有任一条件不满足时,能提前退出方法并抛出相应的错误。由于`throw`语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
|
||||
|
||||
因为`vend(itemNamed:)`方法会传递出它抛出的任何错误,在你代码中调用它的地方必须要么直接处理这些错误 - 使用`do-catch`语句,`try?`或`try!`,要么继续将这些错误传递下去。例如下面例子中`buyFavoriteSnack(_:vendingMachine)`同样是一个 throwing 函数,任何由`vend(itemNamed:)`方法抛出的错误会一直被传递到`buyFavoriteSnack(_:vendingMachine)`函数被调用的那个地方。
|
||||
因为`vend(itemNamed:)`方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用`do-catch`语句,`try?`或`try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(_:vendingMachine:)`同样是一个 throwing 函数,任何由`vend(itemNamed:)`方法抛出的错误会一直被传递到`buyFavoriteSnack(_:vendingMachine:)`函数被调用的地方。
|
||||
|
||||
```swift
|
||||
let favoriteSnacks = [
|
||||
@ -114,12 +129,27 @@ func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
try vendingMachine.vend(itemNamed: snackName)
|
||||
}
|
||||
```
|
||||
上例中`buyFavoriteSnack(_:vendingMachine)`函数会查找某人最喜欢的零食,并通过调用`vend(itemNamed:)`方法来尝试为他们买。因为`vend(itemNamed:)`方法能抛出错误,所以在调用的它时候在它前面加了`try`关键字。
|
||||
|
||||
上例中,`buyFavoriteSnack(_:vendingMachine:)`函数会查找某人最喜欢的零食,并通过调用`vend(itemNamed:)`方法来尝试为他们购买。因为`vend(itemNamed:)`方法能抛出错误,所以在调用的它时候在它前面加了`try`关键字。
|
||||
|
||||
throwing构造器能像throwing函数一样传递错误.例如下面代码中的`PurchasedSnack`构造器在构造过程中调用了throwing函数,并且通过传递到它的调用者来处理这些错误。
|
||||
|
||||
```swift
|
||||
struct PurchasedSnack {
|
||||
let name: String
|
||||
init(name: String, vendingMachine: VendingMachine) throws {
|
||||
try vendingMachine.vend(itemNamed: name)
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
###用 Do-Catch 处理错误
|
||||
可以使用一个`do-catch`语句运行一段闭包代码来做错误处理。如果在`do`语句中的代码抛出了一个错误,则这个错误会与`catch`语句做匹配来决定哪条语句能处理它。
|
||||
下面是`do-catch`语句的通用形式:
|
||||
|
||||
可以使用一个`do-catch`语句运行一段闭包代码来处理错误。如果在`do`子句中的代码抛出了一个错误,这个错误会与`catch`子句做匹配,从而决定哪条子句能处理它。
|
||||
|
||||
下面是`do-catch`语句的一般形式:
|
||||
|
||||
```swift
|
||||
do {
|
||||
@ -131,9 +161,10 @@ do {
|
||||
statements
|
||||
}
|
||||
```
|
||||
在`catch`后面写一个模式(pattern)来表示这个语句能处理什么样的错误。如果一条`catch`语句没带一个模式,那么这条语句可以和任何错误相匹配,并且把错误和一个名字为`name`的局部常量做绑定。关于模式匹配的更多信息请参考[Patterns](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID419)。
|
||||
|
||||
`catch`语句不必将`do`语句中代码所抛出的每个可能的错误都处理。如果没有一条`catch`字句来处理错误,错误就会传播到周围的作用域。然而错误还是必须要被某个周围的作用域处理的 - 要么是一个外围的`do-catch`错误处理语句,要么是一个throwing函数的内部。举例来说,下面的代码处理了`VendingMachineError`枚举类型的全部3个枚举实例,但是所有其它的错误就必须由它周围作用域所处理:
|
||||
在`catch`后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条`catch`子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为`error`的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
|
||||
|
||||
`catch`子句不必将`do`子句中的代码所抛出的每一个可能的错误都作处理。如果所有`catch`子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的`do-catch`错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了`VendingMachineError`枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理:
|
||||
|
||||
```swift
|
||||
var vendingMachine = VendingMachine()
|
||||
@ -147,12 +178,14 @@ do {
|
||||
} catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
}
|
||||
// prints "Insufficient funds. Please insert an additional 2 coins."
|
||||
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
|
||||
```
|
||||
上面的例子中`buyFavoriteSnack(_:vendingMachine:)`在一个`try`表达式中被调用,因为它能抛出一个错误。如果一个错误被抛出,相应的执行会被马上转移到`catch`语句中来,判断这个错误是否要被继续传递下去。如果没有错误抛出,`do`语句中余下的语句就会被执行。
|
||||
|
||||
上面的例子中,`buyFavoriteSnack(_:vendingMachine:)`函数在一个`try`表达式中调用,因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到`catch`子句中,并判断这个错误是否要被继续传递下去。如果没有错误抛出,`do`子句中余下的语句就会被执行。
|
||||
|
||||
###将错误转换成可选值
|
||||
可以使用`try?`通过将其转换成一个可选值来处理错误。如果在评估`try?`表达式时一个错误被抛出,那么这个表达式的值就是`nil`。例如下面代码中的`x`和`y`有相同的值和特性:
|
||||
|
||||
可以使用`try?`通过将错误转换成一个可选值来处理错误。如果在评估`try?`表达式时一个错误被抛出,那么表达式的值就是`nil`。例如,在下面的代码中,`x`和`y`有着相同的数值和等价的含义:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws -> Int {
|
||||
@ -169,8 +202,9 @@ do {
|
||||
}
|
||||
```
|
||||
|
||||
如果`someThrowingFunction()`抛出一个错误,`x`和`y`的值是`nil`。否则`x`和`y`的值就是该函数的返回值。注意无论`someThrowingFunction()`的返回值类型是什么类型,`x`和`y`都是这个类型的可选类型。例子中此函数返回一个整型,所以`x`和`y`是整型的可选类型。
|
||||
如果你想对所有的错误都采用同样的方式来处理,用`try?`就可以让你写出简洁的错误处理代码。比如下面的代码用一些的方式来获取数据,如果所有的方法都失败则返回`nil`。
|
||||
如果`someThrowingFunction()`抛出一个错误,`x`和`y`的值是`nil`。否则`x`和`y`的值就是该函数的返回值。注意,无论`someThrowingFunction()`的返回值类型是什么类型,`x`和`y`都是这个类型的可选类型。例子中此函数返回一个整型,所以`x`和`y`是可选整型。
|
||||
|
||||
如果你想对所有的错误都采用同样的方式来处理,用`try?`就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回`nil`:
|
||||
|
||||
```swift
|
||||
func fetchData() -> Data? {
|
||||
@ -179,9 +213,12 @@ func fetchData() -> Data? {
|
||||
return nil
|
||||
}
|
||||
```
|
||||
###使错误传递失效
|
||||
有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的。在这种条件下,你可以在表达式前面写`try!`来使错误传递失效,并把调用包装在一个运行时断言(runtime assertion)中来断定不会有错误抛出。如果实际上确实抛出了错误,你就会得到一个运行时错误。
|
||||
例如下面的代码使用了`loadImage(_:)`函数,该函数从给定的路径下装载图片资源,如果图片不能够被载入则抛出一个错误。此种情况下因为图片是和应用绑定的,运行时不会有错误被抛出,所以是错误传递失效是没问题的。
|
||||
|
||||
### 禁用错误传递
|
||||
|
||||
有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写`try!`来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
|
||||
|
||||
例如,下面的代码使用了`loadImage(_:)`函数,该函数从给定的路径加载图片资源,如果图片无法载入则抛出一个错误。在这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递:
|
||||
|
||||
```swift
|
||||
let photo = try! loadImage("./Resources/John Appleseed.jpg")
|
||||
@ -189,24 +226,27 @@ let photo = try! loadImage("./Resources/John Appleseed.jpg")
|
||||
|
||||
<a name="specifying_cleanup_actions"></a>
|
||||
##指定清理操作
|
||||
可以使用`defer`语句在代码执行到要离开当前的代码段之前去执行一套语句。该语句让你能够做一些应该被执行的必要清理工作,不管是以何种方式离开当前的代码段的 - 无论是由于抛出错误而离开,或是因为一条`return`或者`break`的类似语句。比如你可以用`defer`语句来保证文件描述符(file descriptors)已被关闭,并且手动分配的内存也被释放了。
|
||||
`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延时执行的语句组成。延时执行的语句不会包含任何会将控制权移交到语句外面的代码,例如一条`break`或是`return`语句,或是抛出一个错误。延迟执行的操作是按照它们被指定的相反顺序执行 - 意思是第一条`defer`语句中的代码执行是在第二条`defer`语句中代码被执行之后,以此类推。
|
||||
|
||||
可以使用`defer`语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如`return`或者`break`的语句。例如,你可以用`defer`语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
|
||||
|
||||
`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如`break`或是`return`语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条`defer`语句中的代码会在第二条`defer`语句中的代码被执行之后才执行,以此类推。
|
||||
|
||||
```swift
|
||||
func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// 处理文件
|
||||
}
|
||||
// 在这里,作用域的最后调用 close(file)
|
||||
}
|
||||
}
|
||||
func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// 处理文件。
|
||||
}
|
||||
// close(file) 会在这里被调用,即作用域的最后。
|
||||
}
|
||||
}
|
||||
```
|
||||
上面的代码用了一条`defer`语句来确保`open(_:)`函数有一个相应的对`close(_:)`的调用。
|
||||
|
||||
>注意
|
||||
>即使没有涉及到错误处理代码,你也可以用`defer`语句。
|
||||
上面的代码使用一条`defer`语句来确保`open(_:)`函数有一个相应的对`close(_:)`函数的调用。
|
||||
|
||||
> 注意
|
||||
> 即使没有涉及到错误处理,你也可以使用`defer`语句。
|
||||
|
||||
@ -1,99 +0,0 @@
|
||||
> 1.0
|
||||
> 翻译:[Lin-H](https://github.com/Lin-H)
|
||||
> 校对:[shinyzhu](https://github.com/shinyzhu)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[SergioChan](https://github.com/SergioChan)
|
||||
|
||||
# 嵌套类型
|
||||
-----------------
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [嵌套类型实例](#nested_types_in_action)
|
||||
- [嵌套类型的引用](#referring_to_nested_types)
|
||||
|
||||
枚举类型常被用于实现特定类或结构体的功能。也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义嵌套类型,可以在枚举类型、类和结构体中定义支持嵌套的类型。
|
||||
|
||||
要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套。
|
||||
|
||||
<a name="nested_types_in_action"></a>
|
||||
##嵌套类型实例
|
||||
|
||||
下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含2个嵌套定义的枚举类型`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`的牌)。
|
||||
|
||||
如上文所提到的,枚举型`Rank`在自己内部定义了一个嵌套结构体`Values`。在这个结构体中,只有`Ace`有两个数值,其余牌都只有一个数值。结构体`Values`中定义的两个属性:
|
||||
|
||||
`first`, 为` Int`
|
||||
`second`, 为 `Int?`, 或 “optional `Int`”
|
||||
|
||||
`Rank`定义了一个计算属性`values`,它将会返回一个结构体`Values`的实例。这个计算属性会根据牌的面值,用适当的数值去初始化`Values`实例,并赋值给`values`。对于`J`,`Q`,`K`,`Ace`会使用特殊数值,对于数字面值的牌使用`Int`类型的值。
|
||||
|
||||
`BlackjackCard`结构体自身有两个属性—`rank`与`suit`,也同样定义了一个计算属性`description`,`description`属性用`rank`和`suit`的中内容来构建对这张扑克牌名字和数值的描述,并用可选类型`second`来检查是否存在第二个值,若存在,则在原有的描述中增加对第二数值的描述。
|
||||
|
||||
因为`BlackjackCard`是一个没有自定义构造函数的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中知道结构体有默认的成员构造函数,所以你可以用默认的`initializer`去初始化新的常量`theAceOfSpades`:
|
||||
|
||||
```swift
|
||||
let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades)
|
||||
print("theAceOfSpades: \(theAceOfSpades.description)")
|
||||
// 打印出 "theAceOfSpades: suit is ♠, value is 1 or 11"
|
||||
```
|
||||
|
||||
尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但仍可被引用,所以在初始化实例时能够通过枚举类型中的成员名称单独引用。在上面的例子中`description`属性能正确得输出对`Ace`牌有1和11两个值。
|
||||
|
||||
<a name="referring_to_nested_types"></a>
|
||||
##嵌套类型的引用
|
||||
|
||||
在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名:
|
||||
|
||||
```swift
|
||||
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
|
||||
// 红心的符号 为 "♡"
|
||||
```
|
||||
|
||||
对于上面这个例子,这样可以使`Suit`, `Rank`, 和 `Values`的名字尽可能的短,因为它们的名字会自然的由定义它们的上下文来限定。
|
||||
@ -8,26 +8,32 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting)
|
||||
- [检查类型](#checking_type)
|
||||
- [向下转型(Downcasting)](#downcasting)
|
||||
- [`Any`和`AnyObject`的类型转换](#type_casting_for_any_and_anyobject)
|
||||
- [`Any` 和 `AnyObject` 的类型转换](#type_casting_for_any_and_anyobject)
|
||||
|
||||
|
||||
_类型转换_ 可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
|
||||
|
||||
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
|
||||
|
||||
你也可以用它来检查一个类是否实现了某个协议,就像在 [检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
|
||||
你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
|
||||
|
||||
<a name="defining_a_class_hierarchy_for_type_casting"></a>
|
||||
## 定义一个类层次作为例子
|
||||
|
||||
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了几个这些类实例的数组,作为类型转换的例子。
|
||||
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了这些类实例的数组,作为类型转换的例子。
|
||||
|
||||
第一个代码片段定义了一个新的基础类 `MediaItem`。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个 `init name` 初始化器。(假定所有的媒体项都有个名称。)
|
||||
第一个代码片段定义了一个新的基类 `MediaItem`。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个 `init(name:)` 初始化器。(假定所有的媒体项都有个名称。)
|
||||
|
||||
```swift
|
||||
class MediaItem {
|
||||
@ -58,7 +64,7 @@ class Song: MediaItem {
|
||||
}
|
||||
```
|
||||
|
||||
最后一个代码段创建了一个数组常量 `library`,包含两个 `Movie` 实例和三个 `Song` 实例。`library` 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift的类型检测器能够推理出 `Movie` 和 `Song` 有共同的父类 `MediaItem`,所以它推断出 `[MediaItem]` 类作为 `library` 的类型。
|
||||
最后一个代码段创建了一个数组常量 `library`,包含两个 `Movie` 实例和三个 `Song` 实例。`library` 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift 的类型检测器能够推断出 `Movie` 和 `Song` 有共同的父类 `MediaItem`,所以它推断出 `[MediaItem]` 类作为 `library` 的类型:
|
||||
|
||||
```swift
|
||||
let library = [
|
||||
@ -68,7 +74,7 @@ let library = [
|
||||
Song(name: "The One And Only", artist: "Chesney Hawkes"),
|
||||
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
|
||||
]
|
||||
// the type of "library" is inferred to be [MediaItem]
|
||||
// 数组 library 的类型被推断为 [MediaItem]
|
||||
```
|
||||
|
||||
在幕后 `library` 里存储的媒体项依然是 `Movie` 和 `Song` 类型的。但是,若你迭代它,依次取出的实例会是 `MediaItem` 类型的,而不是 `Movie` 和 `Song` 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。
|
||||
@ -76,9 +82,9 @@ let library = [
|
||||
<a name="checking_type"></a>
|
||||
## 检查类型(Checking Type)
|
||||
|
||||
用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。
|
||||
用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。
|
||||
|
||||
下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量。
|
||||
下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量:
|
||||
|
||||
```swift
|
||||
var movieCount = 0
|
||||
@ -86,37 +92,36 @@ var songCount = 0
|
||||
|
||||
for item in library {
|
||||
if item is Movie {
|
||||
++movieCount
|
||||
movieCount += 1
|
||||
} else if item is Song {
|
||||
++songCount
|
||||
songCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
// prints "Media library contains 2 movies and 3 songs"
|
||||
// 打印 “Media library contains 2 movies and 3 songs”
|
||||
```
|
||||
|
||||
示例迭代了数组 `library` 中的所有项。每一次,`for`-`in` 循环设置
|
||||
示例迭代了数组 `library` 中的所有项。每一次,`for-in` 循环设置
|
||||
`item` 为数组中的下一个 `MediaItem`。
|
||||
|
||||
若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回
|
||||
`true`,相反返回 `false`。同样的,`item is
|
||||
Song` 检查item是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到属于各自的类型的实例数量。
|
||||
`true`,否则返回 `false`。同样的,`item is Song` 检查 `item` 是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到的属于各自类型的实例的数量。
|
||||
|
||||
<a name="downcasting"></a>
|
||||
## 向下转型(Downcasting)
|
||||
|
||||
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?` 或 `as!`)
|
||||
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?` 或 `as!`)。
|
||||
|
||||
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form) `as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。
|
||||
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form)`as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)转换结果结合为一个操作。
|
||||
|
||||
当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。
|
||||
当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。
|
||||
|
||||
只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
|
||||
只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
|
||||
|
||||
下面的例子,迭代了 `library` 里的每一个 `MediaItem`,并打印出适当的描述。要这样做,`item` 需要真正作为 `Movie` 或 `Song` 的类型来使用,不仅仅是作为 `MediaItem`。为了能够在描述中使用 `Movie` 或 `Song` 的 `director` 或 `artist` 属性,这是必要的。
|
||||
下面的例子,迭代了 `library` 里的每一个 `MediaItem`,并打印出适当的描述。要这样做,`item` 需要真正作为 `Movie` 或 `Song` 的类型来使用,而不仅仅是作为 `MediaItem`。为了能够在描述中使用 `Movie` 或 `Song` 的 `director` 或 `artist` 属性,这是必要的。
|
||||
|
||||
在这个示例中,数组中的每一个 `item` 可能是 `Movie` 或 `Song`。事前你不知道每个 `item` 的真实类型,所以这里使用条件形式的类型转换(`as?`)去检查循环里的每次下转。
|
||||
在这个示例中,数组中的每一个 `item` 可能是 `Movie` 或 `Song`。事前你不知道每个 `item` 的真实类型,所以这里使用条件形式的类型转换(`as?`)去检查循环里的每次下转:
|
||||
|
||||
```swift
|
||||
for item in library {
|
||||
@ -136,37 +141,38 @@ for item in library {
|
||||
|
||||
示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem`
|
||||
类型的实例,它可能是一个 `Movie`;同样,它也可能是一个 `Song`,或者仅仅是基类
|
||||
`MediaItem`。因为不确定,`as?`形式在试图下转时将返回一个可选值。`item as? Movie` 的返回值是 `Movie?` 或 “可选 `Movie`”类型。
|
||||
`MediaItem`。因为不确定,`as?` 形式在试图下转时将返回一个可选值。`item as? Movie` 的返回值是 `Movie?` 或者说“可选 `Movie`”。
|
||||
|
||||
当向下转型为 `Movie` 应用在两个 `Song`
|
||||
实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 `Movie` 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“`if let movie = item as? Movie`”,可以这样解读:
|
||||
|
||||
“尝试将 `item` 转为 `Movie` 类型。若成功,设置一个新的临时常量 `movie` 来存储返回的可选 `Movie`”
|
||||
“尝试将 `item` 转为 `Movie` 类型。若成功,设置一个新的临时常量 `movie` 来存储返回的可选 `Movie` 中的值”
|
||||
|
||||
若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director` 。相近的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。
|
||||
若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director`。相似的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。
|
||||
|
||||
> 注意:
|
||||
> 转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。
|
||||
> 注意
|
||||
> 转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。
|
||||
|
||||
<a name="type_casting_for_any_and_anyobject"></a>
|
||||
## `Any`和`AnyObject`的类型转换
|
||||
## `Any` 和 `AnyObject` 的类型转换
|
||||
|
||||
Swift为不确定类型提供了两种特殊类型别名:
|
||||
Swift 为不确定类型提供了两种特殊的类型别名:
|
||||
|
||||
* `AnyObject`可以代表任何class类型的实例。
|
||||
* `Any`可以表示任何类型,包括方法类型(function types)。
|
||||
* `AnyObject` 可以表示任何类类型的实例。
|
||||
* `Any` 可以表示任何类型,包括函数类型。
|
||||
|
||||
> 注意:
|
||||
> 只有当你明确的需要它的行为和功能时才使用`Any`和`AnyObject`。在你的代码里使用你期望的明确的类型总是更好的。
|
||||
> 注意
|
||||
> 只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。在你的代码里使用你期望的明确类型总是更好的。
|
||||
|
||||
<a name="anyobject"></a>
|
||||
### `AnyObject`类型
|
||||
### `AnyObject` 类型
|
||||
|
||||
当在工作中使用 Cocoa APIs,我们一般会接收一个`[AnyObject]`类型的数组,或者说“一个任何对象类型的数组”。这是因为 Objective-C 没有明确的类型化数组。但是,你常常可以从 API 提供的信息中清晰地确定数组中对象的类型。
|
||||
当我们使用 Cocoa APIs 时,我们会接收到一个 `[AnyObject]` 类型的数组,或者说“一个任意类型对象的数组”。Objective-C现在支持明确的数组类型,但早期版本的Objective-C并没有这个功能。不管怎样,你都可以确信API提供的信息能够正确的表明数组中的元素类型。
|
||||
|
||||
在这些情况下,你可以使用强制形式的类型转换(`as`)来下转在数组中的每一项到比 `AnyObject` 更明确的类型,不需要可选解析(optional unwrapping)。
|
||||
|
||||
下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个`Movie`类型的实例:
|
||||
在这些情况下,你可以使用强制形式的类型转换(`as!`)来下转数组中的每一项到比 `AnyObject` 更明确的类型,不需要可选解包(optional unwrapping)。
|
||||
|
||||
下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个 `Movie` 类型的实例:
|
||||
|
||||
```swift
|
||||
let someObjects: [AnyObject] = [
|
||||
@ -176,7 +182,7 @@ let someObjects: [AnyObject] = [
|
||||
]
|
||||
```
|
||||
|
||||
因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到不可选的`Movie`类型:
|
||||
因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到非可选的 `Movie` 类型:
|
||||
|
||||
```swift
|
||||
for object in someObjects {
|
||||
@ -188,7 +194,7 @@ for object in someObjects {
|
||||
// Movie: 'Alien', dir. Ridley Scott
|
||||
```
|
||||
|
||||
为了变为一个更短的形式,下转`someObjects`数组为`[Movie]`类型来代替下转数组中每一项的方式。
|
||||
为了变为一个更简短的形式,下转 `someObjects` 数组为 `[Movie]` 类型而不是下转数组中的每一项:
|
||||
|
||||
```swift
|
||||
for movie in someObjects as! [Movie] {
|
||||
@ -199,9 +205,10 @@ for movie in someObjects as! [Movie] {
|
||||
// Movie: 'Alien', dir. Ridley Scott
|
||||
```
|
||||
|
||||
### `Any`类型
|
||||
<a name="any"></a>
|
||||
### `Any` 类型
|
||||
|
||||
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括方法类型和非 `class` 类型。它创建了一个可以存储`Any`类型的数组 `things`。
|
||||
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 `Any` 类型的数组 `things`:
|
||||
|
||||
```swift
|
||||
var things = [Any]()
|
||||
@ -216,9 +223,9 @@ things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
|
||||
things.append({ (name: String) -> String in "Hello, \(name)" })
|
||||
```
|
||||
|
||||
`things` 数组包含两个 `Int` 值,2个 `Double` 值,1个 `String` 值,一个元组 `(Double, Double)` ,电影“Ghostbusters”,和一个获取 `String` 值并返回另一个 `String` 值的闭包表达式。
|
||||
`things` 数组包含两个 `Int` 值,两个 `Double` 值,一个 `String` 值,一个元组 `(Double, Double)`,一个`Movie`实例“Ghostbusters”,以及一个接受 `String` 值并返回另一个 `String` 值的闭包表达式。
|
||||
|
||||
你可以在 `switch` 表达式的cases中使用 `is` 和 `as` 操作符来发觉只知道是 `Any` 或 `AnyObject` 的常量或变量的类型。下面的示例迭代 `things` 数组中的每一项的并用`switch`语句查找每一项的类型。这几种 `switch` 语句的情形绑定它们匹配的值到一个规定类型的常量,让它们的值可以被打印:
|
||||
你可以在 `switch` 表达式的 `case` 中使用 `is` 和 `as` 操作符来找出只知道是 `Any` 或 `AnyObject` 类型的常量或变量的具体类型。下面的示例迭代 `things` 数组中的每一项,并用 `switch` 语句查找每一项的类型。有几个 `switch` 语句的 `case` 绑定它们匹配到的值到一个指定类型的常量,从而可以打印这些值:
|
||||
|
||||
```swift
|
||||
for thing in things {
|
||||
@ -255,7 +262,3 @@ for thing in things {
|
||||
// a movie called 'Ghostbusters', dir. Ivan Reitman
|
||||
// Hello, Michael
|
||||
```
|
||||
|
||||
|
||||
> 注意:
|
||||
> 在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型。在 `switch` case 语句的内容中这种检查总是安全的。
|
||||
|
||||
@ -8,30 +8,36 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[SergioChan](https://github.com/SergioChan)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [嵌套类型实例](#nested_types_in_action)
|
||||
- [嵌套类型的引用](#referring_to_nested_types)
|
||||
- [嵌套类型实践](#nested_types_in_action)
|
||||
- [引用嵌套类型](#referring_to_nested_types)
|
||||
|
||||
枚举类型常被用于实现特定类或结构体的功能。也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义嵌套类型,可以在枚举类型、类和结构体中定义支持嵌套的类型。
|
||||
枚举常被用于为特定类或结构体实现某些功能。类似的,枚举可以方便的定义工具类或结构体,从而为某个复杂的类型所使用。为了实现这种功能,Swift 允许你定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体。
|
||||
|
||||
要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套。
|
||||
要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的`{}`内,而且可以根据需要定义多级嵌套。
|
||||
|
||||
<a name="nested_types_in_action"></a>
|
||||
##嵌套类型实例
|
||||
## 嵌套类型实践
|
||||
|
||||
下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含2个嵌套定义的枚举类型`Suit` 和 `Rank`。
|
||||
下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含两个嵌套定义的枚举类型`Suit`和`Rank`。
|
||||
|
||||
在`BlackjackCard`规则中,`Ace`牌可以表示1或者11,`Ace`牌的这一特征用一个嵌套在枚举型`Rank`的结构体`Values`来表示。
|
||||
在`BlackjackCard`中,`Ace`牌可以表示`1`或者`11`,`Ace`牌的这一特征通过一个嵌套在`Rank`枚举中的结构体`Values`来表示:
|
||||
|
||||
```swift
|
||||
struct BlackjackCard {
|
||||
// 嵌套定义枚举型Suit
|
||||
// 嵌套的 Suit 枚举
|
||||
enum Suit: Character {
|
||||
case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣"
|
||||
}
|
||||
|
||||
// 嵌套定义枚举型Rank
|
||||
// 嵌套的 Rank 枚举
|
||||
enum Rank: Int {
|
||||
case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
|
||||
case Jack, Queen, King, Ace
|
||||
@ -63,37 +69,37 @@ struct BlackjackCard {
|
||||
}
|
||||
```
|
||||
|
||||
枚举型的`Suit`用来描述扑克牌的四种花色,并分别用一个`Character`类型的值代表花色符号。
|
||||
`Suit`枚举用来描述扑克牌的四种花色,并用一个`Character`类型的原始值表示花色符号。
|
||||
|
||||
枚举型的`Rank`用来描述扑克牌从`Ace`~10,`J`,`Q`,`K`,13张牌,并分别用一个`Int`类型的值表示牌的面值。(这个`Int`类型的值不适用于`Ace`,`J`,`Q`,`K`的牌)。
|
||||
`Rank`枚举用来描述扑克牌从`Ace`~`10`,以及`J`、`Q`、`K`,这`13`种牌,并用一个`Int`类型的原始值表示牌的面值。(这个`Int`类型的原始值未用于`Ace`、`J`、`Q`、`K`这`4`种牌。)
|
||||
|
||||
如上文所提到的,枚举型`Rank`在自己内部定义了一个嵌套结构体`Values`。在这个结构体中,只有`Ace`有两个数值,其余牌都只有一个数值。结构体`Values`中定义的两个属性:
|
||||
如上所述,`Rank`枚举在内部定义了一个嵌套结构体`Values`。结构体`Values`中定义了两个属性,用于反映只有`Ace`有两个数值,其余牌都只有一个数值:
|
||||
|
||||
- `first`为` Int`
|
||||
- `second`为 `Int?` 或 “optional `Int`”
|
||||
- `first`的类型为`Int`
|
||||
- `second`的类型为`Int?`,或者说“optional `Int`”
|
||||
|
||||
`Rank`定义了一个计算属性`values`,它将会返回一个结构体`Values`的实例。这个计算属性会根据牌的面值,用适当的数值去初始化`Values`实例,并赋值给`values`。对于`J`,`Q`,`K`,`Ace`会使用特殊数值,对于数字面值的牌使用`Int`类型的值。
|
||||
`Rank`还定义了一个计算型属性`values`,它将会返回一个`Values`结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化`Values`实例。对于`J`、`Q`、`K`、`Ace`这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的原始值。
|
||||
|
||||
`BlackjackCard`结构体自身有两个属性—`rank`与`suit`,也同样定义了一个计算属性`description`,`description`属性用`rank`和`suit`的中内容来构建对这张扑克牌名字和数值的描述,并用可选类型`second`来检查是否存在第二个值,若存在,则在原有的描述中增加对第二数值的描述。
|
||||
`BlackjackCard`结构体拥有两个属性——`rank`与`suit`。它也同样定义了一个计算型属性`description`,`description`属性用`rank`和`suit`中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型`second`是否有值,若有值,则在原有的描述中增加对`second`的描述。
|
||||
|
||||
因为`BlackjackCard`是一个没有自定义构造函数的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中知道结构体有默认的成员构造函数,所以你可以用默认的`initializer`去初始化新的常量`theAceOfSpades`:
|
||||
因为`BlackjackCard`是一个没有自定义构造器的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#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"
|
||||
// 打印 “theAceOfSpades: suit is ♠, value is 1 or 11”
|
||||
```
|
||||
|
||||
尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但仍可被引用,所以在初始化实例时能够通过枚举类型中的成员名称单独引用。在上面的例子中`description`属性能正确得输出对`Ace`牌有1和11两个值。
|
||||
尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.Ace`和`.Spades`)引用枚举实例。在上面的例子中,`description`属性正确地反映了黑桃A牌具有`1`和`11`两个值。
|
||||
|
||||
<a name="referring_to_nested_types"></a>
|
||||
##嵌套类型的引用
|
||||
## 引用嵌套类型
|
||||
|
||||
在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名:
|
||||
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
|
||||
|
||||
```swift
|
||||
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
|
||||
// 红心的符号 为 "♡"
|
||||
// 红心符号为 “♡”
|
||||
```
|
||||
|
||||
对于上面这个例子,这样可以使`Suit`, `Rank`, 和 `Values`的名字尽可能的短,因为它们的名字会自然的由定义它们的上下文来限定。
|
||||
对于上面这个例子,这样可以使`Suit`、`Rank`和`Values`的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。
|
||||
|
||||
@ -1,261 +0,0 @@
|
||||
> 1.0
|
||||
> 翻译:[xiehurricane](https://github.com/xiehurricane)
|
||||
> 校对:[happyming](https://github.com/happyming)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
|
||||
|
||||
# 类型转换(Type Casting)
|
||||
-----------------
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting)
|
||||
- [检查类型](#checking_type)
|
||||
- [向下转型(Downcasting)](#downcasting)
|
||||
- [`Any`和`AnyObject`的类型转换](#type_casting_for_any_and_anyobject)
|
||||
|
||||
|
||||
_类型转换_可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
|
||||
|
||||
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
|
||||
|
||||
你也可以用它来检查一个类是否实现了某个协议,就像在 [检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
|
||||
|
||||
<a name="defining_a_class_hierarchy_for_type_casting"></a>
|
||||
## 定义一个类层次作为例子
|
||||
|
||||
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了几个这些类实例的数组,作为类型转换的例子。
|
||||
|
||||
第一个代码片段定义了一个新的基础类 `MediaItem`。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个 `init name` 初始化器。(假定所有的媒体项都有个名称。)
|
||||
|
||||
```swift
|
||||
class MediaItem {
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下一个代码段定义了 `MediaItem` 的两个子类。第一个子类 `Movie` 封装了与电影相关的额外信息,在父类(或者说基类)的基础上增加了一个 `director`(导演)属性,和相应的初始化器。第二个子类 `Song`,在父类的基础上增加了一个 `artist`(艺术家)属性,和相应的初始化器:
|
||||
|
||||
```swift
|
||||
class Movie: MediaItem {
|
||||
var director: String
|
||||
init(name: String, director: String) {
|
||||
self.director = director
|
||||
super.init(name: name)
|
||||
}
|
||||
}
|
||||
|
||||
class Song: MediaItem {
|
||||
var artist: String
|
||||
init(name: String, artist: String) {
|
||||
self.artist = artist
|
||||
super.init(name: name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后一个代码段创建了一个数组常量 `library`,包含两个 `Movie` 实例和三个 `Song` 实例。`library` 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift的类型检测器能够推理出 `Movie` 和 `Song` 有共同的父类 `MediaItem`,所以它推断出 `[MediaItem]` 类作为 `library` 的类型。
|
||||
|
||||
```swift
|
||||
let library = [
|
||||
Movie(name: "Casablanca", director: "Michael Curtiz"),
|
||||
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
|
||||
Movie(name: "Citizen Kane", director: "Orson Welles"),
|
||||
Song(name: "The One And Only", artist: "Chesney Hawkes"),
|
||||
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
|
||||
]
|
||||
// the type of "library" is inferred to be [MediaItem]
|
||||
```
|
||||
|
||||
在幕后 `library` 里存储的媒体项依然是 `Movie` 和 `Song` 类型的。但是,若你迭代它,依次取出的实例会是 `MediaItem` 类型的,而不是 `Movie` 和 `Song` 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。
|
||||
|
||||
<a name="checking_type"></a>
|
||||
## 检查类型(Checking Type)
|
||||
|
||||
用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。
|
||||
|
||||
下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量。
|
||||
|
||||
```swift
|
||||
var movieCount = 0
|
||||
var songCount = 0
|
||||
|
||||
for item in library {
|
||||
if item is Movie {
|
||||
++movieCount
|
||||
} else if item is Song {
|
||||
++songCount
|
||||
}
|
||||
}
|
||||
|
||||
print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
// prints "Media library contains 2 movies and 3 songs"
|
||||
```
|
||||
|
||||
示例迭代了数组 `library` 中的所有项。每一次,`for`-`in` 循环设置
|
||||
`item` 为数组中的下一个 `MediaItem`。
|
||||
|
||||
若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回
|
||||
`true`,相反返回 `false`。同样的,`item is
|
||||
Song` 检查item是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到属于各自的类型的实例数量。
|
||||
|
||||
<a name="downcasting"></a>
|
||||
## 向下转型(Downcasting)
|
||||
|
||||
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?` 或 `as!`)
|
||||
|
||||
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form) `as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。
|
||||
|
||||
当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。
|
||||
|
||||
只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
|
||||
|
||||
下面的例子,迭代了 `library` 里的每一个 `MediaItem`,并打印出适当的描述。要这样做,`item` 需要真正作为 `Movie` 或 `Song` 的类型来使用,不仅仅是作为 `MediaItem`。为了能够在描述中使用 `Movie` 或 `Song` 的 `director` 或 `artist` 属性,这是必要的。
|
||||
|
||||
在这个示例中,数组中的每一个 `item` 可能是 `Movie` 或 `Song`。事前你不知道每个 `item` 的真实类型,所以这里使用条件形式的类型转换(`as?`)去检查循环里的每次下转。
|
||||
|
||||
```swift
|
||||
for item in library {
|
||||
if let movie = item as? Movie {
|
||||
print("Movie: '\(movie.name)', dir. \(movie.director)")
|
||||
} else if let song = item as? Song {
|
||||
print("Song: '\(song.name)', by \(song.artist)")
|
||||
}
|
||||
}
|
||||
|
||||
// Movie: 'Casablanca', dir. Michael Curtiz
|
||||
// Song: 'Blue Suede Shoes', by Elvis Presley
|
||||
// Movie: 'Citizen Kane', dir. Orson Welles
|
||||
// Song: 'The One And Only', by Chesney Hawkes
|
||||
// Song: 'Never Gonna Give You Up', by Rick Astley
|
||||
```
|
||||
|
||||
示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem`
|
||||
类型的实例,它可能是一个 `Movie`;同样,它也可能是一个 `Song`,或者仅仅是基类
|
||||
`MediaItem`。因为不确定,`as?`形式在试图下转时将返回一个可选值。`item as? Movie` 的返回值是 `Movie?` 或 “可选 `Movie`”类型。
|
||||
|
||||
当向下转型为 `Movie` 应用在两个 `Song`
|
||||
实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 `Movie` 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“`if let movie = item as? Movie`”,可以这样解读:
|
||||
|
||||
“尝试将 `item` 转为 `Movie` 类型。若成功,设置一个新的临时常量 `movie` 来存储返回的可选 `Movie`”
|
||||
|
||||
若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director` 。相近的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。
|
||||
|
||||
> 注意:
|
||||
> 转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。
|
||||
|
||||
<a name="type_casting_for_any_and_anyobject"></a>
|
||||
## `Any`和`AnyObject`的类型转换
|
||||
|
||||
Swift为不确定类型提供了两种特殊类型别名:
|
||||
|
||||
* `AnyObject`可以代表任何class类型的实例。
|
||||
* `Any`可以表示任何类型,包括方法类型(function types)。
|
||||
|
||||
> 注意:
|
||||
> 只有当你明确的需要它的行为和功能时才使用`Any`和`AnyObject`。在你的代码里使用你期望的明确的类型总是更好的。
|
||||
|
||||
<a name="anyobject"></a>
|
||||
### `AnyObject`类型
|
||||
|
||||
当在工作中使用 Cocoa APIs,我们一般会接收一个`[AnyObject]`类型的数组,或者说“一个任何对象类型的数组”。这是因为 Objective-C 没有明确的类型化数组。但是,你常常可以从 API 提供的信息中清晰地确定数组中对象的类型。
|
||||
|
||||
在这些情况下,你可以使用强制形式的类型转换(`as`)来下转在数组中的每一项到比 `AnyObject` 更明确的类型,不需要可选解析(optional unwrapping)。
|
||||
|
||||
下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个`Movie`类型的实例:
|
||||
|
||||
```swift
|
||||
let someObjects: [AnyObject] = [
|
||||
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
|
||||
Movie(name: "Moon", director: "Duncan Jones"),
|
||||
Movie(name: "Alien", director: "Ridley Scott")
|
||||
]
|
||||
```
|
||||
|
||||
因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到不可选的`Movie`类型:
|
||||
|
||||
```swift
|
||||
for object in someObjects {
|
||||
let movie = object as! Movie
|
||||
print("Movie: '\(movie.name)', dir. \(movie.director)")
|
||||
}
|
||||
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
|
||||
// Movie: 'Moon', dir. Duncan Jones
|
||||
// Movie: 'Alien', dir. Ridley Scott
|
||||
```
|
||||
|
||||
为了变为一个更短的形式,下转`someObjects`数组为`[Movie]`类型来代替下转数组中每一项的方式。
|
||||
|
||||
```swift
|
||||
for movie in someObjects as! [Movie] {
|
||||
print("Movie: '\(movie.name)', dir. \(movie.director)")
|
||||
}
|
||||
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
|
||||
// Movie: 'Moon', dir. Duncan Jones
|
||||
// Movie: 'Alien', dir. Ridley Scott
|
||||
```
|
||||
|
||||
### `Any`类型
|
||||
|
||||
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括方法类型和非 `class` 类型。它创建了一个可以存储`Any`类型的数组 `things`。
|
||||
|
||||
```swift
|
||||
var things = [Any]()
|
||||
|
||||
things.append(0)
|
||||
things.append(0.0)
|
||||
things.append(42)
|
||||
things.append(3.14159)
|
||||
things.append("hello")
|
||||
things.append((3.0, 5.0))
|
||||
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
|
||||
things.append({ (name: String) -> String in "Hello, \(name)" })
|
||||
```
|
||||
|
||||
`things` 数组包含两个 `Int` 值,2个 `Double` 值,1个 `String` 值,一个元组 `(Double, Double)` ,电影“Ghostbusters”,和一个获取 `String` 值并返回另一个 `String` 值的闭包表达式。
|
||||
|
||||
你可以在 `switch` 表达式的cases中使用 `is` 和 `as` 操作符来发觉只知道是 `Any` 或 `AnyObject` 的常量或变量的类型。下面的示例迭代 `things` 数组中的每一项的并用`switch`语句查找每一项的类型。这几种 `switch` 语句的情形绑定它们匹配的值到一个规定类型的常量,让它们的值可以被打印:
|
||||
|
||||
```swift
|
||||
for thing in things {
|
||||
switch thing {
|
||||
case 0 as Int:
|
||||
print("zero as an Int")
|
||||
case 0 as Double:
|
||||
print("zero as a Double")
|
||||
case let someInt as Int:
|
||||
print("an integer value of \(someInt)")
|
||||
case let someDouble as Double where someDouble > 0:
|
||||
print("a positive double value of \(someDouble)")
|
||||
case is Double:
|
||||
print("some other double value that I don't want to print")
|
||||
case let someString as String:
|
||||
print("a string value of \"\(someString)\"")
|
||||
case let (x, y) as (Double, Double):
|
||||
print("an (x, y) point at \(x), \(y)")
|
||||
case let movie as Movie:
|
||||
print("a movie called '\(movie.name)', dir. \(movie.director)")
|
||||
case let stringConverter as String -> String:
|
||||
print(stringConverter("Michael"))
|
||||
default:
|
||||
print("something else")
|
||||
}
|
||||
}
|
||||
|
||||
// zero as an Int
|
||||
// zero as a Double
|
||||
// an integer value of 42
|
||||
// a positive double value of 3.14159
|
||||
// a string value of "hello"
|
||||
// an (x, y) point at 3.0, 5.0
|
||||
// a movie called 'Ghostbusters', dir. Ivan Reitman
|
||||
// Hello, Michael
|
||||
```
|
||||
|
||||
|
||||
> 注意:
|
||||
> 在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型。在 `switch` case 语句的内容中这种检查总是安全的。
|
||||
@ -6,7 +6,13 @@
|
||||
> 校对:[Hawstein](https://github.com/Hawstein)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[shanksyang](https://github.com/shanksyang)
|
||||
> 翻译+校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
|
||||
|
||||
本页包含内容:
|
||||
|
||||
@ -17,33 +23,34 @@
|
||||
- [下标](#subscripts)
|
||||
- [嵌套类型](#nested_types)
|
||||
|
||||
*扩展*就是向一个已有的类、结构体、枚举类型或者协议类型添加新功能(functionality)。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即*逆向建模*)。扩展和 Objective-C 中的分类(categories)类似。(不过与 Objective-C 不同的是,Swift 的扩展没有名字。)
|
||||
*扩展* 就是为一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即 *逆向建模* )。扩展和 Objective-C 中的分类类似。(与 Objective-C 不同的是,Swift 的扩展没有名字。)
|
||||
|
||||
Swift 中的扩展可以:
|
||||
|
||||
- 添加计算型属性和计算型静态属性
|
||||
- 添加计算型属性和计算型类型属性
|
||||
- 定义实例方法和类型方法
|
||||
- 提供新的构造器
|
||||
- 定义下标
|
||||
- 定义和使用新的嵌套类型
|
||||
- 使一个已有类型符合某个协议
|
||||
|
||||
在 Swift 中,你甚至可以对一个协议(Protocol)进行扩展,提供协议需要的实现,或者添加额外的功能能够对合适的类型带来额外的好处。你可以从[协议扩展](./22_Protocols.html#protocol_extensions)获取更多的细节。
|
||||
在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的类型拥有这些功能。你可以从[协议扩展](./22_Protocols.html#protocol_extensions)获取更多的细节。
|
||||
|
||||
> 注意
|
||||
扩展可以为一个类型添加新的功能,但是不能重写已有的功能。
|
||||
|
||||
>注意:
|
||||
扩展可以对一个类型添加新的功能,但是不能重写已有的功能。
|
||||
<a name="extension_syntax"></a>
|
||||
|
||||
## 扩展语法(Extension Syntax)
|
||||
|
||||
声明一个扩展使用关键字`extension`:
|
||||
使用关键字 `extension` 来声明扩展:
|
||||
|
||||
```swift
|
||||
extension SomeType {
|
||||
// 加到SomeType的新功能写到这里
|
||||
// 为 SomeType 添加的新功能写到这里
|
||||
}
|
||||
```
|
||||
一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议(protocol)。当这种情况发生时,协议的名字应该完全按照类或结构体的名字的方式进行书写:
|
||||
|
||||
可以通过扩展来扩展一个已有类型,使其采纳一个或多个协议。在这种情况下,无论是类还是结构体,协议名字的书写方式完全一样:
|
||||
|
||||
```swift
|
||||
extension SomeType: SomeProtocol, AnotherProctocol {
|
||||
@ -51,15 +58,15 @@ extension SomeType: SomeProtocol, AnotherProctocol {
|
||||
}
|
||||
```
|
||||
|
||||
按照这种方式添加的协议遵循者(protocol conformance)被称之为[在扩展中添加协议遵循者](./22_Protocols.html#adding_protocol_conformance_with_an_extension)
|
||||
通过这种方式添加协议一致性的详细描述请参阅[利用扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)。
|
||||
|
||||
>注意:
|
||||
如果你定义了一个扩展向一个已有类型添加新功能,那么这个新功能对该类型的所有已有实例中都是可用的,即使它们是在你的这个扩展的前面定义的。
|
||||
> 注意
|
||||
如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。
|
||||
|
||||
<a name="computed_properties"></a>
|
||||
## 计算型属性(Computed Properties)
|
||||
|
||||
扩展可以向已有类型添加计算型实例属性和计算型类型属性。下面的例子向 Swift 的内建`Double`类型添加了5个计算型实例属性,从而提供与距离单位协作的基本支持:
|
||||
扩展可以为已有类型添加计算型实例属性和计算型类型属性。下面的例子为 Swift 的内建 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位协作的基本支持:
|
||||
|
||||
```swift
|
||||
extension Double {
|
||||
@ -71,42 +78,41 @@ extension Double {
|
||||
}
|
||||
let oneInch = 25.4.mm
|
||||
print("One inch is \(oneInch) meters")
|
||||
// 打印输出:"One inch is 0.0254 meters"
|
||||
// 打印 “One inch is 0.0254 meters”
|
||||
let threeFeet = 3.ft
|
||||
print("Three feet is \(threeFeet) meters")
|
||||
// 打印输出:"Three feet is 0.914399970739201 meters"
|
||||
// 打印 “Three feet is 0.914399970739201 meters”
|
||||
```
|
||||
|
||||
这些计算属性表达的含义是把一个`Double`型的值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性仍可以接一个带有dot语法的浮点型字面值,而这恰恰是使用这些浮点型字面量实现距离转换的方式。
|
||||
这些计算型属性表达的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
|
||||
|
||||
在上述例子中,一个`Double`型的值`1.0`被用来表示“1米”。这就是为什么`m`计算型属性返回`self`——表达式`1.m`被认为是计算`1.0`的`Double`值。
|
||||
在上述例子中,`Double` 值 `1.0` 用来表示“1米”。这就是为什么计算型属性 `m` 返回 `self`,即表达式 `1.m` 被认为是计算 `Double` 值 `1.0`。
|
||||
|
||||
其它单位则需要一些转换来表示在米下测量的值。1千米等于1,000米,所以`km`计算型属性要把值乘以`1_000.00`来转化成单位米下的数值。类似地,1米有3.28024英尺,所以`ft`计算型属性要把对应的`Double`值除以`3.28024`来实现英尺到米的单位换算。
|
||||
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28024 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28024` 来实现英尺到米的单位换算。
|
||||
|
||||
这些属性是只读的计算型属性,所有从简考虑它们不用`get`关键字表示。它们的返回值是`Double`型,而且可以用于所有接受`Double`的数学计算中:
|
||||
这些属性是只读的计算型属性,为了更简洁,省略了 `get` 关键字。它们的返回值是 `Double`,而且可以用于所有接受 `Double` 值的数学计算中:
|
||||
|
||||
```swift
|
||||
let aMarathon = 42.km + 195.m
|
||||
print("A marathon is \(aMarathon) meters long")
|
||||
// 打印输出:"A marathon is 42195.0 meters long"
|
||||
// 打印 “A marathon is 42195.0 meters long”
|
||||
```
|
||||
|
||||
>注意:
|
||||
扩展可以添加新的计算属性,但是不可以添加存储属性,也不可以向已有属性添加属性观测器(property observers)。
|
||||
> 注意
|
||||
扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。
|
||||
|
||||
<a name="initializers"></a>
|
||||
## 构造器(Initializers)
|
||||
|
||||
扩展可以向已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。
|
||||
扩展可以为已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为其构造器参数,或者提供该类型的原始实现中未提供的额外初始化选项。
|
||||
|
||||
扩展能向类中添加新的便利构造器,但是它们不能向类中添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。
|
||||
扩展能为类添加新的便利构造器,但是它们不能为类添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。
|
||||
|
||||
> 注意:
|
||||
如果你使用扩展向一个值类型添加一个构造器,在该值类型已经向所有的存储属性提供默认值,而且没有定义任何定制构造器(custom initializers)时,你可以在值类型的扩展构造器中调用默认构造器(default initializers)和逐一成员构造器(memberwise initializers)。
|
||||
>
|
||||
正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你已经把构造器写成值类型原始实现的一部分,上述规则不再适用。
|
||||
> 注意
|
||||
如果你使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何定制的构造器且所有存储属性提供了默认值,那么我们就可以在扩展中的构造器里调用默认构造器和逐一成员构造器。
|
||||
正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你把定制的构造器写在值类型的原始实现中,上述规则将不再适用。
|
||||
|
||||
下面的例子定义了一个用于描述几何矩形的定制结构体`Rect`。这个例子同时定义了两个辅助结构体`Size`和`Point`,它们都把`0.0`作为所有属性的默认值:
|
||||
下面的例子定义了一个用于描述几何矩形的结构体 `Rect`。这个例子同时定义了两个辅助结构体 `Size` 和 `Point`,它们都把 `0.0` 作为所有属性的默认值:
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
@ -120,7 +126,7 @@ struct Rect {
|
||||
var size = Size()
|
||||
}
|
||||
```
|
||||
因为结构体`Rect`提供了其所有属性的默认值,所以正如[默认构造器](./14_Initialization.html#default_initializers)中描述的,它可以自动接受一个默认构造器和一个逐一成员构造器。这些构造器可以用于构造新的`Rect`实例:
|
||||
因为结构体 `Rect` 未提供定制的构造器,因此它会获得一个逐一成员构造器。又因为它为所有存储型属性提供了默认值,它又会获得一个默认构造器。详情请参阅[默认构造器](./14_Initialization.html#default_initializers)。这些构造器可以用于构造新的 `Rect` 实例:
|
||||
|
||||
```swift
|
||||
let defaultRect = Rect()
|
||||
@ -128,7 +134,7 @@ let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
size: Size(width: 5.0, height: 5.0))
|
||||
```
|
||||
|
||||
你可以提供一个额外的使用特殊中心点和大小的构造器来扩展`Rect`结构体:
|
||||
你可以提供一个额外的接受指定中心点和大小的构造器来扩展 `Rect` 结构体:
|
||||
|
||||
```swift
|
||||
extension Rect {
|
||||
@ -139,50 +145,50 @@ extension Rect {
|
||||
}
|
||||
}
|
||||
```
|
||||
这个新的构造器首先根据提供的`center`和`size`值计算一个合适的原点。然后调用该结构体自动的逐一成员构造器`init(origin:size:)`,该构造器将新的原点和大小存到了合适的属性中:
|
||||
|
||||
这个新的构造器首先根据提供的 `center` 和 `size` 的值计算一个合适的原点。然后调用该结构体的逐一成员构造器 `init(origin:size:)`,该构造器将新的原点和大小的值保存到了相应的属性中:
|
||||
|
||||
```swift
|
||||
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
size: Size(width: 3.0, height: 3.0))
|
||||
// centerRect的原点是 (2.5, 2.5),大小是 (3.0, 3.0)
|
||||
// centerRect 的原点是 (2.5, 2.5),大小是 (3.0, 3.0)
|
||||
```
|
||||
|
||||
|
||||
>注意:
|
||||
如果你使用扩展提供了一个新的构造器,你依旧有责任保证构造过程能够让所有实例完全初始化。
|
||||
> 注意
|
||||
如果你使用扩展提供了一个新的构造器,你依旧有责任确保构造过程能够让实例完全初始化。
|
||||
|
||||
<a name="methods"></a>
|
||||
## 方法(Methods)
|
||||
|
||||
扩展可以向已有类型添加新的实例方法和类型方法。下面的例子向`Int`类型添加一个名为`repetitions`的新实例方法:
|
||||
扩展可以为已有类型添加新的实例方法和类型方法。下面的例子为 `Int` 类型添加了一个名为 `repetitions` 的实例方法:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
func repetitions(task: () -> ()) {
|
||||
for i in 0..<self {
|
||||
func repetitions(task: () -> Void) {
|
||||
for _ in 0..<self {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个`repetitions`方法使用了一个`() -> ()`类型的单参数(single argument),表明函数没有参数而且没有返回值。
|
||||
这个 `repetitions(:_)` 方法接受一个 `() -> Void` 类型的单参数,表示没有参数且没有返回值的函数。
|
||||
|
||||
定义该扩展之后,你就可以对任意整数调用`repetitions`方法,实现的功能则是多次执行某任务:
|
||||
定义该扩展之后,你就可以对任意整数调用 `repetitions(_:)` 方法,将闭包中的任务执行整数对应的次数:
|
||||
|
||||
```swift
|
||||
3.repetitions({
|
||||
print("Hello!")
|
||||
})
|
||||
})
|
||||
// Hello!
|
||||
// Hello!
|
||||
// Hello!
|
||||
```
|
||||
|
||||
可以使用 trailing 闭包使调用更加简洁:
|
||||
可以使用尾随闭包让调用更加简洁:
|
||||
|
||||
```swift
|
||||
3.repetitions{
|
||||
3.repetitions {
|
||||
print("Goodbye!")
|
||||
}
|
||||
// Goodbye!
|
||||
@ -191,11 +197,11 @@ extension Int {
|
||||
```
|
||||
|
||||
<a name="mutating_instance_methods"></a>
|
||||
### 修改实例方法(Mutating Instance Methods)
|
||||
### 可变实例方法(Mutating Instance Methods)
|
||||
|
||||
通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改`self`或其属性的方法必须将该实例方法标注为`mutating`,正如来自原始实现的修改方法一样。
|
||||
通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改 `self` 或其属性的方法必须将该实例方法标注为 `mutating`,正如来自原始实现的可变方法一样。
|
||||
|
||||
下面的例子向Swift的`Int`类型添加了一个新的名为`square`的修改方法,来实现一个原始值的平方计算:
|
||||
下面的例子为 Swift 的 `Int` 类型添加了一个名为 `square` 的可变方法,用于计算原始值的平方值:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
@ -205,52 +211,51 @@ extension Int {
|
||||
}
|
||||
var someInt = 3
|
||||
someInt.square()
|
||||
// someInt 现在值是 9
|
||||
// someInt 的值现在是 9
|
||||
```
|
||||
|
||||
<a name="subscripts"></a>
|
||||
## 下标(Subscripts)
|
||||
|
||||
扩展可以向一个已有类型添加新下标。这个例子向Swift内建类型`Int`添加了一个整型下标。该下标`[n]`返回十进制数字从右向左数的第n个数字
|
||||
扩展可以为已有类型添加新下标。这个例子为 Swift 内建类型 `Int` 添加了一个整型下标。该下标 `[n]` 返回十进制数字从右向左数的第 `n` 个数字:
|
||||
|
||||
- 123456789[0]返回9
|
||||
- 123456789[1]返回8
|
||||
- `123456789[0]` 返回 `9`
|
||||
- `123456789[1]` 返回 `8`
|
||||
|
||||
...等等
|
||||
……以此类推。
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
subscript(var digitIndex: Int) -> Int {
|
||||
subscript(digitIndex: Int) -> Int {
|
||||
var decimalBase = 1
|
||||
while digitIndex > 0 {
|
||||
decimalBase *= 10
|
||||
--digitIndex
|
||||
}
|
||||
return (self / decimalBase) % 10
|
||||
for _ in 0..<digitIndex {
|
||||
decimalBase *= 10
|
||||
}
|
||||
return (self / decimalBase) % 10
|
||||
}
|
||||
}
|
||||
746381295[0]
|
||||
// returns 5
|
||||
// 返回 5
|
||||
746381295[1]
|
||||
// returns 9
|
||||
// 返回 9
|
||||
746381295[2]
|
||||
// returns 2
|
||||
// 返回 2
|
||||
746381295[8]
|
||||
// returns 7
|
||||
// 返回 7
|
||||
```
|
||||
|
||||
如果该`Int`值没有足够的位数,即下标越界,那么上述实现的下标会返回0,因为它会在数字左边自动补0:
|
||||
如果该 `Int` 值没有足够的位数,即下标越界,那么上述下标实现会返回 `0`,犹如在数字左边自动补 `0`:
|
||||
|
||||
```swift
|
||||
746381295[9]
|
||||
//returns 0, 即等同于:
|
||||
// 返回 0,即等同于:
|
||||
0746381295[9]
|
||||
```
|
||||
|
||||
<a name="nested_types"></a>
|
||||
## 嵌套类型(Nested Types)
|
||||
|
||||
扩展可以向已有的类、结构体和枚举添加新的嵌套类型:
|
||||
扩展可以为已有的类、结构体和枚举添加新的嵌套类型:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
@ -270,11 +275,11 @@ extension Int {
|
||||
}
|
||||
```
|
||||
|
||||
该例子向`Int`添加了新的嵌套枚举。这个名为`Kind`的枚举表示特定整数的类型。具体来说,就是表示整数是正数,零或者负数。
|
||||
该例子为 `Int` 添加了嵌套枚举。这个名为 `Kind` 的枚举表示特定整数的类型。具体来说,就是表示整数是正数、零或者负数。
|
||||
|
||||
这个例子还向`Int`添加了一个新的计算实例属性,即`kind`,用来返回合适的`Kind`枚举成员。
|
||||
这个例子还为 `Int` 添加了一个计算型实例属性,即 `kind`,用来根据整数返回适当的 `Kind` 枚举成员。
|
||||
|
||||
现在,这个嵌套枚举可以和一个`Int`值联合使用了:
|
||||
现在,这个嵌套枚举可以和任意 `Int` 值一起使用了:
|
||||
|
||||
|
||||
```swift
|
||||
@ -282,20 +287,20 @@ func printIntegerKinds(numbers: [Int]) {
|
||||
for number in numbers {
|
||||
switch number.kind {
|
||||
case .Negative:
|
||||
print("- ", appendNewline: false)
|
||||
print("- ", terminator: "")
|
||||
case .Zero:
|
||||
print("0 ", appendNewline: false)
|
||||
print("0 ", terminator: "")
|
||||
case .Positive:
|
||||
print("+ ", appendNewline: false)
|
||||
print("+ ", terminator: "")
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
|
||||
// prints "+ + - 0 - 0 +"
|
||||
// 打印 “+ + - 0 - 0 + ”
|
||||
```
|
||||
|
||||
函数`printIntegerKinds`的输入是一个`Int`数组值并对其字符进行迭代。在每次迭代过程中,考虑当前字符的`kind`计算属性,并打印出合适的类别描述。
|
||||
函数 `printIntegerKinds(_:)` 接受一个 `Int` 数组,然后对该数组进行迭代。在每次迭代过程中,对当前整数的计算型属性 `kind` 的值进行评估,并打印出适当的描述。
|
||||
|
||||
>注意:
|
||||
由于已知`number.kind `是`Int.Kind`型,所以`Int.Kind`中的所有成员值都可以使用`switch`语句里的形式简写,比如使用 `. Negative`代替`Int.Kind.Negative`。
|
||||
> 注意
|
||||
由于已知 `number.kind` 是 `Int.Kind` 类型,因此在 `switch` 语句中,`Int.Kind` 中的所有成员值都可以使用简写形式,例如使用 `. Negative` 而不是 `Int.Kind.Negative`。
|
||||
|
||||
@ -6,77 +6,82 @@
|
||||
> 校对:[dabing1022](https://github.com/dabing1022)
|
||||
|
||||
> 2.0
|
||||
> 翻译:[futantan](https://github.com/futantan)
|
||||
> 校对:[小铁匠Linus](https://github.com/kevin833752)
|
||||
> 定稿:[shanksyang](http://codebuild.me)
|
||||
> 翻译+校对:[futantan](https://github.com/futantan)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[小铁匠Linus](https://github.com/kevin833752)
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [协议的语法(Protocol Syntax)](#protocol_syntax)
|
||||
- [对属性的规定(Property Requirements)](#property_requirements)
|
||||
- [对方法的规定(Method Requirements)](#method_requirements)
|
||||
- [对Mutating方法的规定(Mutating Method Requirements)](#mutating_method_requirements)
|
||||
- [对构造器的规定(Initializer Requirements)](#initializer_requirements)
|
||||
- [协议类型(Protocols as Types)](#protocols_as_types)
|
||||
- [委托(代理)模式(Delegation)](#delegation)
|
||||
- [在扩展中添加协议成员(Adding Protocol Conformance with an Extension)](#adding_protocol_conformance_with_an_extension)
|
||||
- [通过扩展补充协议声明(Declaring Protocol Adoption with an Extension)](#declaring_protocol_adoption_with_an_extension)
|
||||
- [协议语法(Protocol Syntax)](#protocol_syntax)
|
||||
- [属性要求(Property Requirements)](#property_requirements)
|
||||
- [方法要求(Method Requirements)](#method_requirements)
|
||||
- [Mutating 方法要求(Mutating Method Requirements)](#mutating_method_requirements)
|
||||
- [构造器要求(Initializer Requirements)](#initializer_requirements)
|
||||
- [协议作为类型(Protocols as Types)](#protocols_as_types)
|
||||
- [委托(代理)模式(Delegation)](#delegation)
|
||||
- [通过扩展添加协议一致性(Adding Protocol Conformance with an Extension)](#adding_protocol_conformance_with_an_extension)
|
||||
- [通过扩展采纳协议(Declaring Protocol Adoption with an Extension)](#declaring_protocol_adoption_with_an_extension)
|
||||
- [协议类型的集合(Collections of Protocol Types)](#collections_of_protocol_types)
|
||||
- [协议的继承(Protocol Inheritance)](#protocol_inheritance)
|
||||
- [类专属协议(Class-Only Protocol)](#class_only_protocol)
|
||||
- [类类型专属协议(Class-Only Protocol)](#class_only_protocol)
|
||||
- [协议合成(Protocol Composition)](#protocol_composition)
|
||||
- [检验协议的一致性(Checking for Protocol Conformance)](#checking_for_protocol_conformance)
|
||||
- [对可选协议的规定(Optional Protocol Requirements)](#optional_protocol_requirements)
|
||||
- [检查协议一致性(Checking for Protocol Conformance)](#checking_for_protocol_conformance)
|
||||
- [可选的协议要求(Optional Protocol Requirements)](#optional_protocol_requirements)
|
||||
- [协议扩展(Protocol Extensions)](#protocol_extensions)
|
||||
|
||||
协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以采纳协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型“符合”这个协议。
|
||||
|
||||
`协议`定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为`遵循(conform)`这个协议。
|
||||
除了采纳协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样采纳协议的类型就能够使用这些功能。
|
||||
|
||||
除了遵循协议的类型必须实现那些指定的规定以外,还可以对协议进行扩展,实现一些特殊的规定或者一些附加的功能,使得遵循的类型能够收益。
|
||||
<a name="protocol_syntax"></a>
|
||||
## 协议的语法
|
||||
## 协议语法
|
||||
|
||||
协议的定义方式与类,结构体,枚举的定义非常相似。
|
||||
协议的定义方式与类、结构体和枚举的定义非常相似:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
// 协议内容
|
||||
// 这里是协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号`:`分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号`,`分隔。
|
||||
要让自定义类型采纳某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(`:`)分隔。采纳多个协议时,各协议之间用逗号(`,`)分隔:
|
||||
|
||||
```swift
|
||||
struct SomeStructure: FirstProtocol, AnotherProtocol {
|
||||
// 结构体内容
|
||||
// 这里是结构体的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。
|
||||
拥有父类的类在采纳协议时,应该将父类名放在协议名之前,以逗号分隔:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
|
||||
// 类的内容
|
||||
// 这里是类的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
<a name="property_requirements"></a>
|
||||
## 对属性的规定
|
||||
## 属性要求
|
||||
|
||||
协议可以规定其`遵循者`提供特定名称和类型的`实例属性(instance property)`或`类属性(type property)`,而不用指定是`存储型属性(stored property)`还是`计算型属性(calculate property)`。此外还必须指明是只读的还是可读可写的。
|
||||
协议可以要求采纳协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
|
||||
|
||||
如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的(gettable),那个属性不仅可以是只读的,如果你代码需要的话,也可以是可写的。
|
||||
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
|
||||
|
||||
协议中的通常用var来声明变量属性,在类型声明后加上`{ set get }`来表示属性是可读可写的,只读属性则用`{ get }`来表示。
|
||||
协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是可读可写的,可读属性则用 `{ get }` 来表示:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
var mustBeSettable : Int { get set }
|
||||
var mustBeSettable: Int { get set }
|
||||
var doesNotNeedToBeSettable: Int { get }
|
||||
}
|
||||
```
|
||||
|
||||
在协议中定义类属性(type property)时,总是使用`static`关键字作为前缀。当协议的遵循者是类时,可以使用`class`或`static`关键字来声明类属性:
|
||||
在协议中定义类型属性时,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字来声明类型属性:
|
||||
|
||||
```swift
|
||||
protocol AnotherProtocol {
|
||||
@ -84,7 +89,7 @@ protocol AnotherProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,这是一个含有一个实例属性要求的协议:
|
||||
如下所示,这是一个只含有一个实例属性要求的协议:
|
||||
|
||||
```swift
|
||||
protocol FullyNamed {
|
||||
@ -92,23 +97,23 @@ protocol FullyNamed {
|
||||
}
|
||||
```
|
||||
|
||||
`FullyNamed`协议除了要求协议的遵循者提供全名属性外,对协议对遵循者的类型并没有特别的要求。这个协议表示,任何遵循`FullyNamed`协议的类型,都具有一个可读的`String`类型实例属性`fullName`。
|
||||
`FullyNamed` 协议除了要求采纳协议的类型提供 `fullName` 属性外,并没有其他特别的要求。这个协议表示,任何采纳 `FullyNamed` 的类型,都必须有一个可读的 `String` 类型的实例属性 `fullName`。
|
||||
|
||||
下面是一个遵循`FullyNamed`协议的简单结构体:
|
||||
下面是一个采纳 `FullyNamed` 协议的简单结构体:
|
||||
|
||||
```swift
|
||||
struct Person: FullyNamed{
|
||||
struct Person: FullyNamed {
|
||||
var fullName: String
|
||||
}
|
||||
let john = Person(fullName: "John Appleseed")
|
||||
//john.fullName 为 "John Appleseed"
|
||||
// john.fullName 为 "John Appleseed"
|
||||
```
|
||||
|
||||
这个例子中定义了一个叫做`Person`的结构体,用来表示具有名字的人。从第一行代码中可以看出,它遵循了`FullyNamed`协议。
|
||||
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它采纳了 `FullyNamed` 协议。
|
||||
|
||||
`Person`结构体的每一个实例都有一个`String`类型的存储型属性`fullName`。这正好满足了`FullyNamed`协议的要求,也就意味着,`Person`结构体完整的`遵循`了协议。(如果协议要求未被完全满足,在编译时会报错)
|
||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。)
|
||||
|
||||
下面是一个更为复杂的类,它采用并遵循了`FullyNamed`协议:
|
||||
下面是一个更为复杂的类,它采纳并符合了 `FullyNamed` 协议:
|
||||
|
||||
```swift
|
||||
class Starship: FullyNamed {
|
||||
@ -126,14 +131,14 @@ var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
|
||||
// ncc1701.fullName 是 "USS Enterprise"
|
||||
```
|
||||
|
||||
`Starship`类把`fullName`属性实现为只读的计算型属性。每一个`Starship`类的实例都有一个名为`name`的属性和一个名为`prefix`的可选属性。 当`prefix`存在时,将`prefix`插入到`name`之前来为Starship构建`fullName`,`prefix`不存在时,则将直接用`name`构建`fullName`。
|
||||
`Starship` 类把 `fullName` 属性实现为只读的计算型属性。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算型属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而为星际飞船构建一个全名。
|
||||
|
||||
<a name="method_requirements"></a>
|
||||
## 对方法的规定
|
||||
## 方法要求
|
||||
|
||||
协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是在协议的方法定义中,不支持参数默认值。
|
||||
协议可以要求采纳协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值。
|
||||
|
||||
正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用`static`关键字作为前缀。当协议的遵循者是类的时候,你可以在类的实现中使用`class`或者`static`来实现类方法:
|
||||
正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字作为前缀:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
@ -141,7 +146,7 @@ protocol SomeProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子定义了含有一个实例方法的协议:
|
||||
下面的例子定义了一个只含有一个实例方法的协议:
|
||||
|
||||
```swift
|
||||
protocol RandomNumberGenerator {
|
||||
@ -149,12 +154,11 @@ protocol RandomNumberGenerator {
|
||||
}
|
||||
```
|
||||
|
||||
`RandomNumberGenerator`协议要求其遵循者必须拥有一个名为`random`, 返回值类型为`Double`的实例方法。尽管这里并未指明,但是我们假设返回值在[0,1)区间内。
|
||||
`RandomNumberGenerator` 协议要求采纳协议的类型必须拥有一个名为 `random`, 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值在 `[0.0,1.0)` 区间内。
|
||||
|
||||
`RandomNumberGenerator`协议并不在意每一个随机数是怎样生成的,它只强调这里有一个随机数生成器。
|
||||
|
||||
如下所示,下边的是一个遵循了`RandomNumberGenerator`协议的类。该类实现了一个叫做*线性同余生成器(linear congruential generator)*的伪随机数算法。
|
||||
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
|
||||
|
||||
如下所示,下边是一个采纳并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。
|
||||
|
||||
```swift
|
||||
class LinearCongruentialGenerator: RandomNumberGenerator {
|
||||
@ -169,25 +173,24 @@ class LinearCongruentialGenerator: RandomNumberGenerator {
|
||||
}
|
||||
let generator = LinearCongruentialGenerator()
|
||||
print("Here's a random number: \(generator.random())")
|
||||
// 输出 : "Here's a random number: 0.37464991998171"
|
||||
// 打印 “Here's a random number: 0.37464991998171”
|
||||
print("And another one: \(generator.random())")
|
||||
// 输出 : "And another one: 0.729023776863283"
|
||||
// 打印 “And another one: 0.729023776863283”
|
||||
```
|
||||
|
||||
<a name="mutating_method_requirements"></a>
|
||||
## 对Mutating方法的规定
|
||||
## Mutating 方法要求
|
||||
|
||||
有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将`mutating`关键字作为函数的前缀,写在`func`之前,表示可以在该方法中修改它所属的实例及其实例属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。
|
||||
有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。
|
||||
|
||||
如果你在协议中定义了一个方法旨在改变遵循该协议的实例,那么在协议定义时需要在方法前加`mutating`关键字。这使得结构和枚举遵循协议并满足此方法要求。
|
||||
如果你在协议中定义了一个实例方法,该方法会改变采纳该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够采纳此协议并满足此方法要求。
|
||||
|
||||
> 注意
|
||||
> 实现协议中的 `mutating` 方法时,若是类类型,则不用写 `mutating` 关键字。而对于结构体和枚举,则必须写 `mutating` 关键字。
|
||||
|
||||
>注意:
|
||||
>用类实现协议中的`mutating`方法时,不用写`mutating`关键字;用结构体,枚举实现协议中的`mutating`方法时,必须写`mutating`关键字。
|
||||
如下所示,`Togglable` 协议只要求实现一个名为 `toggle` 的实例方法。根据名称的暗示,`toggle()` 方法将改变实例属性,从而切换采纳该协议类型的实例的状态。
|
||||
|
||||
如下所示,`Togglable`协议含有名为`toggle`的实例方法。根据名称推测,`toggle()`方法将通过改变实例属性,来切换遵循该协议的实例的状态。
|
||||
|
||||
`toggle()`方法在定义的时候,使用`mutating`关键字标记,这表明当它被调用时该方法将会改变协议遵循者实例的状态:
|
||||
`toggle()` 方法在定义的时候,使用 `mutating` 关键字标记,这表明当它被调用时,该方法将会改变采纳协议的类型的实例:
|
||||
|
||||
```swift
|
||||
protocol Togglable {
|
||||
@ -195,9 +198,9 @@ protocol Togglable {
|
||||
}
|
||||
```
|
||||
|
||||
当使用`枚举`或`结构体`来实现`Togglable`协议时,需要提供一个带有`mutating`前缀的`toggle`方法。
|
||||
当使用枚举或结构体来实现 `Togglable` 协议时,需要提供一个带有 `mutating` 前缀的 `toggle()` 方法。
|
||||
|
||||
下面定义了一个名为`OnOffSwitch`的枚举类型。这个枚举类型在两种状态之间进行切换,用枚举成员`On`和`Off`表示。枚举类型的`toggle`方法被标记为`mutating`以满足`Togglable`协议的要求:
|
||||
下面定义了一个名为 `OnOffSwitch` 的枚举。这个枚举在两种状态之间进行切换,用枚举成员 `On` 和 `Off` 表示。枚举的 `toggle()` 方法被标记为 `mutating`,以满足 `Togglable` 协议的要求:
|
||||
|
||||
```swift
|
||||
enum OnOffSwitch: Togglable {
|
||||
@ -213,13 +216,13 @@ enum OnOffSwitch: Togglable {
|
||||
}
|
||||
var lightSwitch = OnOffSwitch.Off
|
||||
lightSwitch.toggle()
|
||||
//lightSwitch 现在的值为 .On
|
||||
// lightSwitch 现在的值为 .On
|
||||
```
|
||||
|
||||
<a name="initializer_requirements"></a>
|
||||
## 对构造器的规定
|
||||
## 构造器要求
|
||||
|
||||
协议可以要求它的遵循者实现指定的构造器。你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
|
||||
协议可以要求采纳协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
@ -227,70 +230,68 @@ protocol SomeProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
### 协议构造器规定在类中的实现
|
||||
### 构造器要求在类中的实现
|
||||
|
||||
你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器(designated initializer)或者便利构造器(convenience initializer)。在这两种情况下,你都必须给构造器实现标上"required"修饰符:
|
||||
你可以在采纳协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeProtocol {
|
||||
required init(someParameter: Int) {
|
||||
//构造器实现
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用`required`修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。
|
||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
|
||||
|
||||
关于`required`构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
|
||||
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
|
||||
|
||||
>注意
|
||||
>如果类已经被标记为`final`,那么不需要在协议构造器的实现中使用`required`修饰符。因为final类不能有子类。关于`final`修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。
|
||||
> 注意
|
||||
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。
|
||||
|
||||
如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示`required`和`override`修饰符:
|
||||
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required` 和 `override` 修饰符:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
init()
|
||||
}
|
||||
|
||||
|
||||
class SomeSuperClass {
|
||||
init() {
|
||||
// 构造器的实现
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SomeSubClass: SomeSuperClass, SomeProtocol {
|
||||
// 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"
|
||||
// 因为采纳协议,需要加上 required
|
||||
// 因为继承自父类,需要加上 override
|
||||
required override init() {
|
||||
// 构造器实现
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 可失败构造器的规定
|
||||
### 可失败构造器要求
|
||||
|
||||
可以通过给协议`Protocols`中添加[可失败构造器](./14_Initialization.html#failable_initializers)来使遵循该协议的类型必须实现该可失败构造器。
|
||||
|
||||
如果在协议中定义一个可失败构造器,则在遵顼该协议的类型中必须添加同名同参数的可失败构造器或非可失败构造器。如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器(`init!`)。
|
||||
协议还可以为采纳协议的类型定义可失败构造器要求,详见[可失败构造器](./14_Initialization.html#failable_initializers)。
|
||||
|
||||
采纳协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。
|
||||
|
||||
<a name="protocols_as_types"></a>
|
||||
## 协议类型
|
||||
## 协议作为类型
|
||||
|
||||
尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
|
||||
尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。
|
||||
|
||||
协议可以像其他普通类型一样使用,使用场景:
|
||||
协议可以像其他普通类型一样使用,使用场景如下:
|
||||
|
||||
* 作为函数、方法或构造器中的参数类型或返回值类型
|
||||
* 作为常量、变量或属性的类型
|
||||
* 作为数组、字典或其他容器中的元素类型
|
||||
|
||||
> 注意
|
||||
> 协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed`和`RandomNumberGenerator`)
|
||||
> 协议是一种类型,因此协议类型的名称应与其他类型(例如 `Int`,`Double`,`String`)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed` 和 `RandomNumberGenerator`)。
|
||||
|
||||
如下所示,这个示例中将协议当做类型来使用:
|
||||
下面是将协议作为类型使用的例子:
|
||||
|
||||
```swift
|
||||
class Dice {
|
||||
@ -306,35 +307,34 @@ class Dice {
|
||||
}
|
||||
```
|
||||
|
||||
例子中定义了一个`Dice`类,用来代表桌游中的拥有N个面的骰子。`Dice`的实例含有`sides`和`generator`两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器。
|
||||
例子中定义了一个 `Dice` 类,用来代表桌游中拥有 N 个面的骰子。`Dice` 的实例含有 `sides` 和 `generator` 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器,从而生成随机点数。
|
||||
|
||||
`generator`属性的类型为`RandomNumberGenerator`,因此任何遵循了`RandomNumberGenerator`协议的类型的实例都可以赋值给`generator`,除此之外,无其他要求。
|
||||
`generator` 属性的类型为 `RandomNumberGenerator`,因此任何采纳了 `RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。
|
||||
|
||||
`Dice`类中也有一个构造器(initializer),用来进行初始化操作。构造器中含有一个名为`generator`,类型为`RandomNumberGenerator`的形参。在调用构造方法时创建`Dice`的实例时,可以传入任何遵循`RandomNumberGenerator`协议的实例给generator。
|
||||
`Dice` 类还有一个构造器,用来设置初始状态。构造器有一个名为 `generator`,类型为 `RandomNumberGenerator` 的形参。在调用构造方法创建 `Dice` 的实例时,可以传入任何采纳 `RandomNumberGenerator` 协议的实例给 `generator`。
|
||||
|
||||
`Dice`类也提供了一个名为`roll`的实例方法用来模拟骰子的面值。它先使用`generator`的`random()`方法来创建一个[0,1)区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为generator遵循了`RandomNumberGenerator`协议,因而保证了`random`方法可以被调用。
|
||||
`Dice` 类提供了一个名为 `roll` 的实例方法,用来模拟骰子的面值。它先调用 `generator` 的 `random()` 方法来生成一个 `[0.0,1.0)` 区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为 `generator` 采纳了 `RandomNumberGenerator` 协议,可以确保它有个 `random()` 方法可供调用。
|
||||
|
||||
下面的例子展示了如何使用`LinearCongruentialGenerator`的实例作为随机数生成器创建一个六面骰子:
|
||||
下面的例子展示了如何使用 `LinearCongruentialGenerator` 的实例作为随机数生成器来创建一个六面骰子:
|
||||
|
||||
```swift
|
||||
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
|
||||
for _ in 1...5 {
|
||||
print("Random dice roll is \(d6.roll())")
|
||||
}
|
||||
//输出结果
|
||||
//Random dice roll is 3
|
||||
//Random dice roll is 5
|
||||
//Random dice roll is 4
|
||||
//Random dice roll is 5
|
||||
//Random dice roll is 4
|
||||
// Random dice roll is 3
|
||||
// Random dice roll is 5
|
||||
// Random dice roll is 4
|
||||
// Random dice roll is 5
|
||||
// Random dice roll is 4
|
||||
```
|
||||
|
||||
<a name="delegation"></a>
|
||||
## 委托(代理)模式
|
||||
## 委托(代理)模式
|
||||
|
||||
委托是一种设计模式,它允许`类`或`结构体`将一些需要它们负责的功能`交由(或委托)`给其他的类型的实例。委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法,使其`遵循者`拥有这些被委托的`函数和方法`。委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源的类型信息。
|
||||
委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保采纳协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
|
||||
|
||||
下面的例子是两个基于骰子游戏的协议:
|
||||
下面的例子定义了两个基于骰子游戏的协议:
|
||||
|
||||
```swift
|
||||
protocol DiceGame {
|
||||
@ -349,9 +349,9 @@ protocol DiceGameDelegate {
|
||||
}
|
||||
```
|
||||
|
||||
`DiceGame`协议可以在任意含有骰子的游戏中实现。`DiceGameDelegate`协议可以用来追踪`DiceGame`的游戏过程。
|
||||
`DiceGame` 协议可以被任意涉及骰子的游戏采纳。`DiceGameDelegate` 协议可以被任意类型采纳,用来追踪 `DiceGame` 的游戏过程。
|
||||
|
||||
如下所示,`SnakesAndLadders`是`Snakes and Ladders`([Control Flow](./05_Control_Flow.html)章节有该游戏的详细介绍)游戏的新版本。新版本使用`Dice`作为骰子,并且实现了`DiceGame`和`DiceGameDelegate`协议,后者用来记录游戏的过程:
|
||||
如下所示,`SnakesAndLadders` 是 [控制流](./05_Control_Flow.html) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame` 和 `DiceGameDelegate` 协议,后者用来记录游戏的过程:
|
||||
|
||||
```swift
|
||||
class SnakesAndLadders: DiceGame {
|
||||
@ -370,15 +370,15 @@ class SnakesAndLadders: DiceGame {
|
||||
delegate?.gameDidStart(self)
|
||||
gameLoop: while square != finalSquare {
|
||||
let diceRoll = dice.roll()
|
||||
delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll)
|
||||
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
|
||||
switch square + diceRoll {
|
||||
case finalSquare:
|
||||
break gameLoop
|
||||
case let newSquare where newSquare > finalSquare:
|
||||
continue gameLoop
|
||||
default:
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
delegate?.gameDidEnd(self)
|
||||
@ -386,17 +386,19 @@ class SnakesAndLadders: DiceGame {
|
||||
}
|
||||
```
|
||||
|
||||
这个版本的游戏封装到了`SnakesAndLadders`类中,该类遵循了`DiceGame`协议,并且提供了相应的可读的`dice`属性和`play`实例方法。(`dice`属性在构造之后就不再改变,且协议只要求`dice`为只读的,因此将`dice`声明为常量属性。)
|
||||
关于这个蛇梯棋游戏的详细描述请参阅 [控制流](./05_Control_Flow.html) 章节中的 [Break](./05_Control_Flow.html#break) 部分。
|
||||
|
||||
游戏使用`SnakesAndLadders`类的`构造器(initializer)`初始化游戏。所有的游戏逻辑被转移到了协议中的`play`方法,`play`方法使用协议规定的`dice`属性提供骰子摇出的值。
|
||||
这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类采纳了 `DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。)
|
||||
|
||||
注意:`delegate`并不是游戏的必备条件,因此`delegate`被定义为遵循`DiceGameDelegate`协议的可选属性。因为`delegate`是可选值,因此在初始化的时候被自动赋值为`nil`。随后,可以在游戏中为`delegate`设置适当的值。
|
||||
游戏使用 `SnakesAndLadders` 类的 `init()` 构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的 `play()` 方法,`play()` 方法使用协议要求的 `dice` 属性提供骰子摇出的值。
|
||||
|
||||
`DicegameDelegate`协议提供了三个方法用来追踪游戏过程。被放置于游戏的逻辑中,即`play()`方法内。分别在游戏开始时,新一轮开始时,游戏结束时被调用。
|
||||
注意,`delegate` 并不是游戏的必备条件,因此 `delegate` 被定义为 `DiceGameDelegate` 类型的可选属性。因为 `delegate` 是可选值,因此会被自动赋予初始值 `nil`。随后,可以在游戏中为 `delegate` 设置适当的值。
|
||||
|
||||
因为`delegate`是一个遵循`DiceGameDelegate`的可选属性,因此在`play()`方法中使用了`可选链`来调用委托方法。 若`delegate`属性为`nil`, 则delegate所调用的方法失效,并不会产生错误。若`delegate`不为`nil`,则方法能够被调用
|
||||
`DicegameDelegate` 协议提供了三个方法用来追踪游戏过程。这三个方法被放置于游戏的逻辑中,即 `play()` 方法内。分别在游戏开始时,新一轮开始时,以及游戏结束时被调用。
|
||||
|
||||
如下所示,`DiceGameTracker`遵循了`DiceGameDelegate`协议:
|
||||
因为 `delegate` 是一个 `DiceGameDelegate` 类型的可选属性,因此在 `play()` 方法中通过可选链式调用来调用它的方法。若 `delegate` 属性为 `nil`,则调用方法会优雅地失败,并不会产生错误。若 `delegate` 不为 `nil`,则方法能够被调用,并传递 `SnakesAndLadders` 实例作为参数。
|
||||
|
||||
如下示例定义了 `DiceGameTracker` 类,它采纳了 `DiceGameDelegate` 协议:
|
||||
|
||||
```swift
|
||||
class DiceGameTracker: DiceGameDelegate {
|
||||
@ -409,7 +411,7 @@ class DiceGameTracker: DiceGameDelegate {
|
||||
print("The game is using a \(game.dice.sides)-sided dice")
|
||||
}
|
||||
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
|
||||
++numberOfTurns
|
||||
numberOfTurns += 1
|
||||
print("Rolled a \(diceRoll)")
|
||||
}
|
||||
func gameDidEnd(game: DiceGame) {
|
||||
@ -418,37 +420,37 @@ class DiceGameTracker: DiceGameDelegate {
|
||||
}
|
||||
```
|
||||
|
||||
`DiceGameTracker`实现了`DiceGameDelegate`协议规定的三个方法,用来记录游戏已经进行的轮数。 当游戏开始时,`numberOfTurns`属性被赋值为0; 在每新一轮中递增; 游戏结束后,输出打印游戏的总轮数。
|
||||
`DiceGameTracker` 实现了 `DiceGameDelegate` 协议要求的三个方法,用来记录游戏已经进行的轮数。当游戏开始时,`numberOfTurns` 属性被赋值为 `0`,然后在每新一轮中递增,游戏结束后,打印游戏的总轮数。
|
||||
|
||||
`gameDidStart`方法从`game`参数获取游戏信息并输出。`game`在方法中被当做`DiceGame`类型而不是`SnakeAndLadders`类型,所以方法中只能访问`DiceGame`协议中的成员。当然了,这些方法也可以在类型转换之后调用。在上例代码中,通过`is`操作符检查`game`是否为 `SnakesAndLadders`类型的实例,如果是,则打印出相应的内容。
|
||||
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
|
||||
|
||||
无论当前进行的是何种游戏,`game`都遵循`DiceGame`协议以确保`game`含有`dice`属性,因此在`gameDidStart(_:)`方法中可以通过传入的`game`参数来访问`dice`属性,进而打印出`dice`的`sides`属性的值。
|
||||
无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。
|
||||
|
||||
`DiceGameTracker`的运行情况,如下所示:
|
||||
`DiceGameTracker` 的运行情况如下所示:
|
||||
|
||||
```swift
|
||||
let tracker = DiceGameTracker()
|
||||
let game = SnakesAndLadders()
|
||||
game.delegate = tracker
|
||||
game.play()
|
||||
// 开始一个新的Snakes and Ladders的游戏
|
||||
// 游戏使用 6 面的骰子
|
||||
// 翻转得到 3
|
||||
// 翻转得到 5
|
||||
// 翻转得到 4
|
||||
// 翻转得到 5
|
||||
// 游戏进行了 4 轮
|
||||
// Started a new game of Snakes and Ladders
|
||||
// The game is using a 6-sided dice
|
||||
// Rolled a 3
|
||||
// Rolled a 5
|
||||
// Rolled a 4
|
||||
// Rolled a 5
|
||||
// The game lasted for 4 turns
|
||||
```
|
||||
|
||||
<a name="adding_protocol_conformance_with_an_extension"></a>
|
||||
## 在扩展中添加协议成员
|
||||
## 通过扩展添加协议一致性
|
||||
|
||||
即便无法修改源代码,依然可以通过扩展(Extension)来扩充已存在类型(*译者注: 类,结构体,枚举等*)。扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。详情请在[扩展](./21_Extensions.html)章节中查看。
|
||||
即便无法修改源代码,依然可以通过扩展令已有类型采纳并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./21_Extensions.html)章节中查看。
|
||||
|
||||
> 注意
|
||||
> 通过扩展为已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法
|
||||
> 通过扩展令已有类型采纳并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
|
||||
|
||||
例如`TextRepresentable`协议,任何想要表示一些文本内容的类型都可以遵循该协议。这些想要表示的内容可以是类型本身的描述,也可以是当前内容的版本:
|
||||
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
|
||||
|
||||
```swift
|
||||
protocol TextRepresentable {
|
||||
@ -456,7 +458,7 @@ protocol TextRepresentable {
|
||||
}
|
||||
```
|
||||
|
||||
可以通过扩展,为上一节中提到的`Dice`增加类遵循`TextRepresentable`协议的功能:
|
||||
可以通过扩展,令先前提到的 `Dice` 类采纳并符合 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension Dice: TextRepresentable {
|
||||
@ -465,17 +467,18 @@ extension Dice: TextRepresentable {
|
||||
}
|
||||
}
|
||||
```
|
||||
现在,通过扩展使得`Dice`类型遵循了一个新的协议,这和`Dice`类型在定义的时候声明为遵循`TextRepresentable`协议的效果相同。在扩展的时候,协议名称写在类型名之后,以冒号隔开,在大括号内写明新添加的协议内容。
|
||||
|
||||
现在所有`Dice`的实例都遵循了`TextRepresentable`协议:
|
||||
通过扩展采纳并符合协议,和在原始定义中采纳并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
|
||||
|
||||
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
|
||||
|
||||
```swift
|
||||
let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator())
|
||||
print(d12. textualDescription)
|
||||
// 输出 "A 12-sided dice"
|
||||
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
|
||||
print(d12.textualDescription)
|
||||
// 打印 “A 12-sided dice”
|
||||
```
|
||||
|
||||
同样`SnakesAndLadders`类也可以通过`扩展`的方式来遵循`TextRepresentable`协议:
|
||||
同样,`SnakesAndLadders` 类也可以通过扩展采纳并符合 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: TextRepresentable {
|
||||
@ -484,13 +487,13 @@ extension SnakesAndLadders: TextRepresentable {
|
||||
}
|
||||
}
|
||||
print(game.textualDescription)
|
||||
// 输出 "A game of Snakes and Ladders with 25 squares"
|
||||
// 打印 “A game of Snakes and Ladders with 25 squares”
|
||||
```
|
||||
|
||||
<a name="declaring_protocol_adoption_with_an_extension"></a>
|
||||
## 通过扩展补充协议声明
|
||||
## 通过扩展采纳协议
|
||||
|
||||
当一个类型已经实现了协议中的所有要求,却没有声明为遵循该协议时,可以通过扩展(空的扩展体)来补充协议声明:
|
||||
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展来采纳该协议:
|
||||
|
||||
```swift
|
||||
struct Hamster {
|
||||
@ -502,53 +505,52 @@ struct Hamster {
|
||||
extension Hamster: TextRepresentable {}
|
||||
```
|
||||
|
||||
从现在起,`Hamster`的实例可以作为`TextRepresentable`类型使用:
|
||||
从现在起,`Hamster` 的实例可以作为 `TextRepresentable` 类型使用:
|
||||
|
||||
```swift
|
||||
let simonTheHamster = Hamster(name: "Simon")
|
||||
let somethingTextRepresentable: TextRepresentable = simonTheHamster
|
||||
print(somethingTextRepresentable.textualDescription)
|
||||
// 输出 "A hamster named Simon"
|
||||
// 打印 “A hamster named Simon”
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 即使满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出显式的协议声明。
|
||||
> 即使满足了协议的所有要求,类型也不会自动采纳协议,必须显式地采纳协议。
|
||||
|
||||
<a name="collections_of_protocol_types"></a>
|
||||
## 协议类型的集合
|
||||
|
||||
协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./22_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个类型为`TextRepresentable`的数组:
|
||||
协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./22_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
|
||||
|
||||
```swift
|
||||
let things: [TextRepresentable] = [game,d12,simonTheHamster]
|
||||
let things: [TextRepresentable] = [game, d12, simonTheHamster]
|
||||
```
|
||||
|
||||
如下所示,`things`数组可以被直接遍历,并打印每个元素的文本表示:
|
||||
如下所示,可以遍历 `things` 数组,并打印每个元素的文本表示:
|
||||
|
||||
```swift
|
||||
for thing in things {
|
||||
print(thing.textualDescription)
|
||||
}
|
||||
// 输出:
|
||||
// A game of Snakes and Ladders with 25 squares
|
||||
// A 12-sided dice
|
||||
// A hamster named Simon
|
||||
```
|
||||
|
||||
`thing`被当做是`TextRepresentable`类型而不是`Dice`,`DiceGame`,`Hamster`等类型,即使真实的实例是它们中的一种类型。尽管如此,由于它是`TextRepresentable`类型,任何`TextRepresentable`都拥有一个`textualDescription`属性,所以每次循环访问`thing.textualDescription`是安全的。
|
||||
`thing` 是 `TextRepresentable` 类型而不是 `Dice`,`DiceGame`,`Hamster` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing` 是 `TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`。
|
||||
|
||||
<a name="protocol_inheritance"></a>
|
||||
## 协议的继承
|
||||
|
||||
协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
|
||||
协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
|
||||
|
||||
```swift
|
||||
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
|
||||
// 协议定义
|
||||
// 这里是协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,`PrettyTextRepresentable`协议继承了`TextRepresentable`协议:
|
||||
如下所示,`PrettyTextRepresentable` 协议继承了 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
protocol PrettyTextRepresentable: TextRepresentable {
|
||||
@ -556,9 +558,9 @@ protocol PrettyTextRepresentable: TextRepresentable {
|
||||
}
|
||||
```
|
||||
|
||||
例子中定义了一个新的协议`PrettyTextRepresentable`,它继承自`TextRepresentable`协议。任何遵循`PrettyTextRepresentable`协议的类型在满足该协议的要求时,也必须满足`TextRepresentable`协议的要求。在这个例子中,`PrettyTextRepresentable`协议要求其遵循者提供一个返回值为`String`类型的`prettyTextualDescription`属性。
|
||||
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何采纳 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求采纳协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
|
||||
|
||||
如下所示,扩展`SnakesAndLadders`,让其遵循`PrettyTextRepresentable`协议:
|
||||
如下所示,扩展 `SnakesAndLadders`,使其采纳并符合 `PrettyTextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: PrettyTextRepresentable {
|
||||
@ -566,7 +568,7 @@ extension SnakesAndLadders: PrettyTextRepresentable {
|
||||
var output = textualDescription + ":\n"
|
||||
for index in 1...finalSquare {
|
||||
switch board[index] {
|
||||
case let ladder where ladder > 0:
|
||||
case let ladder where ladder > 0:
|
||||
output += "▲ "
|
||||
case let snake where snake < 0:
|
||||
output += "▼ "
|
||||
@ -579,42 +581,42 @@ extension SnakesAndLadders: PrettyTextRepresentable {
|
||||
}
|
||||
```
|
||||
|
||||
上述扩展使得`SnakesAndLadders`遵循了`PrettyTextRepresentable`协议,并为每个`SnakesAndLadders`类型提供了协议要求的`prettyTextualDescription`属性。每个`PrettyTextRepresentable`类型同时也是`TextRepresentable`类型,所以在`prettyTextualDescription`的实现中,可以调用`textualDescription`属性。之后在每一行加上换行符,作为输出的开始。然后遍历数组中的元素,输出一个几何图形来表示遍历的结果:
|
||||
上述扩展令 `SnakesAndLadders` 采纳了 `PrettyTextRepresentable` 协议,并提供了协议要求的 `prettyTextualDescription` 属性。每个 `PrettyTextRepresentable` 类型同时也是 `TextRepresentable` 类型,所以在 `prettyTextualDescription` 的实现中,可以访问 `textualDescription` 属性。然后,拼接上了冒号和换行符。接着,遍历数组中的元素,拼接一个几何图形来表示每个棋盘方格的内容:
|
||||
|
||||
* 当从数组中取出的元素的值大于0时,用`▲`表示
|
||||
* 当从数组中取出的元素的值小于0时,用`▼`表示
|
||||
* 当从数组中取出的元素的值等于0时,用`○`表示
|
||||
* 当从数组中取出的元素的值大于 `0` 时,用 `▲` 表示。
|
||||
* 当从数组中取出的元素的值小于 `0` 时,用 `▼` 表示。
|
||||
* 当从数组中取出的元素的值等于 `0` 时,用 `○` 表示。
|
||||
|
||||
任意`SankesAndLadders`的实例都可以使用`prettyTextualDescription`属性。
|
||||
任意 `SankesAndLadders` 的实例都可以使用 `prettyTextualDescription` 属性来打印一个漂亮的文本描述:
|
||||
|
||||
```swift
|
||||
print(game.prettyTextualDescription)
|
||||
// A game of Snakes and Ladders with 25 squares:TODO
|
||||
// A game of Snakes and Ladders with 25 squares:
|
||||
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
|
||||
```
|
||||
|
||||
<a name="class_only_protocol"></a>
|
||||
## 类专属协议
|
||||
你可以在协议的继承列表中,通过添加`class`关键字,限制协议只能适配到类(class)类型。(结构体或枚举不能遵循该协议)。该`class`关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。
|
||||
## 类类型专属协议
|
||||
|
||||
你可以在协议的继承列表中,通过添加 `class` 关键字来限制协议只能被类类型采纳,而结构体或枚举不能采纳该协议。`class` 关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:
|
||||
|
||||
```swift
|
||||
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
|
||||
// 协议定义
|
||||
// 这里是类类型专属协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
在以上例子中,协议`SomeClassOnlyProtocol`只能被类(class)类型适配。如果尝试让结构体或枚举类型适配该协议,则会出现编译错误。
|
||||
|
||||
>注意
|
||||
>当协议想要定义的行为,要求(或假设)它的遵循类型必须是引用语义而非值语义时,应该采用类专属协议。关于引用语义,值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)和[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。
|
||||
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳该协议,则会导致编译错误。
|
||||
|
||||
> 注意
|
||||
> 当协议定义的要求需要采纳协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)和[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。
|
||||
|
||||
<a name="protocol_composition"></a>
|
||||
## 协议合成
|
||||
|
||||
有时候需要同时遵循多个协议。你可以将多个协议采用`protocol<SomeProtocol, AnotherProtocol>`这样的格式进行组合,称为`协议合成(protocol composition)`。你可以在`<>`中罗列任意多个你想要遵循的协议,以逗号分隔。
|
||||
有时候需要同时采纳多个协议,你可以将多个协议采用 `protocol<SomeProtocol, AnotherProtocol>` 这样的格式进行组合,称为 *协议合成(protocol composition)*。你可以在 `<>` 中罗列任意多个你想要采纳的协议,以逗号分隔。
|
||||
|
||||
下面的例子中,将`Named`和`Aged`两个协议按照上述的语法组合成一个协议:
|
||||
下面的例子中,将 `Named` 和 `Aged` 两个协议按照上述语法组合成一个协议,作为函数参数的类型:
|
||||
|
||||
```swift
|
||||
protocol Named {
|
||||
@ -632,28 +634,28 @@ func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
|
||||
}
|
||||
let birthdayPerson = Person(name: "Malcolm", age: 21)
|
||||
wishHappyBirthday(birthdayPerson)
|
||||
// 输出 "Happy birthday Malcolm - you're 21!
|
||||
// 打印 “Happy birthday Malcolm - you're 21!”
|
||||
```
|
||||
|
||||
`Named`协议包含`String`类型的`name`属性;`Aged`协议包含`Int`类型的`age`属性。`Person`结构体`遵循`了这两个协议。
|
||||
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。
|
||||
|
||||
`wishHappyBirthday`函数的形参`celebrator`的类型为`protocol<Named,Aged>`。可以传入任意`遵循`这两个协议的类型的实例。
|
||||
`wishHappyBirthday(_:)` 函数的参数 `celebrator` 的类型为 `protocol<Named,Aged>`。这意味着它不关心参数的具体类型,只要参数符合这两个协议即可。
|
||||
|
||||
上面的例子创建了一个名为`birthdayPerson`的`Person`实例,作为参数传递给了`wishHappyBirthday(_:)`函数。因为`Person`同时遵循这两个协议,所以这个参数合法,函数将输出生日问候语。
|
||||
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(_:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
|
||||
|
||||
> 注意
|
||||
> `协议合成`并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。
|
||||
> 协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。
|
||||
|
||||
<a name="checking_for_protocol_conformance"></a>
|
||||
## 检验协议的一致性
|
||||
## 检查协议一致性
|
||||
|
||||
你可以使用`is`和`as`操作符来检查是否遵循某一协议或强制转化为某一类型。检查和转化的语法和之前相同(*详情查看[类型转换](./20_Type_Casting.html)*):
|
||||
你可以使用[类型转换](./20_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
|
||||
|
||||
* `is`操作符用来检查实例是否`遵循`了某个`协议`。
|
||||
* `as?`返回一个可选值,当实例`遵循`协议时,返回该协议类型;否则返回`nil`。
|
||||
* `as`用以强制向下转型,如果强转失败,会引起运行时错误。
|
||||
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`。
|
||||
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。
|
||||
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
|
||||
|
||||
下面的例子定义了一个`HasArea`的协议,要求有一个`Double`类型可读的`area`:
|
||||
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`:
|
||||
|
||||
```swift
|
||||
protocol HasArea {
|
||||
@ -661,7 +663,7 @@ protocol HasArea {
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,定义了`Circle`和`Country`类,它们都遵循了`HasArea`协议:
|
||||
如下所示,`Circle` 类和 `Country` 类都采纳了 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
class Circle: HasArea {
|
||||
@ -676,9 +678,9 @@ class Country: HasArea {
|
||||
}
|
||||
```
|
||||
|
||||
`Circle`类把`area`实现为基于`存储型属性`radius的`计算型属性`,`Country`类则把`area`实现为`存储型属性`。这两个类都`遵循`了`HasArea`协议。
|
||||
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地符合了 `HasArea` 协议。
|
||||
|
||||
如下所示,`Animal`是一个没有实现`HasArea`协议的类:
|
||||
如下所示,`Animal` 是一个未采纳 `HasArea` 协议的类:
|
||||
|
||||
```swift
|
||||
class Animal {
|
||||
@ -687,7 +689,7 @@ class Animal {
|
||||
}
|
||||
```
|
||||
|
||||
`Circle`,`Country`,`Animal`并没有一个相同的基类,然而,它们都是类,它们的实例都可以作为`AnyObject`类型的变量,存储在同一个数组中:
|
||||
`Circle`,`Country`,`Animal` 并没有一个共同的基类,尽管如此,它们都是类,它们的实例都可以作为 `AnyObject` 类型的值,存储在同一个数组中:
|
||||
|
||||
```swift
|
||||
let objects: [AnyObject] = [
|
||||
@ -697,9 +699,9 @@ let objects: [AnyObject] = [
|
||||
]
|
||||
```
|
||||
|
||||
`objects`数组使用字面量初始化,数组包含一个`radius`为2的`Circle`的实例,一个保存了英国面积的`Country`实例和一个`legs`为4的`Animal`实例。
|
||||
`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。
|
||||
|
||||
如下所示,`objects`数组可以被迭代,对迭代出的每一个元素进行检查,看它是否遵循了`HasArea`协议:
|
||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
for object in objects {
|
||||
@ -714,22 +716,23 @@ for object in objects {
|
||||
// Something that doesn't have an area
|
||||
```
|
||||
|
||||
当迭代出的元素遵循`HasArea`协议时,通过`as?`操作符将其`可选绑定(optional binding)`到`objectWithArea`常量上。`objectWithArea`是`HasArea`协议类型的实例,因此`area`属性是可以被访问和打印的。
|
||||
当迭代出的元素符合 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
|
||||
|
||||
`objects`数组中元素的类型并不会因为强转而丢失类型信息,它们仍然是`Circle`,`Country`,`Animal`类型。然而,当它们被赋值给`objectWithArea`常量时,则只被视为`HasArea`类型,因此只有`area`属性能够被访问。
|
||||
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle`,`Country`,`Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
|
||||
|
||||
<a name="optional_protocol_requirements"></a>
|
||||
## 对可选协议的规定
|
||||
协议可以含有可选成员,其`遵循者`可以选择是否实现这些成员。在协议中使用`optional`关键字作为前缀来定义可选成员。当需要使用可选规定的方法或者属性时,他的类型自动会变成可选的。比如,一个定义为`(Int) -> String`的方法变成`((Int) -> String)?`。需要注意的是整个函数定义包裹在可选中,而不是放在函数的返回值后面。
|
||||
## 可选的协议要求
|
||||
|
||||
可选协议在调用时使用`可选链`,因为协议的遵循者可能没有实现可选内容。像`someOptionalMethod?(someArgument)`这样,你可以在可选方法名称后加上`?`来检查该方法是否被实现。详细内容在[可空链式调用](./17_Optional_Chaining.html)章节中查看。
|
||||
协议可以定义可选要求,采纳协议的类型可以选择是否实现这些要求。在协议中使用 `optional` 关键字作为前缀来定义可选要求。使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。
|
||||
|
||||
协议中的可选要求可通过可选链式调用来使用,因为采纳协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./17_Optional_Chaining.html)章节中查看。
|
||||
|
||||
> 注意
|
||||
> 可选协议只能在含有`@objc`前缀的协议中生效。
|
||||
> 这个前缀表示协议将暴露给Objective-C代码,详情参见[`Using Swift with Cocoa and Objective-C(Swift 2.1)`](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。即使你不打算和Objective-C有什么交互,如果你想要指明协议包含可选属性,那么还是要加上`@obj`前缀。
|
||||
> 还需要注意的是,`@objc`的协议只能由继承自 Objective-C 类的类或者其他的`@objc`类来遵循。它也不能被结构体和枚举遵循。
|
||||
> 可选的协议要求只能用在标记 `@objc` 特性的协议中。
|
||||
> 该特性表示协议将暴露给 Objective-C 代码,详情参见[`Using Swift with Cocoa and Objective-C(Swift 2.2)`](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。即使你不打算和 Objective-C 有什么交互,如果你想要指定可选的协议要求,那么还是要为协议加上 `@objc` 特性。
|
||||
> 还需要注意的是,标记 `@objc` 特性的协议只能被继承自 Objective-C 类的类或者 `@objc` 类采纳,其他类以及结构体和枚举均不能采纳这种协议。
|
||||
|
||||
下面的例子定义了一个叫`Counter`的整数加法类,它使用外部的数据源来提供每次的增量。数据源是两个可选规定,在`CounterDataSource`协议中定义:
|
||||
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求:
|
||||
|
||||
```swift
|
||||
@objc protocol CounterDataSource {
|
||||
@ -738,49 +741,49 @@ for object in objects {
|
||||
}
|
||||
```
|
||||
|
||||
`CounterDataSource`含有`incrementForCount(_:)`可选方法和`fiexdIncrement`可选属性,它们使用了不同的方法来从数据源中获取合适的增量值。
|
||||
`CounterDataSource` 协议定义了一个可选方法 `incrementForCount(_:)` 和一个可选属性 `fiexdIncrement`,它们使用了不同的方法来从数据源中获取适当的增量值。
|
||||
|
||||
> 注意
|
||||
> 严格来讲,`CounterDataSource`中的属性和方法都是可选的,因此可以在类中声明都不实现这些成员,尽管技术上允许这样做,不过最好不要这样写。
|
||||
> 严格来讲,`CounterDataSource` 协议中的方法和属性都是可选的,因此采纳协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。
|
||||
|
||||
`Counter`类含有`CounterDataSource?`类型的可选属性`dataSource`,如下所示:
|
||||
`Counter` 类含有 `CounterDataSource?` 类型的可选属性 `dataSource`,如下所示:
|
||||
|
||||
```swift
|
||||
@objc class Counter {
|
||||
class Counter {
|
||||
var count = 0
|
||||
var dataSource: CounterDataSource?
|
||||
func increment() {
|
||||
if let amount = dataSource?.incrementForCount?(count) {
|
||||
count += amount
|
||||
} else if let amount = dataSource?.fixedIncrement? {
|
||||
} else if let amount = dataSource?.fixedIncrement {
|
||||
count += amount
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
类`Counter`使用`count`来存储当前的值。该类同时定义了一个`increment`方法,每次调用该方法的时候,将会增加`count`的值。
|
||||
`Counter` 类使用变量属性 `count` 来存储当前值。该类还定义了一个 `increment()` 方法,每次调用该方法的时候,将会增加 `count` 的值。
|
||||
|
||||
`increment()`方法首先试图使用`incrementForCount(_:)`方法来得到每次的增量。`increment()`方法使用可选链来尝试调用`incrementForCount(_:)`,并将当前的`count`值作为参数传入。
|
||||
`increment()` 方法首先试图使用 `incrementForCount(_:)` 方法来得到每次的增量。`increment()` 方法使用可选链式调用来尝试调用 `incrementForCount(_:)`,并将当前的 `count` 值作为参数传入。
|
||||
|
||||
这里使用了两种可选链方法。首先,由于`dataSource`可能为`nil`,因此在`dataSource`后边加上了`?`标记来表明只在`dataSource`非空时才去调用`incrementForCount(_:)`方法。其次,即使`dataSource`存在,也无法保证其是否实现了`incrementForCount(_:)`方法,因为这个方法是可选的。在这里,有可能未被实现的`incrementForCount(_:)`方法同样使用可选链进行调用。只有在`incrementForCount(_:)`存在的情况下才能调用`incrementForCount(_:)`-也就是说,它是`nil`的时候。这就是为什么要在`incrementForCount(_:)`方法后边也加有`?`标记的原因。
|
||||
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `incrementForCount(_:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `incrementForCount(_:)` 方法,因为这个方法是可选的。因此,`incrementForCount(_:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `incrementForCount(_:)` 方法后边也加上了 `?`。
|
||||
|
||||
|
||||
调用`incrementForCount(_:)`方法在上述两种情形都有可能失败,所以返回值为*可选*`Int`类型。虽然在`CounterDataSource`中,`incrementForCount`被定义为一个非可选`Int`(non-optional),但是这里我们仍然需要返回*可选*`Int`类型。想获得更多的关于如何使用多可选链的操作的信息,请查阅[多层链接](./17_Optional_Chaining)
|
||||
调用 `incrementForCount(_:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`incrementForCount(_:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型,即 `Int?` 而不是 `Int??`。关于这一点的更多信息,请查阅[连接多层可选链式调用](./17_Optional_Chaining)
|
||||
|
||||
在调用`incrementForCount(_:)`方法后,`Int`型`可选值`通过`可选绑定(optional binding)`自动拆包并赋值给常量`amount`。如果可选值确实包含一个数值,这表示`delegate`和方法都存在,之后便将`amount`加到`count`上,增加操作完成。
|
||||
在调用 `incrementForCount(_:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。
|
||||
|
||||
如果没有从`incrementForCount(_:)`获取到值,可能是`dataSource`为nil,或者它并没有实现`incrementForCount(_:)`方法——那么`increment()`方法将试图从数据源的`fixedIncrement`属性中获取增量。`fixedIncrement`也是一个可选型,所以在属性名的后面添加`?`来试图取回可选属性的值。和之前一样,返回值为可选型,即使在`CounterDataSource`中定义的是一个非可选的`Int`类型的`fixedIncrement`属性。
|
||||
如果没有从 `incrementForCount(_:)` 方法获取到值,可能由于 `dataSource` 为 `nil`,或者它并没有实现 `incrementForCount(_:)` 方法,那么 `increment()` 方法将试图从数据源的 `fixedIncrement` 属性中获取增量。`fixedIncrement` 是一个可选属性,因此属性值是一个 `Int?` 值,即使该属性在 `CounterDataSource` 协议中的类型是非可选的 `Int`。
|
||||
|
||||
`ThreeSource`实现了`CounterDataSource`协议,它实现来可选属性`fixedIncrement`,设置值为`3`:
|
||||
下面的例子展示了 `CounterDataSource` 的简单实现。`ThreeSource` 类采纳了 `CounterDataSource` 协议,它实现了可选属性 `fixedIncrement`,每次会返回 `3`:
|
||||
|
||||
```swift
|
||||
@objc class ThreeSource: CounterDataSource {
|
||||
let fixedIncrement = 3
|
||||
class ThreeSource: NSObject, CounterDataSource {
|
||||
let fixedIncrement = 3
|
||||
}
|
||||
```
|
||||
|
||||
可以使用`ThreeSource`的实例作为`Counter`实例的数据源:
|
||||
可以使用 `ThreeSource` 的实例作为 `Counter` 实例的数据源:
|
||||
|
||||
```swift
|
||||
var counter = Counter()
|
||||
@ -795,13 +798,13 @@ for _ in 1...4 {
|
||||
// 12
|
||||
```
|
||||
|
||||
上述代码新建了一个`Counter`实例;将它的数据源设置为`TreeSource`实例;调用`increment()`4次。和你预想的一样,每次在调用的时候,`count`的值增加3.
|
||||
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法四次。和预期一样,每次调用都会将 `count` 的值增加 `3`.
|
||||
|
||||
下面是一个更为复杂的数据源`TowardsZeroSource`,它将使得最后的值变为0:
|
||||
下面是一个更为复杂的数据源 `TowardsZeroSource`,它将使得最后的值变为 `0`:
|
||||
|
||||
```swift
|
||||
class TowardsZeroSource: CounterDataSource {
|
||||
func incrementForCount(count: Int) -> Int {
|
||||
@objc class TowardsZeroSource: NSObject, CounterDataSource {
|
||||
func incrementForCount(count: Int) -> Int {
|
||||
if count == 0 {
|
||||
return 0
|
||||
} else if count < 0 {
|
||||
@ -813,11 +816,9 @@ func incrementForCount(count: Int) -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
`TowardsZeroSource`实现了`CounterDataSource`协议中的`incrementForCount(_:)`方法,以`count`参数为依据,计算出每次的增量。如果`count`已经为0,方法返回0,这表示之后不会再有增量。
|
||||
`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `incrementForCount(_:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法返回 `0`,以此表明之后不应再有增量操作发生。
|
||||
|
||||
你可以配合使用`TowardsZeroSource`实例和`Counter`实例来从`-4`增加到`0`.一旦增加到`0`,数值便不会再有变动。
|
||||
|
||||
在下面的例子中,将从`-4`增加到`0`。一旦结果为`0`,便不在增加:
|
||||
你可以使用 `TowardsZeroSource` 实例将 `Counter` 实例来从 `-4` 增加到 `0`。一旦增加到 `0`,数值便不会再有变动:
|
||||
|
||||
```swift
|
||||
counter.count = -4
|
||||
@ -836,9 +837,9 @@ for _ in 1...5 {
|
||||
<a name="protocol_extensions"></a>
|
||||
## 协议扩展
|
||||
|
||||
使用扩展协议的方式可以为遵循者提供方法或属性的实现。通过这种方式,可以让你无需在每个遵循者中都实现一次,无需使用全局函数,你可以通过扩展协议的方式进行定义。
|
||||
协议可以通过扩展来为采纳协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个采纳协议的类型中都重复同样的实现,也无需使用全局函数。
|
||||
|
||||
例如,可以扩展`RandomNumberGenerator`协议,让其提供`randomBool()`方法。该方法使用`random()`方法返回一个随机的`Bool`值:
|
||||
例如,可以扩展 `RandomNumberGenerator` 协议来提供 `randomBool()` 方法。该方法使用协议中定义的 `random()` 方法来返回一个随机的 `Bool` 值:
|
||||
|
||||
```swift
|
||||
extension RandomNumberGenerator {
|
||||
@ -848,24 +849,25 @@ extension RandomNumberGenerator {
|
||||
}
|
||||
```
|
||||
|
||||
通过扩展协议,所有协议的遵循者,在不用任何修改的情况下,都自动得到了这个扩展所增加的方法。
|
||||
通过协议扩展,所有采纳协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外修改:
|
||||
|
||||
```swift
|
||||
let generator = LinearCongruentialGenerator()
|
||||
print("Here's a random number: \(generator.random())")
|
||||
// 输出 "Here's a random number: 0.37464991998171"
|
||||
// 打印 “Here's a random number: 0.37464991998171”
|
||||
print("And here's a random Boolean: \(generator.randomBool())")
|
||||
// 输出 "And here's a random Boolean: true"
|
||||
// 打印 “And here's a random Boolean: true”
|
||||
```
|
||||
|
||||
<a name="providing_default_implementations"></a>
|
||||
### 提供默认实现
|
||||
|
||||
可以通过协议扩展的方式来为协议规定的属性和方法提供默认的实现。如果协议的遵循者对规定的属性和方法提供了自己的实现,那么遵循者提供的实现将被使用。
|
||||
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果采纳协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
|
||||
|
||||
> 注意
|
||||
> 通过扩展协议提供的协议实现和可选协议规定有区别。虽然协议遵循者无需自己实现,通过扩展提供的默认实现,可以不是用可选链调用。
|
||||
> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,采纳协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
|
||||
|
||||
例如,`PrettyTextRepresentable`协议,继承自`TextRepresentable`协议,可以为其提供一个默认的`prettyTextualDescription`属性,来简化访问`textualDescription`属性:
|
||||
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性,只是简单地返回 `textualDescription` 属性的值:
|
||||
|
||||
```swift
|
||||
extension PrettyTextRepresentable {
|
||||
@ -875,14 +877,15 @@ extension PrettyTextRepresentable {
|
||||
}
|
||||
```
|
||||
|
||||
<a name="adding_constraints_to_protocol_extensions"></a>
|
||||
### 为协议扩展添加限制条件
|
||||
|
||||
在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制写在协议名之后,使用`where`关键字来描述限制情况。([Where语句](./23_Generics.html#where_clauses))。:
|
||||
在扩展协议的时候,可以指定一些限制条件,只有采纳协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[Where子句](./23_Generics.html#where_clauses)中所描述的。
|
||||
|
||||
例如,你可以扩展`CollectionType`协议,但是只适用于元素遵循`TextRepresentable`的情况:
|
||||
例如,你可以扩展 `CollectionType` 协议,但是只适用于集合中的元素采纳了 `TextRepresentable` 协议的情况:
|
||||
|
||||
```swift
|
||||
extension CollectionType where Generator.Element : TextRepresentable {
|
||||
extension CollectionType where Generator.Element: TextRepresentable {
|
||||
var textualDescription: String {
|
||||
let itemsAsText = self.map { $0.textualDescription }
|
||||
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
|
||||
@ -890,9 +893,9 @@ extension CollectionType where Generator.Element : TextRepresentable {
|
||||
}
|
||||
```
|
||||
|
||||
`textualDescription`属性将每个元素的文本描述以逗号分隔的方式连接起来。
|
||||
`textualDescription` 属性返回整个集合的文本描述,它将集合中的每个元素的文本描述以逗号分隔的方式连接起来,包在一对方括号中。
|
||||
|
||||
现在我们来看`Hamster`,它遵循`TextRepresentable`协议:
|
||||
现在我们来看看先前的 `Hamster` 结构体,它符合 `TextRepresentable` 协议,同时这里还有个装有 `Hamster` 的实例的数组:
|
||||
|
||||
```swift
|
||||
let murrayTheHamster = Hamster(name: "Murray")
|
||||
@ -901,12 +904,12 @@ let mauriceTheHamster = Hamster(name: "Maurice")
|
||||
let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
|
||||
```
|
||||
|
||||
因为`Array`遵循`CollectionType`协议,数组的元素又遵循`TextRepresentable`协议,所以数组可以使用`textualDescription`属性得到数组内容的文本表示:
|
||||
因为 `Array` 符合 `CollectionType` 协议,而数组中的元素又符合 `TextRepresentable` 协议,所以数组可以使用 `textualDescription` 属性得到数组内容的文本表示:
|
||||
|
||||
```swift
|
||||
print(hamsters.textualDescription)
|
||||
// 输出 "(A hamster named Murray, A hamster named Morgan, A hamster named Maurice)"
|
||||
// 打印 “[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]”
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 如果有多个协议扩展,而一个协议的遵循者又同时满足它们的限制,那么将会使用所满足限制最多的那个扩展。
|
||||
> 如果多个协议扩展都为同一个协议要求提供了默认实现,而采纳协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
> 2.0
|
||||
> 翻译+校对: [SergioChan](https://github.com/SergioChan)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
|
||||
> 2.2:翻译+校对:[Lanford](https://github.com/LanfordCai),2016-04-08 [SketchK](https://github.com/SketchK) 2016-05-16
|
||||
|
||||
> 3.0:翻译+校对:[chenmingjia](https://github.com/chenmingjia),2016-09-12
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [泛型所解决的问题](#the_problem_that_generics_solve)
|
||||
@ -19,39 +26,38 @@
|
||||
- [扩展一个泛型类型](#extending_a_generic_type)
|
||||
- [类型约束](#type_constraints)
|
||||
- [关联类型](#associated_types)
|
||||
- [`Where`语句](#where_clauses)
|
||||
- [Where 子句](#where_clauses)
|
||||
|
||||
*泛型代码*可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。
|
||||
泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
|
||||
|
||||
泛型是 Swift 强大特征中的其中一个,许多 Swift 标准库是通过泛型代码构建出来的。事实上,泛型的使用贯穿了整本语言手册,只是你没有发现而已。例如,Swift 的数组和字典类型都是泛型集。你可以创建一个`Int`数组,也可创建一个`String`数组,或者甚至于可以是任何其他 Swift 的类型数据数组。同样的,你也可以创建存储任何指定类型的字典(dictionary),而且这些类型可以是没有限制的。
|
||||
泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言手册,只是你可能没有发现而已。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。
|
||||
|
||||
<a name="the_problem_that_generics_solve"></a>
|
||||
## 泛型所解决的问题
|
||||
|
||||
这里是一个标准的,非泛型函数`swapTwoInts`,用来交换两个Int值:
|
||||
下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(inout a: Int, inout _ b: Int) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
这个函数使用写入读出(in-out)参数来交换`a`和`b`的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。
|
||||
这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。
|
||||
|
||||
`swapTwoInts(_:_:)`函数可以交换`b`的原始值到`a`,也可以交换a的原始值到`b`,你可以调用这个函数交换两个`Int`变量值:
|
||||
`swapTwoInts(_:_:)` 函数交换 `b` 的原始值到 `a`,并交换 `a` 的原始值到 `b`。你可以调用这个函数交换两个 `Int` 变量的值:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// 输出 "someInt is now 107, and anotherInt is now 3"
|
||||
// 打印 “someInt is now 107, and anotherInt is now 3”
|
||||
```
|
||||
|
||||
|
||||
`swapTwoInts(_:_:)`函数是非常有用的,但是它只能交换`Int`值,如果你想要交换两个`String`或者`Double`,就不得不写更多的函数,如 `swapTwoStrings`和`swapTwoDoubles(_:_:)`,如同如下所示:
|
||||
诚然,`swapTwoInts(_:_:)` 函数挺有用,但是它只能交换 `Int` 值,如果你想要交换两个 `String` 值或者 `Double`值,就不得不写更多的函数,例如 `swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)`,如下所示:
|
||||
|
||||
```swift
|
||||
func swapTwoStrings(inout a: String, inout _ b: String) {
|
||||
@ -67,17 +73,17 @@ func swapTwoDoubles(inout a: Double, inout _ b: Double) {
|
||||
}
|
||||
```
|
||||
|
||||
你可能注意到 `swapTwoInts`、 `swapTwoStrings`和`swapTwoDoubles(_:_:)`函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是`Int`、`String`和`Double`。
|
||||
你可能注意到 `swapTwoInts(_:_:)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int`、`String` 和 `Double`。
|
||||
|
||||
但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。)
|
||||
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
|
||||
|
||||
>注意:
|
||||
在所有三个函数中,`a`和`b`的类型是一样的。如果`a`和`b`不是相同的类型,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个`String`类型的变量和一个`Double`类型的变量互相交换值。如果一定要做,Swift 将报编译错误。
|
||||
> 注意
|
||||
在上面三个函数中,`a` 和 `b` 类型相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
|
||||
|
||||
<a name="generic_functions"></a>
|
||||
## 泛型函数
|
||||
|
||||
`泛型函数`可以工作于任何类型,这里是一个上面`swapTwoInts(_:_:)`函数的泛型版本,用于交换两个值:
|
||||
泛型函数可以适用于任何类型,下面的 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本:
|
||||
|
||||
```swift
|
||||
func swapTwoValues<T>(inout a: T, inout _ b: T) {
|
||||
@ -87,81 +93,74 @@ func swapTwoValues<T>(inout a: T, inout _ b: T) {
|
||||
}
|
||||
```
|
||||
|
||||
`swapTwoValues(_:_:)`函数主体和`swapTwoInts(_:_:)`函数是一样的,它只在第一行稍微有那么一点点不同于`swapTwoInts`,如下所示:
|
||||
`swapTwoValues(_:_:)` 的函数主体和 `swapTwoInts(_:_:)` 函数是一样的,它们只在第一行有点不同,如下所示:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(inout a: Int, inout _ b: Int)
|
||||
func swapTwoValues<T>(inout a: T, inout _ b: T)
|
||||
```
|
||||
|
||||
这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int`、`String` 或 `Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a` 和 `b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才能根据所传入的实际类型决定 `T` 所代表的类型。
|
||||
|
||||
这个函数的泛型版本使用了占位类型名字(通常此情况下用字母`T`来表示)来代替实际类型名(如`Int`、`String`或`Double`)。占位类型名没有提示`T`必须是什么类型,但是它提示了`a`和`b`必须是同一类型`T`,而不管`T`表示什么类型。只有`swapTwoValues(_:_:)`函数在每次调用时所传入的实际类型才能决定`T`所代表的类型。
|
||||
另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。
|
||||
|
||||
另外一个不同之处在于这个泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的(`<T>`)。这个尖括号告诉 Swift 那个`T`是`swapTwoValues(_:_:)`函数所定义的一个类型。因为`T`是一个占位命名类型,Swift 不会去查找命名为T的实际类型。
|
||||
`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来。
|
||||
|
||||
`swapTwoValues(_:_:)`函数除了要求传入的两个任何类型值是同一类型外,也可以作为`swapTwoInts`函数被调用。每次`swapTwoValues`被调用,T所代表的类型值都会传给函数。
|
||||
|
||||
在下面的两个例子中,`T`分别代表`Int`和`String`:
|
||||
在下面的两个例子中,`T` 分别代表 `Int` 和 `String`:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoValues(&someInt, &anotherInt)
|
||||
// someInt 现在等于 107, anotherInt 现在等于 3
|
||||
```
|
||||
// someInt is now 107, and anotherInt is now 3
|
||||
|
||||
```swift
|
||||
var someString = "hello"
|
||||
var anotherString = "world"
|
||||
swapTwoValues(&someString, &anotherString)
|
||||
// someString 现在等于 "world", anotherString 现在等于 "hello"
|
||||
// someString is now "world", and anotherString is now "hello"
|
||||
```
|
||||
|
||||
|
||||
>注意
|
||||
上面定义的函数`swapTwoValues(_:_:)`是受`swap`函数启发而实现的。`swap`函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似`swapTwoValues(_:_:)`函数的功能,你可以使用已存在的交换函数`swap(_:_:)`函数。
|
||||
> 注意
|
||||
上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
|
||||
|
||||
<a name="type_parameters"></a>
|
||||
## 类型参数
|
||||
|
||||
在上面的`swapTwoValues`例子中,占位类型`T`是一种类型参数的示例。类型参数指定并命名为一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(如`<T>`)。
|
||||
在上面的 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
|
||||
|
||||
一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型(如`swapTwoValues(_:_:)`函数中的参数`a`和`b`),或作为一个函数返回类型,或用作函数主体中的注释类型。在这种情况下,被类型参数所代表的占位类型不管函数任何时候被调用,都会被实际类型所替换(在上面`swapTwoValues`例子中,当函数第一次被调用时,`T`被`Int`替换,第二次调用时,被`String`替换。)。
|
||||
一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a` 和 `b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T` 被 `Int` 替换,第二次调用时,被 `String` 替换。)
|
||||
|
||||
你可支持多个类型参数,命名在尖括号中,用逗号分开。
|
||||
你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
|
||||
|
||||
<a name="naming_type_parameters"></a>
|
||||
## 命名类型参数
|
||||
|
||||
在简单的情况下,泛型函数或泛型类型需要指定一个占位类型(如上面的`swapTwoValues`泛型函数,或一个存储单一类型的泛型集,如数组),通常用一单个字母`T`来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。
|
||||
在大多数情况下,类型参数具有一个描述性名字,例如 `Dictionary<Key, Value>` 中的 `Key` 和 `Value`,以及 `Array<Element>` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 `T`、`U`、`V`,正如上面演示的 `swapTwoValues(_:_:)` 函数中的 `T` 一样。
|
||||
|
||||
如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的。例如,Swift 字典(Dictionary)类型有两个类型参数,一个是键,另外一个是值。如果你自己写字典,你或许会定义这两个类型参数为`Key`和`Value`,用来记住它们在你的泛型代码中的作用。
|
||||
|
||||
>注意
|
||||
请始终使用大写字母开头的驼峰式命名法(例如`T`和`Key`)来给类型参数命名,以表明它们是类型的占位符,而非类型值。
|
||||
> 注意
|
||||
请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。
|
||||
|
||||
<a name="generic_types"></a>
|
||||
## 泛型类型
|
||||
|
||||
除了泛型函数,Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于 `Array` 和 `Dictionary`。
|
||||
|
||||
通常在泛型函数中,Swift 允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同`Array`和`Dictionary`的用法。
|
||||
这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。
|
||||
|
||||
这部分向你展示如何写一个泛型集类型--`Stack`(栈)。一个栈是一系列值域的集合,和`Array`(数组)类似,但其是一个比 Swift 的`Array`类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许在集合的末端添加新的项(如同*push*一个新值进栈)。同样的一个栈也只能从末端移除项(如同*pop*一个值出栈)。
|
||||
> 注意
|
||||
栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。
|
||||
|
||||
>注意
|
||||
栈的概念已被`UINavigationController`类使用来模拟试图控制器的导航结构。你通过调用`UINavigationController`的`pushViewController(_:animated:)`方法来为导航栈添加(add)新的试图控制器;而通过`popViewControllerAnimated(_:)`的方法来从导航栈中移除(pop)某个试图控制器。每当你需要一个严格的`后进先出`方式来管理集合,堆栈都是最实用的模型。
|
||||
|
||||
下图展示了一个栈的压栈(push)/出栈(pop)的行为:
|
||||
下图展示了一个栈的入栈(push)和出栈(pop)的行为:
|
||||
|
||||

|
||||
|
||||
1. 现在有三个值在栈中;
|
||||
2. 第四个值“pushed”到栈的顶部;
|
||||
3. 现在有四个值在栈中,最近的那个在顶部;
|
||||
4. 栈中最顶部的那个项被移除,或称之为“popped”;
|
||||
5. 移除掉一个值后,现在栈又重新只有三个值。
|
||||
1. 现在有三个值在栈中。
|
||||
2. 第四个值被压入到栈的顶部。
|
||||
3. 现在有四个值在栈中,最近入栈的那个值在顶部。
|
||||
4. 栈中最顶部的那个值被移除,或称之为出栈。
|
||||
5. 移除掉一个值后,现在栈又只有三个值了。
|
||||
|
||||
这里展示了如何写一个非泛型版本的栈,`Int`值型的栈:
|
||||
下面展示了如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
|
||||
|
||||
```swift
|
||||
struct IntStack {
|
||||
@ -175,37 +174,35 @@ struct IntStack {
|
||||
}
|
||||
```
|
||||
|
||||
这个结构体在栈中使用一个`Array`性质的`items`存储值。`Stack`提供两个方法:`push`和`pop`,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为它们需要修改(或*转换*)结构体的`items`数组。
|
||||
这个结构体在栈中使用一个名为 `items` 的 `Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)` 和 `pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
|
||||
|
||||
上面所展现的`IntStack`类型只能用于`Int`值,不过,其对于定义一个泛型`Stack`类(可以处理*任何*类型值的栈)是非常有用的。
|
||||
|
||||
这里是一个相同代码的泛型版本:
|
||||
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。
|
||||
|
||||
下面是相同代码的泛型版本:
|
||||
|
||||
```swift
|
||||
struct Stack<T> {
|
||||
var items = [T]()
|
||||
mutating func push(item: T) {
|
||||
struct Stack<Element> {
|
||||
var items = [Element]()
|
||||
mutating func push(item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> T {
|
||||
mutating func pop() -> Element {
|
||||
return items.removeLast()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(`<Element>`)。
|
||||
|
||||
注意到`Stack`的泛型版本基本上和非泛型版本相同,但是泛型版本的占位类型参数为T代替了实际`Int`类型。这种类型参数包含在一对尖括号里(`<T>`),紧随在结构体名字后面。
|
||||
`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符:
|
||||
|
||||
`T`定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下,`T`在如下三个地方被用作节点:
|
||||
- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
|
||||
- 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
|
||||
- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
|
||||
|
||||
- 创建一个名为`items`的属性,使用空的T类型值数组对其进行初始化;
|
||||
- 指定一个包含一个参数名为`item`的`push(_:)`方法,该参数必须是T类型;
|
||||
- 指定一个`pop`方法的返回值,该返回值将是一个T类型值。
|
||||
由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 `Array` 和 `Dictionary` 那样。
|
||||
|
||||
由于`Stack`是泛型类型,所以在 Swift 中其可以用来创建任何有效类型的栈,这种方式如同`Array`和`Dictionary`。
|
||||
|
||||
你可以通过在尖括号里写出栈中需要存储的数据类型来创建并初始化一个`Stack`实例。比如,要创建一个`strings`的栈,你可以写成`Stack<String>()`:
|
||||
你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack<String>()`:
|
||||
|
||||
```swift
|
||||
var stackOfStrings = Stack<String>()
|
||||
@ -213,78 +210,80 @@ stackOfStrings.push("uno")
|
||||
stackOfStrings.push("dos")
|
||||
stackOfStrings.push("tres")
|
||||
stackOfStrings.push("cuatro")
|
||||
// 现在栈已经有4个string了
|
||||
// 栈中现在有 4 个字符串
|
||||
```
|
||||
|
||||
下图将展示`stackOfStrings`如何`push`这四个值进栈的过程:
|
||||
下图展示了 `stackOfStrings` 如何将这四个值入栈:
|
||||
|
||||

|
||||
|
||||
从栈中`pop`并移除值"cuatro":
|
||||
移除并返回栈顶部的值 `"cuatro"`,即将其出栈:
|
||||
|
||||
```swift
|
||||
let fromTheTop = stackOfStrings.pop()
|
||||
// fromTheTop 等于 "cuatro", 现在栈中还有3个string
|
||||
// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串
|
||||
```
|
||||
|
||||
下图展示了如何从栈中pop一个值的过程:
|
||||
下图展示了 `stackOfStrings` 如何将顶部的值出栈:
|
||||
|
||||

|
||||
|
||||
<a name="extending_a_generic_type"></a>
|
||||
## 扩展一个泛型类型
|
||||
|
||||
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
|
||||
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
|
||||
|
||||
下面的例子扩展了泛型`Stack`类型,为其添加了一个名为`topItem`的只读计算属性,它将会返回当前栈顶端的元素而不会将其从栈中移除。
|
||||
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:
|
||||
|
||||
```swift
|
||||
extension Stack {
|
||||
var topItem: T? {
|
||||
return items.isEmpty ? nil : items[items.count - 1]
|
||||
var topItem: Element? {
|
||||
return items.isEmpty ? nil : items[items.count - 1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`topItem`属性会返回一个`T`类型的可选值。当栈为空的时候,`topItem`将会返回`nil`;当栈不为空的时候,`topItem`会返回`items`数组中的最后一个元素。
|
||||
`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
|
||||
|
||||
注意这里的扩展并没有定义一个类型参数列表。相反的,`Stack`类型已有的类型参数名称,`T`,被用在扩展中当做`topItem`计算属性的可选类型。
|
||||
注意,这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
|
||||
|
||||
`topItem`计算属性现在可以被用来返回任意`Stack`实例的顶端元素而无需移除它:
|
||||
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它:
|
||||
|
||||
```swift
|
||||
if let topItem = stackOfStrings.topItem {
|
||||
print("The top item on the stack is \(topItem).")
|
||||
print("The top item on the stack is \(topItem).")
|
||||
}
|
||||
// 输出 "The top item on the stack is tres."
|
||||
// 打印 “The top item on the stack is tres.”
|
||||
```
|
||||
|
||||
<a name="type_constraints"></a>
|
||||
##类型约束
|
||||
## 类型约束
|
||||
|
||||
`swapTwoValues(_:_:)`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
|
||||
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
|
||||
|
||||
例如,Swift 的`Dictionary`类型对作用于其键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键类型必须是*可哈希*,也就是说,必须有一种方法可以使其被唯一的表示。`Dictionary`之所以需要其键是可哈希是为了以便于其检查其是否已经包含某个特定键的值。如无此需求,`Dictionary`既不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。
|
||||
例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
|
||||
|
||||
这个需求强制加上一个类型约束作用于`Dictionary`的键上,当然其键类型必须遵循`Hashable`协议(Swift 标准库中定义的一个特定协议)。所有的 Swift 基本类型(如`String`,`Int`, `Double`和 `Bool`)默认都是可哈希。
|
||||
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须符合 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。
|
||||
|
||||
当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如`可哈希`具有的类型特征是根据它们概念特征来界定的,而不是它们的直接类型特征。
|
||||
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
|
||||
|
||||
<a name="type_constraint_syntax"></a>
|
||||
### 类型约束语法
|
||||
|
||||
你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):
|
||||
你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同):
|
||||
|
||||
```swift
|
||||
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
||||
// 这里是函数主体
|
||||
// 这里是泛型函数的函数体部分
|
||||
}
|
||||
```
|
||||
|
||||
上面这个假定函数有两个类型参数。第一个类型参数`T`,有一个需要`T`必须是`SomeClass`子类的类型约束;第二个类型参数`U`,有一个需要`U`必须遵循`SomeProtocol`协议的类型约束。
|
||||
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须符合 `SomeProtocol` 协议的类型约束。
|
||||
|
||||
### 类型约束行为
|
||||
<a name="type_constraints_in_action"></a>
|
||||
### 类型约束实践
|
||||
|
||||
这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex(_:_:)`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`:
|
||||
这里有个名为 `findStringIndex` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findStringIndex(_:_:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`:
|
||||
|
||||
```swift
|
||||
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
|
||||
@ -297,20 +296,19 @@ func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
`findStringIndex(_:_:)`函数可以作用于查找一字符串数组中的某个字符串:
|
||||
`findStringIndex(_:_:)` 函数可以用于查找字符串数组中的某个字符串:
|
||||
|
||||
```swift
|
||||
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
||||
if let foundIndex = findStringIndex(strings, "llama") {
|
||||
print("The index of llama is \(foundIndex)")
|
||||
}
|
||||
// 输出 "The index of llama is 2"
|
||||
// 打印 “The index of llama is 2”
|
||||
```
|
||||
|
||||
如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数`findIndex`,用某个类型`T`值替换掉提到的字符串。
|
||||
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`。
|
||||
|
||||
这里展示如何写一个你或许期望的`findStringIndex`的泛型版本`findIndex`。请注意这个函数仍然返回`Int`,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明:
|
||||
下面展示了 `findStringIndex(_:_:)` 函数的泛型版本 `findIndex(_:_:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明:
|
||||
|
||||
```swift
|
||||
func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
|
||||
@ -323,11 +321,11 @@ func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
|
||||
}
|
||||
```
|
||||
|
||||
上面所写的函数不会编译。这个问题的位置在等式的检查上,`“if value == valueToFind”`。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型`T`,当你试图编译这部分代码时估计会出现相应的错误。
|
||||
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。比如说,如果你创建一个自定义的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。
|
||||
|
||||
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个`Equatable`协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持`Equatable`协议。
|
||||
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
|
||||
|
||||
任何`Equatable`类型都可以安全的使用在`findIndex(_:_:)`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分:
|
||||
任何 `Equatable` 类型都可以安全地使用在 `findIndex(_:_:)` 函数中,因为其保证支持等式操作符。为了说明这个事实,当你定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
|
||||
|
||||
```swift
|
||||
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
|
||||
@ -340,55 +338,55 @@ func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
|
||||
}
|
||||
```
|
||||
|
||||
`findIndex(_:_:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T` ”。
|
||||
|
||||
`findIndex`中这个单个类型参数写做:`T: Equatable`,也就意味着“任何T类型都遵循`Equatable`协议”。
|
||||
|
||||
`findIndex(_:_:)`函数现在则可以成功的编译过,并且作用于任何遵循`Equatable`的类型,如`Double`或`String`:
|
||||
`findIndex(_:_:)` 函数现在可以成功编译了,并且可以作用于任何符合 `Equatable` 的类型,如 `Double` 或 `String`:
|
||||
|
||||
```swift
|
||||
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
|
||||
// doubleIndex is an optional Int with no value, because 9.3 is not in the array
|
||||
// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
|
||||
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
|
||||
// stringIndex is an optional Int containing a value of 2
|
||||
// stringIndex 类型为 Int?,其值为 2
|
||||
```
|
||||
|
||||
<a name="associated_types"></a>
|
||||
##关联类型(Associated Types)
|
||||
## 关联类型
|
||||
|
||||
当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为`typealias`关键字。
|
||||
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
|
||||
|
||||
### 关联类型行为
|
||||
<a name="associated_types_in_action"></a>
|
||||
### 关联类型实践
|
||||
|
||||
这里是一个`Container`协议的例子,定义了一个`ItemType`关联类型:
|
||||
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `ItemType`:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
typealias ItemType
|
||||
associatedtype ItemType
|
||||
mutating func append(item: ItemType)
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> ItemType { get }
|
||||
}
|
||||
```
|
||||
|
||||
`Container`协议定义了三个任何容器必须支持的兼容要求:
|
||||
`Container` 协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:
|
||||
|
||||
- 必须可以通过`append(_:)`方法添加一个新元素到容器里;
|
||||
- 必须可以通过使用`count`属性获取容器里元素的数量,并返回一个`Int`值;
|
||||
- 必须可以通过容器的`Int`索引值下标可以检索到每一个元素。
|
||||
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
|
||||
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
|
||||
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
|
||||
|
||||
这个协议没有指定容器里的元素是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵从 `Container` 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||
|
||||
任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的元素可以加进容器里,必须明确可以通过其下标返回元素类型。
|
||||
任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
|
||||
|
||||
为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append(_:)`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。
|
||||
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
|
||||
|
||||
为了达到此目的,`Container`协议声明了一个`ItemType`的关联类型,写作`typealias ItemType`。这个协议不会定义`ItemType`是什么的别名,这个信息将由任何遵循协议的类型来提供。尽管如此,`ItemType`别名提供了一种识别`Container`中元素类型的方法,并且用于`append(_:)`方法和`subscript`方法的类型定义,以便保证任何`Container`期望的行为能够被执行。
|
||||
为了达到这个目的,`Container` 协议声明了一个关联类型 `ItemType`,写作 `associatedtype ItemType`。这个协议无法定义 `ItemType` 是什么类型的别名,这个信息将留给遵从协议的类型来提供。尽管如此,`ItemType` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。
|
||||
|
||||
这里是一个早前`IntStack`类型的非泛型版本,遵循`Container`协议:
|
||||
下面是先前的非泛型的 `IntStack` 类型,这一版本采纳并符合了 `Container` 协议:
|
||||
|
||||
```swift
|
||||
struct IntStack: Container {
|
||||
// IntStack的原始实现
|
||||
// IntStack 的原始实现部分
|
||||
var items = [Int]()
|
||||
mutating func push(item: Int) {
|
||||
items.append(item)
|
||||
@ -396,13 +394,13 @@ struct IntStack: Container {
|
||||
mutating func pop() -> Int {
|
||||
return items.removeLast()
|
||||
}
|
||||
// 遵循Container协议的实现
|
||||
// Container 协议的实现部分
|
||||
typealias ItemType = Int
|
||||
mutating func append(item: Int) {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> Int {
|
||||
return items[i]
|
||||
@ -410,118 +408,114 @@ struct IntStack: Container {
|
||||
}
|
||||
```
|
||||
|
||||
`IntStack` 结构体实现了 `Container` 协议的三个要求,其原有功能也不会和这些要求相冲突。
|
||||
|
||||
`IntStack`类型实现了`Container`协议的所有三个要求,在`IntStack`类型的每个包含部分的功能都满足这些要求。
|
||||
此外,`IntStack` 在实现 `Container` 的要求时,指定 `ItemType` 为 `Int` 类型,即 `typealias ItemType = Int`,从而将 `Container` 协议中抽象的 `ItemType` 类型转换为具体的 `Int` 类型。
|
||||
|
||||
此外,`IntStack`指定了`Container`的实现,适用的`ItemType`被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。
|
||||
由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `ItemType` 为 `Int`。因为 `IntStack` 符合 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `ItemType` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias ItemType = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `ItemType` 应该是哪种类型。
|
||||
|
||||
感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append(_:)`方法的`item`参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道`ItemType`使用的是何种类型。
|
||||
|
||||
你也可以生成遵循`Container`协议的泛型`Stack`类型:
|
||||
你也可以让泛型 `Stack` 结构体遵从 `Container` 协议:
|
||||
|
||||
```swift
|
||||
struct Stack<T>: Container {
|
||||
// original Stack<T> implementation
|
||||
var items = [T]()
|
||||
mutating func push(item: T) {
|
||||
struct Stack<Element>: Container {
|
||||
// Stack<Element> 的原始实现部分
|
||||
var items = [Element]()
|
||||
mutating func push(item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> T {
|
||||
mutating func pop() -> Element {
|
||||
return items.removeLast()
|
||||
}
|
||||
// conformance to the Container protocol
|
||||
mutating func append(item: T) {
|
||||
// Container 协议的实现部分
|
||||
mutating func append(item: Element) {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> T {
|
||||
subscript(i: Int) -> Element {
|
||||
return items[i]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个时候,占位类型参数`T`被用作`append(_:)`方法的`item`参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。
|
||||
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `ItemType` 的类型。
|
||||
|
||||
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
|
||||
### 通过扩展一个存在的类型来指定关联类型
|
||||
|
||||
### 扩展一个存在的类型为一指定关联类型
|
||||
[通过扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。
|
||||
|
||||
在[在扩展中添加协议成员](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。
|
||||
|
||||
Swift的`Array`已经提供`append(_:)`方法,一个`count`属性和通过下标来查找一个自己的元素。这三个功能都达到`Container`协议的要求。也就意味着你可以扩展`Array`去遵循`Container`协议,只要通过简单声明`Array`适用于该协议而已。如何实践这样一个空扩展,在[通过扩展补充协议声明](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中有描述这样一个实现一个空扩展的行为:
|
||||
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 采纳该协议就可以扩展 `Array`,使其遵从 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展采纳协议](./22_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
|
||||
|
||||
```swift
|
||||
extension Array: Container {}
|
||||
```
|
||||
|
||||
如同上面的泛型`Stack`类型一样,`Array`的`append(_:)`方法和下标保证`Swift`可以推断出`ItemType`所使用的适用的类型。定义了这个扩展后,你可以将任何`Array`当作`Container`来使用。
|
||||
如同上面的泛型 `Stack` 结构体一样,`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `ItemType` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。
|
||||
|
||||
<a name="where_clauses"></a>
|
||||
## Where 语句
|
||||
## Where 子句
|
||||
|
||||
[类型约束](#type_constraints)能够确保类型符合泛型函数或类的定义约束。
|
||||
[类型约束](#type_constraints)让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。
|
||||
|
||||
对关联类型定义约束是非常有用的。你可以在参数列表中通过*where*语句定义参数的约束。一个`where`语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可以写一个`where`语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。
|
||||
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
|
||||
|
||||
下面的例子定义了一个名为`allItemsMatch`的泛型函数,用来检查两个`Container`实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回一个为`true`的`Boolean`值,反之则为`false`。
|
||||
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。
|
||||
|
||||
被检查的两个`Container`可以不是相同类型的容器(虽然它们可以是),但它们确实拥有相同类型的元素。这个需求通过一个类型约束和`where`语句结合来表示:
|
||||
被检查的两个 `Container` 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 `where` 子句来表示:
|
||||
|
||||
```swift
|
||||
func allItemsMatch<
|
||||
C1: Container, C2: Container
|
||||
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
|
||||
(someContainer: C1, anotherContainer: C2) -> Bool {
|
||||
|
||||
// 检查两个Container的元素个数是否相同
|
||||
func allItemsMatch<C1: Container, C2: Container>
|
||||
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
|
||||
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
|
||||
|
||||
// 检查两个容器含有相同数量的元素
|
||||
if someContainer.count != anotherContainer.count {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查两个Container相应位置的元素彼此是否相等
|
||||
|
||||
// 检查每一对元素是否相等
|
||||
for i in 0..<someContainer.count {
|
||||
if someContainer[i] != anotherContainer[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有元素检查都相同则返回true
|
||||
|
||||
// 所有元素都匹配,返回 true
|
||||
return true
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
这个函数接受 `someContainer` 和 `anotherContainer` 两个参数。参数 `someContainer` 的类型为 `C1`,参数 `anotherContainer` 的类型为 `C2`。`C1` 和 `C2` 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
|
||||
|
||||
这个函数用了两个参数:`someContainer`和`anotherContainer`。`someContainer`参数是类型`C1`,`anotherContainer`参数是类型`C2`。`C1`和`C2`是容器的两个占位类型参数,决定了这个函数何时被调用。
|
||||
这个函数的类型参数列表还定义了对两个类型参数的要求:
|
||||
|
||||
这个函数的类型参数列紧随在两个类型参数需求的后面:
|
||||
- `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
|
||||
- `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
|
||||
- `C1` 的 `ItemType` 必须和 `C2` 的 `ItemType`类型相同(写作 `C1.ItemType == C2.ItemType`)。
|
||||
- `C1` 的 `ItemType` 必须符合 `Equatable` 协议(写作 `C1.ItemType: Equatable`)。
|
||||
|
||||
- `C1`必须遵循`Container`协议 (写作 `C1: Container`)。
|
||||
- `C2`必须遵循`Container`协议 (写作 `C2: Container`)。
|
||||
- `C1`的`ItemType`同样是C2的`ItemType`(写作 `C1.ItemType == C2.ItemType`)。
|
||||
- `C1`的`ItemType`必须遵循`Equatable`协议 (写作 `C1.ItemType: Equatable`)。
|
||||
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
|
||||
|
||||
第三个和第四个要求被定义为一个`where`语句的一部分,写在关键字`where`后面,作为函数类型参数链的一部分。
|
||||
这些要求意味着:
|
||||
|
||||
这些要求意思是:
|
||||
- `someContainer` 是一个 `C1` 类型的容器。
|
||||
- `anotherContainer` 是一个 `C2` 类型的容器。
|
||||
- `someContainer` 和 `anotherContainer` 包含相同类型的元素。
|
||||
- `someContainer` 中的元素可以通过不等于操作符(`!=`)来检查它们是否彼此不同。
|
||||
|
||||
`someContainer`是一个`C1`类型的容器。
|
||||
`anotherContainer`是一个`C2`类型的容器。
|
||||
`someContainer`和`anotherContainer`包含相同的元素类型。
|
||||
`someContainer`中的元素可以通过不等于操作(`!=`)来检查它们是否彼此不同。
|
||||
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
|
||||
|
||||
第三个和第四个要求结合起来的意思是`anotherContainer`中的元素也可以通过 `!=` 操作来检查,因为它们在`someContainer`中元素确实是相同的类型。
|
||||
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们的容器类型不同。
|
||||
|
||||
这些要求能够使`allItemsMatch(_:_:)`函数比较两个容器,即便它们是不同的容器类型。
|
||||
`allItemsMatch(_:_:)` 函数首先检查两个容器是否拥有相同数量的元素,如果它们的元素数量不同,那么一定不匹配,函数就会返回 `false`。
|
||||
|
||||
`allItemsMatch(_:_:)`首先检查两个容器是否拥有同样数目的items,如果它们的元素数目不同,没有办法进行匹配,函数就会`false`。
|
||||
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 `false`。
|
||||
|
||||
检查完之后,函数通过`for-in`循环和半闭区间操作(`..<`)来迭代`someContainer`中的所有元素。对于每个元素,函数检查是否`someContainer`中的元素不等于对应的`anotherContainer`中的元素,如果这两个元素不等,则这两个容器不匹配,返回`false`。
|
||||
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`。
|
||||
|
||||
如果循环体结束后未发现没有任何的不匹配,那表明两个容器匹配,函数返回`true`。
|
||||
|
||||
这里演示了`allItemsMatch(_:_:)`函数运算的过程:
|
||||
下面演示了 `allItemsMatch(_:_:)` 函数的使用:
|
||||
|
||||
```swift
|
||||
var stackOfStrings = Stack<String>()
|
||||
@ -536,16 +530,7 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
||||
} else {
|
||||
print("Not all items match.")
|
||||
}
|
||||
// 输出 "All items match."
|
||||
// 打印 “All items match.”
|
||||
```
|
||||
|
||||
上面的例子创建一个`Stack`单例来存储`String`,然后压了三个字符串进栈。这个例子也创建了一个`Array`单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组是不同的类型,但它们都遵循`Container`协议,而且它们都包含同样的类型值。因此你可以调用`allItemsMatch(_:_:)`函数,用这两个容器作为它的参数。在上面的例子中,`allItemsMatch(_:_:)`函数正确的显示了这两个容器的所有元素都是相互匹配的。
|
||||
|
||||
[1]: ../chapter2/06_Functions.html
|
||||
[2]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png
|
||||
[3]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png
|
||||
[4]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png
|
||||
[5]: ../chapter2/04_Collection_Types.html
|
||||
[6]: ../chapter2/21_Protocols.html
|
||||
[7]: ../chapter2/21_Protocols.html
|
||||
[8]: #type_constraints
|
||||
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
||||
|
||||
@ -8,96 +8,111 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[mmoaay](https://github.com/mmoaay)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Prayer](https://github.com/futantan)
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-17
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [模块和源文件](#modules_and_source_files)
|
||||
- [访问级别](#access_levels)
|
||||
- [访问级别的使用原则](#guiding_principle_of_access_levels)
|
||||
- [访问级别基本原则](#guiding_principle_of_access_levels)
|
||||
- [默认访问级别](#default_access_levels)
|
||||
- [单目标应用程序的访问级别](#access_levels_for_single-target_apps)
|
||||
- [Framework的访问级别](#access_levels_for_frameworks)
|
||||
- [单元测试目标的访问级别](#access_levels_for_unit_test_targets)
|
||||
- [单 target 应用程序的访问级别](#access_levels_for_single-target_apps)
|
||||
- [框架的访问级别](#access_levels_for_frameworks)
|
||||
- [单元测试 target 的访问级别](#access_levels_for_unit_test_targets)
|
||||
- [访问控制语法](#access_control_syntax)
|
||||
- [自定义类型](#custom_types)
|
||||
- [元组类型](#tuple_types)
|
||||
- [函数类型](#function_types)
|
||||
- [枚举类型](#enumeration_types)
|
||||
- [原始值和关联值](#raw_values_and_associated_values)
|
||||
- [嵌套类型](#nested_types)
|
||||
- [子类](#subclassing)
|
||||
- [常量、变量、属性、下标](#constants_variables_properties_subscripts)
|
||||
- [Getter和Setter](#getters_and_setters)
|
||||
- [初始化](#initializers)
|
||||
- [默认初始化方法](#default_initializers)
|
||||
- [结构体的默认成员初始化方法](#default_memberwise_initializers_for_structure_types)
|
||||
- [构造器](#initializers)
|
||||
- [默认构造器](#default_initializers)
|
||||
- [结构体默认的成员逐一构造器](#default_memberwise_initializers_for_structure_types)
|
||||
- [协议](#protocols)
|
||||
- [协议继承](#protocol_inheritance)
|
||||
- [协议一致性](#protocol_conformance)
|
||||
- [扩展](#extensions)
|
||||
- [协议的扩展](#adding_protocol_conformance_with_an_extension)
|
||||
- [通过扩展添加协议一致性](#adding_protocol_conformance_with_an_extension)
|
||||
- [泛型](#generics)
|
||||
- [类型别名](#type_aliases)
|
||||
|
||||
*访问控制*可以限定其他源文件或模块中代码对你代码的访问级别。这个特性可以让我们隐藏功能实现的一些细节,并且可以明确的申明我们提供给其他人的接口中哪些部分是他们可以访问和使用的。
|
||||
访问控制可以限定其他源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。
|
||||
|
||||
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
|
||||
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
|
||||
|
||||
在提供了不同访问级别的同时,Swift还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单目标应用程序,我们完全可以不用申明代码的显式访问级别。
|
||||
Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式申明代码的访问级别。
|
||||
|
||||
> 注意:简单起见,代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会以“实体”代替。
|
||||
> 注意
|
||||
为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。
|
||||
|
||||
<a name="modules_and_source_files"></a>
|
||||
## 模块和源文件
|
||||
Swift 中的访问控制模型基于模块和源文件这两个概念。
|
||||
|
||||
*模块*指的是以独立单元构建和发布的`Framework`或`Application`。在Swift 中的一个模块可以使用`import`关键字引入另外一个模块。
|
||||
模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。
|
||||
|
||||
在 Swift 中,Xcode的每个构建目标(比如`Framework`或`app bundle`)都被当作模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的`Framework`,这个`Framework`在 Swift 中就被称为模块。当它被引入到某个 app 工程或者另外一个`Framework`时,它里面的一切(属性、函数等)仍然属于这个独立的模块。
|
||||
在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。
|
||||
|
||||
*源文件*指的是 Swift 中的`Swift File`,就是编写 Swift 源代码的文件,它通常属于一个模块。尽管一般我们将不同的`类` 分别定义在不同的源文件中,但是同一个源文件可以包含多个`类`和`函数` 的定义。
|
||||
源文件就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。
|
||||
|
||||
<a name="access_levels"></a>
|
||||
## 访问级别
|
||||
Swift 为代码中的实体提供了三种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
|
||||
|
||||
- `public`:可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。通常情况下,`Framework` 中的某个接口是可以被任何人使用时,你可以将其设置为`public`级别。
|
||||
- `internal`:可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。通常情况下,某个接口或`Framework`作为内部结构使用时,你可以将其设置为`internal`级别。
|
||||
- `private`:只能在当前源文件中使用的实体,称为私有实体。使用`private`级别,可以用作隐藏某些功能的实现细节。
|
||||
- `public`:可以访问同一模块源文件中的任何实体,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,框架中的某个接口可以被任何人使用时,你可以将其设置为 `public` 级别。
|
||||
- `internal`:可以访问同一模块源文件中的任何实体,但是不能从模块外访问该模块源文件中的实体。通常情况下,某个接口只在应用程序或框架内部使用时,你可以将其设置为 `internal` 级别。
|
||||
- `private`:限制实体只能在所在的源文件内部使用。使用 `private` 级别可以隐藏某些功能的实现细节。
|
||||
|
||||
`public`为最高级访问级别,`private`为最低级访问级别。
|
||||
`public` 为最高(限制最少)访问级别,`private` 为最低(限制最多)访问级别。
|
||||
|
||||
> 注意:Swift中的`private`访问和其他语言中的`private`访问不太一样,它的范围限于整个源文件,而不是声明。这就意味着,一个`类` 可以访问定义该`类` 的源文件中定义的所有`private`实体,但是如果一个`类` 的扩展是定义在独立的源文件中,那么就不能访问这个`类` 的`private`成员。
|
||||
> 注意
|
||||
Swift 中的 `private` 访问级别不同于其他语言,它的范围限于源文件,而不是声明范围内。这就意味着,一个类型可以访问其所在源文件中的所有 `private` 实体,但是如果它的扩展定义在其他源文件中,那么它的扩展就不能访问它在这个源文件中定义的 `private` 实体。
|
||||
|
||||
<a name="guiding_principle_of_access_levels"></a>
|
||||
### 访问级别的使用原则
|
||||
Swift 中的访问级别遵循一个使用原则:访问级别统一性。
|
||||
比如说:
|
||||
### 访问级别基本原则
|
||||
|
||||
- 一个`public`访问级别的变量,不能将它的类型定义为`internal`和`private`。因为变量可以被任何人访问,但是定义它的类型不可以,所以这样就会出现错误。
|
||||
- 函数的访问级别不能高于它的参数、返回类型的访问级别。因为如果函数定义为`public`而参数或者返回类型定义为`internal`或`private`,就会出现函数可以被任何人访问,但是它的参数和返回类型确不可以,同样会出现错误。
|
||||
Swift 中的访问级别遵循一个基本原则:不可以在某个实体中定义访问级别更高的实体。
|
||||
|
||||
例如:
|
||||
|
||||
- 一个 `public` 访问级别的变量,其类型的访问级别不能是 `internal` 或 `private`。因为无法保证变量的类型在使用变量的地方也具有访问权限。
|
||||
- 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为如果函数定义为 `public` 而参数类型或者返回类型定义为 `internal` 或 `private`,就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以。
|
||||
|
||||
<a name="default_access_levels"></a>
|
||||
### 默认访问级别
|
||||
如果你不为代码中的所有实体定义显式访问级别,那么它们默认为`internal`级别。在大多数情况下,我们不需要设置实体的显式访问级别。因为我们一般都是在开发一个`app bundle`。
|
||||
|
||||
如果你不为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。
|
||||
|
||||
<a name="access_levels_for_single-target_apps"></a>
|
||||
### 单目标应用程序的访问级别
|
||||
当你编写一个单目标应用程序时,该应用的所有功能都是为该应用服务,不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别`internal`即可。但是如果你愿意,你也可以使用`private`级别,用于隐藏一些功能的实现细节。
|
||||
### 单 target 应用程序的访问级别
|
||||
|
||||
当你编写一个单 target 应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 `internal` 即可。但是,你也可以使用 `private` 级别,用于隐藏一些功能的实现细节。
|
||||
|
||||
<a name="access_levels_for_frameworks"></a>
|
||||
### Framework的访问级别
|
||||
当你开发`Framework`时,就需要把一些对外的接口定义为`public`级别,以便其他人导入该`Framework`后可以正常使用其功能。这些被你定义为`public`的接口,就是这个`Framework`的API。
|
||||
### 框架的访问级别
|
||||
|
||||
> 注意:`Framework`的内部实现细节依然可以使用默认的`internal`级别,或者也可以定义为`private`级别。只有当你想把它作为 API 的一部分的时候,才将其定义为`public`级别。
|
||||
当你开发框架时,就需要把一些对外的接口定义为 `public` 级别,以便使用者导入该框架后可以正常使用其功能。这些被你定义为 `public` 的接口,就是这个框架的 API。
|
||||
|
||||
> 注意
|
||||
框架依然会使用默认的 `internal` 级别,也可以指定为 `private` 级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定 `public` 级别。
|
||||
|
||||
<a name="access_levels_for_unit_test_targets"></a>
|
||||
### 单元测试目标的访问级别
|
||||
### 单元测试 target 的访问级别
|
||||
|
||||
当你的app有单元测试目标时,为了方便测试,测试模块需要能访问到你app中的代码。默认情况下只有`public`级别的实体才可以被其他模块访问。然而,如果在引入一个生产模块时使用`@testable`注解,然后用带测试的方式编译这个生产模块,单元测试目标就可以访问所有`internal`级别的实体。
|
||||
当你的应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 `public` 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试 target 就可以访问应用程序模块中所有 `internal` 级别的实体。
|
||||
|
||||
<a name="access_control_syntax"></a>
|
||||
## 访问控制语法
|
||||
通过修饰符`public`、`internal`、`private`来声明实体的访问级别:
|
||||
|
||||
通过修饰符 `public`、`internal`、`private` 来声明实体的访问级别:
|
||||
|
||||
```swift
|
||||
public class SomePublicClass {}
|
||||
@ -109,72 +124,78 @@ internal let someInternalConstant = 0
|
||||
private func somePrivateFunction() {}
|
||||
```
|
||||
|
||||
除非有特殊的说明,否则实体都使用默认的访问级别`internal`,可以查阅**[默认访问级别](#default_access_levels)**这一节。这意味着在不使用修饰符声明显式访问级别的情况下,`SomeInternalClass`和`someInternalConstant`仍然拥有隐式的访问级别`internal`:
|
||||
除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的访问级别 `internal`:
|
||||
|
||||
```swift
|
||||
class SomeInternalClass {} // 隐式访问级别 internal
|
||||
var someInternalConstant = 0 // 隐式访问级别 internal
|
||||
class SomeInternalClass {} // 隐式访问级别 internal
|
||||
var someInternalConstant = 0 // 隐式访问级别 internal
|
||||
```
|
||||
<a name="custom_types"></a>
|
||||
## 自定义类型
|
||||
如果想为一个自定义类型申明显式访问级别,那么要明确一点。那就是你要确保新类型的访问级别和它实际的作用域相匹配。比如说,如果你定义了一个`private`类,那这个类就只能在定义它的源文件中当作属性类型、函数参数或者返回类型使用。
|
||||
|
||||
类的访问级别也可以影响到类成员(属性、函数、初始化方法等)的默认访问级别。如果你将类申明为`private`类,那么该类的所有成员的默认访问级别也会成为`private`。如果你将类申明为`public`或者`internal`类(或者不明确的申明访问级别,而使用默认的`internal`访问级别),那么该类的所有成员的访问级别是`internal`。
|
||||
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `private` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。
|
||||
|
||||
> 注意:上面提到,一个`public`类的所有成员的访问级别默认为`internal`级别,而不是`public`级别。如果你想将某个成员申明为`public`级别,那么你必须使用修饰符明确的声明该成员。这样做的好处是,在你定义公共接口API的时候,可以明确的选择哪些属性或方法是需要公开的,哪些是内部使用的,可以避免将内部使用的属性方法公开成公共API的错误。
|
||||
一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 级别,那么该类型的所有成员的默认访问级别也会变成 `private`。如果你将类型指定为 `public` 或者 `internal` 级别(或者不明确指定访问级别,而使用默认的 `internal` 访问级别),那么该类型的所有成员的默认访问级别将是 `internal`。
|
||||
|
||||
> 注意
|
||||
上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。
|
||||
|
||||
```swift
|
||||
public class SomePublicClass { // 显式的 public 类
|
||||
public var somePublicProperty = 0 // 显式的 public 类成员
|
||||
var someInternalProperty = 0 // 隐式的 internal 类成员
|
||||
private func somePrivateMethod() {} // 显式的 private 类成员
|
||||
public var somePublicProperty = 0 // 显式的 public 类成员
|
||||
var someInternalProperty = 0 // 隐式的 internal 类成员
|
||||
private func somePrivateMethod() {} // 显式的 private 类成员
|
||||
}
|
||||
|
||||
class SomeInternalClass { // 隐式的 internal 类
|
||||
var someInternalProperty = 0 // 隐式的 internal 类成员
|
||||
private func somePrivateMethod() {} // 显式的 private 类成员
|
||||
var someInternalProperty = 0 // 隐式的 internal 类成员
|
||||
private func somePrivateMethod() {} // 显式的 private 类成员
|
||||
}
|
||||
|
||||
private class SomePrivateClass { // 显式的 private 类
|
||||
var somePrivateProperty = 0 // 隐式的 private 类成员
|
||||
func somePrivateMethod() {} // 隐式的 private 类成员
|
||||
var somePrivateProperty = 0 // 隐式的 private 类成员
|
||||
func somePrivateMethod() {} // 隐式的 private 类成员
|
||||
}
|
||||
```
|
||||
<a name="tuple_types"></a>
|
||||
### 元组类型
|
||||
元组的访问级别使用是所有类型的访问级别使用中最为严谨的。比如说,如果你构建一个包含两种不同类型元素的元组,其中一个元素类型的访问级别为`internal`,另一个为`private`级别,那么这个元组的访问级别为`private`。也就是说元组的访问级别与元组中访问级别最低的类型一致。
|
||||
|
||||
> 注意:元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推导出的,而不是明确的申明。
|
||||
元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal` 级别,另一个类型为 `private` 级别,那么这个元组的访问级别为 `private`。
|
||||
|
||||
> 注意
|
||||
元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。
|
||||
|
||||
<a name="function_types"></a>
|
||||
### 函数类型
|
||||
函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。如果根据参数类型和返回类型得出的函数访问级别不符合默认上下文,那么就需要明确地申明该函数的访问级别。
|
||||
|
||||
下面的例子定义了一个名为`someFunction`全局函数,并且没有明确地申明其访问级别。也许你会认为该函数应该拥有默认的访问级别`internal`,但事实并非如此。事实上,如果按下面这种写法,代码是无法编译通过的:
|
||||
函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。
|
||||
|
||||
下面的例子定义了一个名为 `someFunction` 的全局函数,并且没有明确地指定其访问级别。也许你会认为该函数应该拥有默认的访问级别 `internal`,但事实并非如此。事实上,如果按下面这种写法,代码将无法通过编译:
|
||||
|
||||
```swift
|
||||
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
|
||||
// function implementation goes here
|
||||
// 此处是函数实现部分
|
||||
}
|
||||
```
|
||||
|
||||
我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅**[自定义类型](#custom_types)**)。其中一个类的访问级别是`internal`,另一个的访问级别是`private`,所以根据元组访问级别的原则,该元组的访问级别是`private`(元组的访问级别与元组中访问级别最低的类型一致)。
|
||||
我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅[自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。
|
||||
|
||||
因为该函数返回类型的访问级别是`private`,所以你必须使用`private`修饰符,明确的声明该函数:
|
||||
因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符,明确指定该函数的访问级别:
|
||||
|
||||
```swift
|
||||
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
|
||||
// function implementation goes here
|
||||
// 此处是函数实现部分
|
||||
}
|
||||
```
|
||||
|
||||
将该函数申明为`public`或`internal`,或者使用默认的访问级别`internal`都是错误的,因为如果把该函数当做`public`或`internal`级别来使用的话,是无法得到`private`级别的返回值的。
|
||||
将该函数指定为 `public` 或 `internal`,或者使用默认的访问级别 `internal` 都是错误的,因为如果把该函数当做 `public` 或 `internal` 级别来使用的话,可能会无法访问 `private` 级别的返回值。
|
||||
|
||||
<a name="enumeration_types"></a>
|
||||
### 枚举类型
|
||||
枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。
|
||||
|
||||
比如下面的例子,枚举`CompassPoint`被明确的申明为`public`级别,那么它的成员`North`,`South`,`East`,`West`的访问级别同样也是`public`:
|
||||
枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
|
||||
|
||||
比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public` 级别,那么它的成员 `North`、`South`、`East`、`West` 的访问级别同样也是 `public`:
|
||||
|
||||
```swift
|
||||
public enum CompassPoint {
|
||||
@ -186,20 +207,23 @@ public enum CompassPoint {
|
||||
```
|
||||
|
||||
<a name="raw_values_and_associated_values"></a>
|
||||
### 原始值和关联值
|
||||
枚举定义中的任何原始值或关联值的类型都必须有一个访问级别,这个级别至少要不低于枚举的访问级别。比如说,你不能在一个`internal`访问级别的枚举中定义`private`级别的原始值类型。
|
||||
#### 原始值和关联值
|
||||
|
||||
枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 访问级别的枚举中定义 `private` 级别的原始值类型。
|
||||
|
||||
<a name="nested_types"></a>
|
||||
### 嵌套类型
|
||||
如果在`private`级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有`private`访问级别。如果在`public`或者`internal`级别的类型中定义嵌套类型,那么该嵌套类型自动拥有`internal`访问级别。如果想让嵌套类型拥有`public`访问级别,那么需要明确地申明该嵌套类型的访问级别。
|
||||
|
||||
如果在 `private` 级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。
|
||||
|
||||
<a name="subclassing"></a>
|
||||
## 子类
|
||||
子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是`internal`,子类的访问级别就不能申明为`public`。
|
||||
|
||||
此外,在满足子类不高于父类访问级别以及遵循各访问级别作用域(即模块或源文件)的前提下,你可以重写任意类成员(方法、属性、初始化方法、下标索引等)。
|
||||
子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`。
|
||||
|
||||
如果我们无法直接访问某个类中的属性或函数等,那么可以继承该类,从而可以更容易的访问到该类的类成员。下面的例子中,类`A`的访问级别是`public`,它包含一个函数`someMethod`,访问级别为`private`。类`B`继承类`A`,并且访问级别申明为`internal`,但是在类`B`中重写了类`A`中访问级别为`private`的方法`someMethod`,并重新申明为`internal`级别。通过这种方式,我们就可以访问到某类中`private`级别的类成员,并且可以重新申明其访问级别,以便其他人使用:
|
||||
此外,你可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。
|
||||
|
||||
可以通过重写为继承来的类成员提供更高的访问级别。下面的例子中,类 `A` 的访问级别是 `public`,它包含一个方法 `someMethod()`,访问级别为 `private`。类 `B` 继承自类 `A`,访问级别为 `internal`,但是在类 `B` 中重写了类 `A` 中访问级别为 `private` 的方法 `someMethod()`,并重新指定为 `internal` 级别。通过这种方式,我们就可以将某类中 `private` 级别的类成员重新指定为更高的访问级别,以便其他人使用:
|
||||
|
||||
```swift
|
||||
public class A {
|
||||
@ -211,7 +235,7 @@ internal class B: A {
|
||||
}
|
||||
```
|
||||
|
||||
只要满足子类不高于父类访问级别以及遵循各访问级别作用域的前提下(即`private`的作用域在同一个源文件中,`internal`的作用域在同一个模块下),我们甚至可以在子类中,用子类成员访问父类成员,哪怕父类成员的访问级别比子类成员的要低:
|
||||
我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 `private` 级别的成员,在同一模块内访问父类 `internal` 级别的成员):
|
||||
|
||||
```swift
|
||||
public class A {
|
||||
@ -219,51 +243,53 @@ public class A {
|
||||
}
|
||||
|
||||
internal class B: A {
|
||||
override internal func someMethod() {
|
||||
super.someMethod()
|
||||
}
|
||||
override internal func someMethod() {
|
||||
super.someMethod()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
因为父类`A`和子类`B`定义在同一个源文件中,所以在类`B`中可以在重写的`someMethod`方法中调用`super.someMethod()`。
|
||||
因为父类 `A` 和子类 `B` 定义在同一个源文件中,所以在子类 `B` 可以在重写的 `someMethod()` 方法中调用 `super.someMethod()`。
|
||||
|
||||
<a name="constants_variables_properties_subscripts"></a>
|
||||
## 常量、变量、属性、下标
|
||||
常量、变量、属性不能拥有比它们的类型更高的访问级别。比如说,你定义一个`public`级别的属性,但是它的类型是`private`级别的,这是编译器所不允许的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
|
||||
|
||||
如果常量、变量、属性、下标索引的定义类型是`private`级别的,那么它们必须要明确的申明访问级别为`private`:
|
||||
常量、变量、属性不能拥有比它们的类型更高的访问级别。例如,你不能定义一个 `public` 级别的属性,但是它的类型却是 `private` 级别的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
|
||||
|
||||
如果常量、变量、属性、下标的类型是 `private` 级别的,那么它们必须明确指定访问级别为 `private`:
|
||||
|
||||
```swift
|
||||
private var privateInstance = SomePrivateClass()
|
||||
```
|
||||
|
||||
<a name="getters_and_setters"></a>
|
||||
### Getter和Setter
|
||||
常量、变量、属性、下标索引的`Getters`和`Setters`的访问级别继承自它们所属成员的访问级别。
|
||||
### Getter 和 Setter
|
||||
|
||||
`Setter`的访问级别可以低于对应的`Getter`的访问级别,这样就可以控制变量、属性或下标索引的读写权限。在`var`或`subscript`定义作用域之前,你可以通过`private(set)`或`internal(set)`先为它们的写权限申明一个较低的访问级别。
|
||||
常量、变量、属性、下标的 `Getters` 和 `Setters` 的访问级别和它们所属类型的访问级别相同。
|
||||
|
||||
> 注意:这个规定适用于用作存储的属性或用作计算的属性。即使你不明确地申明存储属性的`Getter`、`Setter`,Swift也会隐式的为其创建`Getter`和`Setter`,用于对该属性进行读取操作。使用`private(set)`和`internal(set)`可以改变Swift隐式创建的`Setter`的访问级别。这对计算属性也同样适用。
|
||||
`Setter` 的访问级别可以低于对应的 `Getter` 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 `var` 或 `subscript` 关键字之前,你可以通过 `private(set)` 或 `internal(set)` 为它们的写入权限指定更低的访问级别。
|
||||
|
||||
下面的例子中定义了一个名为`TrackedString`的结构体,它记录了`value`属性被修改的次数:
|
||||
> 注意
|
||||
这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`,Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的后备存储。使用 `private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。
|
||||
|
||||
下面的例子中定义了一个名为 `TrackedString` 的结构体,它记录了 `value` 属性被修改的次数:
|
||||
|
||||
```swift
|
||||
struct TrackedString {
|
||||
private(set) var numberOfEdits = 0
|
||||
var value: String = "" {
|
||||
didSet {
|
||||
numberOfEdits++
|
||||
}
|
||||
}
|
||||
private(set) var numberOfEdits = 0
|
||||
var value: String = "" {
|
||||
didSet {
|
||||
numberOfEdits += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`TrackedString` 结构体定义了一个用于存储 `String` 值的属性 `value`,并将初始值设为 `""`(一个空字符串)。该结构体还定义了另一个用于存储 `Int` 值的属性 `numberOfEdits`,它用于记录属性 `value` 被修改的次数。这个功能通过属性 `value` 的 `didSet` 观察器实现,每当给 `value` 赋新值时就会调用 `didSet` 方法,然后将 `numberOfEdits` 的值加一。
|
||||
|
||||
`TrackedString`结构体定义了一个用于存储`String`类型的属性`value`,并将初始化值设为`""`(即一个空字符串)。该结构体同时也定义了另一个用于存储`Int`类型的属性名`numberOfEdits`,它用于记录属性`value`被修改的次数。这个功能的实现通过属性`value`的`didSet`方法实现,每当给`value`赋新值时就会调用`didSet`方法,然后将`numberOfEdits`的值加一。
|
||||
结构体 `TrackedString` 和它的属性 `value` 均没有显式指定访问级别,所以它们都拥有默认的访问级别 `internal`。但是该结构体的 `numberOfEdits` 属性使用了 `private(set)` 修饰符,这意味着 `numberOfEdits` 属性只能在定义该结构体的源文件中赋值。`numberOfEdits` 属性的 `Getter` 依然是默认的访问级别 `internal`,但是 `Setter` 的访问级别是 `private`,这表示该属性只有在当前的源文件中是可读写的,而在当前源文件所属的模块中只是一个可读的属性。
|
||||
|
||||
结构体`TrackedString`和它的属性`value`均没有申明显式访问级别,所以它们都拥有默认的访问级别`internal`。但是该结构体的`numberOfEdits`属性使用`private(set)`修饰符进行申明,这意味着`numberOfEdits`属性只能在定义该结构体的源文件中赋值。`numberOfEdits`属性的`Getter`依然是默认的访问级别`internal`,但是`Setter`的访问级别是`private`,这表示该属性只有在当前的源文件中是可读写的,而在当前源文件所属的模块中它只是一个可读的属性。
|
||||
|
||||
如果你实例化`TrackedString`结构体,并且多次对`value`属性的值进行修改,你就会看到`numberOfEdits`的值会随着修改次数进行变化:
|
||||
如果你实例化 `TrackedString` 结构体,并多次对 `value` 属性的值进行修改,你就会看到 `numberOfEdits` 的值会随着修改次数而变化:
|
||||
|
||||
```swift
|
||||
var stringToEdit = TrackedString()
|
||||
@ -271,19 +297,19 @@ stringToEdit.value = "This string will be tracked."
|
||||
stringToEdit.value += " This edit will increment numberOfEdits."
|
||||
stringToEdit.value += " So will this one."
|
||||
print("The number of edits is \(stringToEdit.numberOfEdits)")
|
||||
// prints "The number of edits is 3"
|
||||
// 打印 “The number of edits is 3”
|
||||
```
|
||||
|
||||
虽然你可以在其他的源文件中实例化该结构体并且获取到`numberOfEdits`属性的值,但是你不能对其进行赋值。这样就能很好的告诉使用者,你只管使用,而不需要知道其实现细节。
|
||||
虽然你可以在其他的源文件中实例化该结构体并且获取到 `numberOfEdits` 属性的值,但是你不能对其进行赋值。这一限制保护了该记录功能的实现细节,同时还提供了方便的访问方式。
|
||||
|
||||
如果有必要你可以为`Getter`和`Setter`方法设定显式访问级别。下面的例子就是明确申明了`public`访问级别的`TrackedString`结构体。结构体的成员(包括`numberOfEdits`属性)拥有默认的访问级别`internal`。你可以结合`public`和`private(set)`修饰符把结构体中的`numberOfEdits`属性`Getter`方法的访问级别设置为`public`,而`Setter`方法的访问级别设置为`private`:
|
||||
你可以在必要时为 `Getter` 和 `Setter` 显式指定访问级别。下面的例子将 `TrackedString` 结构体明确指定为了 `public` 访问级别。结构体的成员(包括 `numberOfEdits` 属性)拥有默认的访问级别 `internal`。你可以结合 `public` 和 `private(set)` 修饰符把结构体中的 `numberOfEdits` 属性的 `Getter` 的访问级别设置为 `public`,而 `Setter` 的访问级别设置为 `private`:
|
||||
|
||||
```swift
|
||||
public struct TrackedString {
|
||||
public private(set) var numberOfEdits = 0
|
||||
public var value: String = "" {
|
||||
didSet {
|
||||
numberOfEdits++
|
||||
numberOfEdits += 1
|
||||
}
|
||||
}
|
||||
public init() {}
|
||||
@ -291,61 +317,74 @@ public struct TrackedString {
|
||||
```
|
||||
|
||||
<a name="initializers"></a>
|
||||
## 初始化
|
||||
我们可以给自定义的初始化方法申明访问级别,但是要不高于它所属类的访问级别。但[必要构造器](./14_Initialization.html#required_initializers)例外,它的访问级别必须和所属类的访问级别相同。
|
||||
## 构造器
|
||||
|
||||
如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。
|
||||
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.html#required_initializers),它的访问级别必须和所属类型的访问级别相同。
|
||||
|
||||
如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。
|
||||
|
||||
<a name="default_initializers"></a>
|
||||
### 默认初始化方法
|
||||
Swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体值。默认初始化方法可以参阅[默认构造器](./14_Initialization.html#default_initializers)。默认初始化方法的访问级别与所属类型的访问级别相同。
|
||||
### 默认构造器
|
||||
|
||||
> 注意:如果一个类型被申明为`public`级别,那么默认的初始化方法的访问级别为`internal`。如果你想让无参的初始化方法在其他模块中可以被使用,那么你必须提供一个具有`public`访问级别的无参初始化方法。
|
||||
如[默认构造器](./14_Initialization.html#default_initializers)所述,Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
|
||||
|
||||
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。
|
||||
|
||||
<a name="default_memberwise_initializers_for_structure_types"></a>
|
||||
### 结构体的默认成员初始化方法
|
||||
如果结构体中的任一存储属性的访问级别为`private`,那么它的默认成员初始化方法访问级别就是`private`。尽管如此,结构体的初始化方法的访问级别依然是`internal`。
|
||||
### 结构体默认的成员逐一构造器
|
||||
|
||||
如果你想在其他模块中使用该结构体的默认成员初始化方法,那么你需要提供一个访问级别为`public`的默认成员初始化方法。
|
||||
如果结构体中任意存储型属性的访问级别为 `private`,那么该结构体默认的成员逐一构造器的访问级别就是 `private`。否则,这种构造器的访问级别依然是 `internal`。
|
||||
|
||||
如同前面提到的默认构造器,如果你希望一个 `public` 级别的结构体也能在其他模块中使用其默认的成员逐一构造器,你依然只能自己提供一个 `public` 访问级别的成员逐一构造器。
|
||||
|
||||
<a name="protocols"></a>
|
||||
## 协议
|
||||
如果想为一个协议明确的申明访问级别,那么需要注意一点,就是你要确保该协议只在你申明的访问级别作用域中使用。
|
||||
|
||||
协议中的每一个必须要实现的函数都具有和该协议相同的访问级别。这样才能确保该协议的使用者可以实现它所提供的函数。
|
||||
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。
|
||||
|
||||
> 注意:如果你定义了一个`public`访问级别的协议,那么实现该协议提供的必要函数也会是`public`的访问级别。这一点不同于其他类型,比如,`public`访问级别的其他类型,他们成员的访问级别为`internal`。
|
||||
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。
|
||||
|
||||
> 注意
|
||||
如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。
|
||||
|
||||
<a name="protocol_inheritance"></a>
|
||||
### 协议继承
|
||||
如果定义了一个新的协议,并且该协议继承了一个已知的协议,那么新协议拥有的访问级别最高也只和被继承协议的访问级别相同。比如说,你不能定义一个`public`的协议而去继承一个`internal`的协议。
|
||||
|
||||
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
|
||||
|
||||
<a name="protocol_conformance"></a>
|
||||
### 协议一致性
|
||||
类可以采用比自身访问级别低的协议。比如说,你可以定义一个`public`级别的类,可以让它在其他模块中使用,同时它也可以采用一个`internal`级别的协议,并且只能在定义了该协议的模块中使用。
|
||||
|
||||
采用了协议的类的访问级别取它本身和所采用协议中最低的访问级别。也就是说如果一个类是`public`级别,采用的协议是`internal`级别,那么采用了这个协议后,该类的访问级别也是`internal`。
|
||||
一个类型可以采纳比自身访问级别低的协议。例如,你可以定义一个 `public` 级别的类型,它可以在其他模块中使用,同时它也可以采纳一个 `internal` 级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。
|
||||
|
||||
如果你采用了协议,那么实现了协议所必须的方法后,该方法的访问级别遵循协议的访问级别。比如说,一个`public`级别的类,采用了`internal`级别的协议,那么该类实现协议的方法至少也得是`internal`。
|
||||
采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。也就是说如果一个类型是 `public` 级别,采纳的协议是 `internal` 级别,那么采纳了这个协议后,该类型作为符合协议的类型时,其访问级别也是 `internal`。
|
||||
|
||||
> 注意:Swift和Objective-C一样,协议的一致性保证了一个类不可能在同一个程序中用不同的方法采用同一个协议。
|
||||
如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 `public` 级别的类型,采纳了 `internal` 级别的协议,那么协议的实现至少也得是 `internal` 级别。
|
||||
|
||||
> 注意
|
||||
Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
|
||||
|
||||
<a name="extensions"></a>
|
||||
## 扩展
|
||||
你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型,那么你新加的成员应该具有和原始成员一样的默认的`internal`访问级别。
|
||||
|
||||
或者,你可以明确申明扩展的访问级别(比如使用`private extension`)给该扩展内所有成员申明一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所申明的访问级别所覆盖。
|
||||
你可以在访问级别允许的情况下对类、结构体、枚举进行扩展。扩展成员具有和原始类型成员一致的访问级别。例如,你扩展了一个 `public` 或者 `internal` 类型,扩展中的成员具有默认的 `internal` 访问级别,和原始类型中的成员一致 。如果你扩展了一个 `private` 类型,扩展成员则拥有默认的 `private` 访问级别。
|
||||
|
||||
或者,你可以明确指定扩展的访问级别(例如,`private extension`),从而给该扩展中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。
|
||||
|
||||
<a name="adding_protocol_conformance_with_an_extension"></a>
|
||||
### 协议的扩展
|
||||
如果一个扩展采用了某个协议,那么你就不能对该扩展使用访问级别修饰符来申明了。该扩展中实现协议的方法都会遵循该协议的访问级别。
|
||||
### 通过扩展添加协议一致性
|
||||
|
||||
如果你通过扩展来采纳协议,那么你就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中所有协议要求的实现提供默认的访问级别。
|
||||
|
||||
<a name="generics"></a>
|
||||
## 泛型
|
||||
泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。
|
||||
|
||||
泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。
|
||||
|
||||
<a name="type_aliases"></a>
|
||||
## 类型别名
|
||||
任何你定义的类型别名都会被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。比如说,一个`private`级别的类型别名可以设定给一个`public`、`internal`、`private`的类型,但是一个`public`级别的类型别名只能设定给一个`public`级别的类型,不能设定给`internal`或`private` 级别的类型。
|
||||
|
||||
> 注意:这条规则也适用于为满足协议一致性而给相关类型命名别名的情况。
|
||||
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `public`、`internal`、`private` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal` 或 `private` 类型的别名。
|
||||
|
||||
> 注意
|
||||
这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。
|
||||
|
||||
@ -8,241 +8,253 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[buginux](https://github.com/buginux)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-17
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [位运算符](#bitwise_operators)
|
||||
- [溢出运算符](#overflow_operators)
|
||||
- [优先级和结合性(Precedence and Associativity)](#precedence_and_associativity)
|
||||
- [运算符函数(Operator Functions)](#operator_functions)
|
||||
- [优先级和结合性](#precedence_and_associativity)
|
||||
- [运算符函数](#operator_functions)
|
||||
- [自定义运算符](#custom_operators)
|
||||
|
||||
除了在之前介绍过的[基本运算符](./02_Basic_Operators.html),Swift 中还有许多可以对数值进行复杂操作的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
|
||||
除了在之前介绍过的[基本运算符](./02_Basic_Operators.html),Swift 中还有许多可以对数值进行复杂运算的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
|
||||
|
||||
与C语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
|
||||
与 C 语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
|
||||
|
||||
在定义自有的结构体、类和枚举时,最好也同时为它们提供标准Swift运算符的实现。Swift简化了运算符的自定义实现,也使判断不同类型所对应的行为更为简单。
|
||||
自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中自定义运算符非常简单,运算符也会针对不同类型使用对应实现。
|
||||
|
||||
我们不用被预定义的运算符所限制。在 Swift 当中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。这些运算符在代码中可以像预设的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。
|
||||
我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。这些运算符在代码中可以像预定义的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。
|
||||
|
||||
<a name="bitwise_operators"></a>
|
||||
## 位运算符
|
||||
|
||||
位运算符(`Bitwise operators`)可以操作一个数据结构中每个独立的位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
|
||||
位运算符可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
|
||||
|
||||
Swift 支持C语言中的全部位运算符,具体如下:
|
||||
Swift 支持 C 语言中的全部位运算符,接下来会一一介绍。
|
||||
|
||||
### 按位取反运算符(`bitwise NOT operator`)
|
||||
<a name="bitwise_not_operator"></a>
|
||||
### 按位取反运算符
|
||||
|
||||
按位取反运算符(`~`)可以对一个数值的全部比特位进行取反:
|
||||
|
||||
按位取反运算符(`~`) 可以对一个数值的全部位进行取反:
|
||||
|
||||

|
||||
|
||||
按位取反操作符是一个前置运算符,需要直接放在操作数的之前,并且它们之间不能添加任何空格。
|
||||
按位取反运算符是一个前缀运算符,需要直接放在运算的数之前,并且它们之间不能添加任何空格:
|
||||
|
||||
```
|
||||
```swift
|
||||
let initialBits: UInt8 = 0b00001111
|
||||
let invertedBits = ~initialBits // 等于 0b11110000
|
||||
let invertedBits = ~initialBits // 等于 0b11110000
|
||||
```
|
||||
|
||||
`UInt8` 类型的整数有 8 个比特位,可以存储 0 ~ 255之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位都为`0`,后 4 位都为`1`。这个值等价于十进制的 `15` 。
|
||||
`UInt8` 类型的整数有 8 个比特位,可以存储 `0 ~ 255` 之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位都为 `0`,后 4 位都为 `1`。这个值等价于十进制的 `15`。
|
||||
|
||||
接着使用按位取反运算符创建了一个名为 `invertedBits` 的常量,这个常量的值与全部位取反后的 `initialBits` 相等。即所有的 `0` 都变成了 `1`,同时所有的 `1` 都变成 `0`。`invertedBits` 的二进制值为 `11110000`,等价于无符号十进制数的 `240`。
|
||||
|
||||
### 按位与运算符(Bitwise AND Operator)
|
||||
<a name="bitwise_and_operator"></a>
|
||||
### 按位与运算符
|
||||
|
||||
按位与运算符(`&`)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个操作数的对应位*都*为 `1` 的时候,该数的对应位才为 `1`。
|
||||
按位与运算符(`&`)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位都为 `1` 的时候,新数的对应位才为 `1`:
|
||||
|
||||

|
||||

|
||||
|
||||
在下面的示例当中,`firstSixBits` 和 `lastSixBits` 中间 4 个位的值都为 1 。按位与运算符对它们进行了运算,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`:
|
||||
在下面的示例当中,`firstSixBits` 和 `lastSixBits` 中间 4 个位的值都为 `1`。按位与运算符对它们进行了运算,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`:
|
||||
|
||||
```
|
||||
```swift
|
||||
let firstSixBits: UInt8 = 0b11111100
|
||||
let lastSixBits: UInt8 = 0b00111111
|
||||
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
|
||||
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
|
||||
```
|
||||
|
||||
### 按位或运算符(Bitwise OR Operator)
|
||||
<a name="bitwise_or_operator"></a>
|
||||
### 按位或运算符
|
||||
|
||||
按位或运算符(`|`)可以对两个数的比特位进行比较。它返回一个新的数,只要两个操作数的对应位中有*任意*一个为 `1` 时,该数的对应位就为 `1`。
|
||||
按位或运算符(`|`)可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有任意一个为 `1` 时,新数的对应位就为 `1`:
|
||||
|
||||

|
||||
|
||||
在下面的示例当中,`someBits` 和 `moreBits` 将不同的位设置为 `1`。接位或运算符对它们进行了运算,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`:
|
||||
在下面的示例中,`someBits` 和 `moreBits` 不同的位会被设置为 `1`。接位或运算符对它们进行了运算,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`:
|
||||
|
||||
```
|
||||
```swift
|
||||
let someBits: UInt8 = 0b10110010
|
||||
let moreBits: UInt8 = 0b01011110
|
||||
let combinedbits = someBits | moreBits // 等于 11111110
|
||||
let combinedbits = someBits | moreBits // 等于 11111110
|
||||
```
|
||||
|
||||
### 按位异或运算符(Bitwise XOR Opoerator)
|
||||
<a name="bitwise_xor_operator"></a>
|
||||
### 按位异或运算符
|
||||
|
||||
按位异或运算符(`^`)可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位不相同时,该数的对应位就为 `1`:
|
||||
按位异或运算符(`^`)可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`:
|
||||
|
||||

|
||||
|
||||
在下面的示例当中,`firstBits` 和 `otherBits` 都有一个自己设置为 `1` 而对方设置为 `0` 的位。 按位异或运算符将这两个位都设置为 `1`,同时将其它位都设置为 `0`:
|
||||
在下面的示例当中,`firstBits` 和 `otherBits` 都有一个自己的位为 `1` 而对方的对应位为 `0` 的位。 按位异或运算符将新数的这两个位都设置为 `1`,同时将其它位都设置为 `0`:
|
||||
|
||||
```
|
||||
```swift
|
||||
let firstBits: UInt8 = 0b00010100
|
||||
let otherBits: UInt8 = 0b00000101
|
||||
let outputBits = firstBits ^ otherBits // 等于 00010001
|
||||
let outputBits = firstBits ^ otherBits // 等于 00010001
|
||||
```
|
||||
|
||||
### 按位左移/右移运算符
|
||||
<a name="bitwise_left_and_right_shift_operators"></a>
|
||||
### 按位左移、右移运算符
|
||||
|
||||
按位左移运算符(`<<`)和按位右移运算符(`>>`)可以对一个数进行指定位数的左移和右移,但是需要遵守下面定义的规则。
|
||||
按位左移运算符(`<<`)和按位右移运算符(`>>`)可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
|
||||
|
||||
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。
|
||||
|
||||
#### 无符号整型的移位操作
|
||||
<a name="shifting_behavior_for_unsigned_integers"></a>
|
||||
#### 无符号整数的移位运算
|
||||
|
||||
对无符号整型进行移位的规则如下:
|
||||
对无符号整数进行移位的规则如下:
|
||||
|
||||
1. 已经存在的比特位按指定的位数进行左移和右移。
|
||||
2. 任何移动超出整型存储边界的位都会被丢弃。
|
||||
3. 用 0 来填充移动后产生的空白位。
|
||||
1. 已经存在的位按指定的位数进行左移和右移。
|
||||
2. 任何因移动而超出整型存储范围的位都会被丢弃。
|
||||
3. 用 `0` 来填充移位后产生的空白位。
|
||||
|
||||
这种方法称为逻辑移位(`logical shift`)。
|
||||
这种方法称为逻辑移位。
|
||||
|
||||
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 1 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 1 位) 的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的。
|
||||
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的:
|
||||
|
||||

|
||||
|
||||
下面的代码演示了 Swift 中的移位操作:
|
||||
下面的代码演示了 Swift 中的移位运算:
|
||||
|
||||
```
|
||||
let shiftBits: UInt8 = 4 // 即二进制的00000100
|
||||
shiftBits << 1 // 00001000
|
||||
shiftBits << 2 // 00010000
|
||||
shiftBits << 5 // 10000000
|
||||
shiftBits << 6 // 00000000
|
||||
shiftBits >> 2 // 00000001
|
||||
```swift
|
||||
let shiftBits: UInt8 = 4 // 即二进制的 00000100
|
||||
shiftBits << 1 // 00001000
|
||||
shiftBits << 2 // 00010000
|
||||
shiftBits << 5 // 10000000
|
||||
shiftBits << 6 // 00000000
|
||||
shiftBits >> 2 // 00000001
|
||||
```
|
||||
|
||||
可以使用移位操作对其他的数据类型进行编码和解码:
|
||||
可以使用移位运算对其他的数据类型进行编码和解码:
|
||||
|
||||
```
|
||||
```swift
|
||||
let pink: UInt32 = 0xCC6699
|
||||
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC, 即 204
|
||||
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102
|
||||
let blueComponent = pink & 0x0000FF // blueComponent 是 0x99, 即 153
|
||||
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC,即 204
|
||||
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102
|
||||
let blueComponent = pink & 0x0000FF // blueComponent 是 0x99,即 153
|
||||
```
|
||||
|
||||
这个示例使用了一个命名为 `pink` 的 `UInt32` 型常量来存储层叠样式表(`CSS`)中粉色的颜色值。该 `CSS` 的十六进制颜色值 `#CC6699`, 在 Swift 中表示为 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
|
||||
这个示例使用了一个命名为 `pink` 的 `UInt32` 型常量来存储 CSS 中粉色的颜色值。该 CSS 的十六进制颜色值 `#CC6699`,在 Swift 中表示为 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
|
||||
|
||||
红色部分是通过对 `0xCC6699` 和 `0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分作为*掩码*,掩盖了 `OxCC6699` 中的第二和第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`。
|
||||
红色部分是通过对 `0xCC6699` 和 `0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分“掩盖”了 `OxCC6699` 中的第二、第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`。
|
||||
|
||||
然后,再将这个数按向右移动 16 位(`>> 16`)。十六进制中每两个字符表示 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和`0xCC`是等同的,也就是十进制数值的 `204`。
|
||||
然后,再将这个数按向右移动 16 位(`>> 16`)。十六进制中每两个字符表示 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和`0xCC`是等同的,也就是十进制数值的 `204`。
|
||||
|
||||
同样的,绿色部分通过对 `0xCC6699` 和 `0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`,也就是十进制数值的 `102`。
|
||||
|
||||
最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。并且不需要进行向右移位,所以结果为 `0x99` ,也就是十进制数值的 `153`。
|
||||
最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。这里不需要再向右移位,所以结果为 `0x99` ,也就是十进制数值的 `153`。
|
||||
|
||||
#### 有符号整型的移位操作
|
||||
<a name="shifting_behavior_for_signed_integers"></a>
|
||||
#### 有符号整数的移位运算
|
||||
|
||||
对比无符号整型来说,有符整型的移位操作相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 位有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。)
|
||||
对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特位的有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。)
|
||||
|
||||
有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
|
||||
有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
|
||||
|
||||
其余的比特位(通常被称为数值位)存储了这个数的真实值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4` 的 `Int8` 型整数的二进制位表现形式:
|
||||
其余的比特位(通常被称为数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4` 的 `Int8` 型整数的二进制位表现形式:
|
||||
|
||||

|
||||
|
||||
符号位为 `0`,说明这是一个正数,另外 7 位则代表了十进制数值 `4` 的二进制表示。
|
||||
|
||||
负数的存储方式略有不同。它存储的是 `2` 的 n 次方减去它的真实值绝对值,这里的 n 为数值位的位数。一个 8 位的数有 7 个数值位,所以是 2 的 7 次方,即 128。
|
||||
负数的存储方式略有不同。它存储的值的绝对值等于 `2` 的 `n` 次方减去它的实际值(也就是数值位表示的值),这里的 `n` 为数值位的比特位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2` 的 `7` 次方,即 `128`。
|
||||
|
||||
这是值为 `-4` 的 `Int8` 型整数的二进制位表现形式:
|
||||
|
||||

|
||||
|
||||
这次的符号位为 `1`,说明这是一个负数,另外 7 个位则代表了数值 `124`(即 `128 - 4`) 的二进制表示。
|
||||
这次的符号位为 `1`,说明这是一个负数,另外 7 个位则代表了数值 `124`(即 `128 - 4`)的二进制表示:
|
||||
|
||||

|
||||
|
||||
负数的表示通常被称为二进制补码(`two's complement`)表示法。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
|
||||
负数的表示通常被称为二进制补码表示。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
|
||||
|
||||
首先,如果想对 `-1` 和 `-4` 进行加法操作,我们只需要将这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 8 位的数值丢弃:
|
||||
首先,如果想对 `-1` 和 `-4` 进行加法运算,我们只需要将这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 8 位的数值丢弃:
|
||||
|
||||

|
||||
|
||||
其次,使用二进制补码可以使负数的按位左移和右移操作得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则:
|
||||
其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则:
|
||||
|
||||
* 当对正整数进行按位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用*符号位*进行填充,而不是用 0。
|
||||
* 当对整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是用 `0`。
|
||||
|
||||

|
||||
|
||||
这个行为可以确保有符号整数的符号位不会因为右移操作而改变,这通常被称为算术移位(`arithmetic shift`)。
|
||||
这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为算术移位。
|
||||
|
||||
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 0。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。
|
||||
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 `0`。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。
|
||||
|
||||
<a name="overflow_operators"></a>
|
||||
## 溢出运算符
|
||||
|
||||
在默认情况下,当向一个整数赋超过它容量的值时,Swift 默认会报错,而不是生成一个无效的数。这个行为给我们操作过大或着过小的数的时候提供了额外的安全性。
|
||||
在默认情况下,当向一个整数赋予超过它容量的值时,Swift 默认会报错,而不是生成一个无效的数。这个行为为我们在运算过大或着过小的数的时候提供了额外的安全性。
|
||||
|
||||
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768` 到 `32767`,当为一个 `Int16` 型变量赋的值超过这个范围时,系统就会报错:
|
||||
|
||||
```
|
||||
```swift
|
||||
var potentialOverflow = Int16.max
|
||||
// potentialOverflow 的值是 32767, 这是 Int16 能容纳的最大整数
|
||||
|
||||
// potentialOverflow 的值是 32767,这是 Int16 能容纳的最大整数
|
||||
potentialOverflow += 1
|
||||
// 这里会报错
|
||||
```
|
||||
|
||||
为过大或者过小的数值提供错误处理,能让我们在处理边界值时更加灵活。
|
||||
|
||||
然而,也可以选择让系统在数值溢出的时候采取截断操作,而非报错。可以使用 Swift 提供的三个溢出操作符(`overflow operators`)来让系统支持整数溢出运算。这些操作符都是以 `&` 开头的:
|
||||
然而,也可以选择让系统在数值溢出的时候采取截断处理,而非报错。可以使用 Swift 提供的三个溢出运算符来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的:
|
||||
|
||||
* 溢出加法 `&+`
|
||||
* 溢出减法 `&-`
|
||||
* 溢出乘法 `&*`
|
||||
|
||||
<a name="value_overflow"></a>
|
||||
### 数值溢出
|
||||
|
||||
数值有可能出现上溢或者下溢。
|
||||
|
||||
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
|
||||
```
|
||||
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
|
||||
|
||||
```swift
|
||||
var unsignedOverflow = UInt8.max
|
||||
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
|
||||
|
||||
unsignedOverflow = unsignedOverflow &+ 1
|
||||
// 此时 unsignedOverflow 等于 0
|
||||
```
|
||||
|
||||
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用了溢出加法运算符(`&+`)对其进行加 1 操作。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 0。
|
||||
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用了溢出加法运算符(`&+`)对其进行加 `1` 运算。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 `0`。
|
||||
|
||||

|
||||
|
||||
同样地,当我们对一个无符号整数使用溢出减法(`&-`)进行下溢运算时也会产生类似的现象:
|
||||
同样地,当我们对一个无符号整数使用溢出减法(`&-`)进行下溢运算时也会产生类似的现象:
|
||||
|
||||
```
|
||||
```swift
|
||||
var unsignedOverflow = UInt8.min
|
||||
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
|
||||
|
||||
unsignedOverflow = unsignedOverflow &- 1
|
||||
// 此时 unsignedOverflow 等于 255
|
||||
```
|
||||
|
||||
`UInt8` 型整数能容纳的最小值是 0,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 1 操作时,数值会产生下溢并被截断为 `11111111`, 也就是十进制数值的 255。
|
||||
`UInt8` 型整数能容纳的最小值是 `0`,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 `1` 运算时,数值会产生下溢并被截断为 `11111111`, 也就是十进制数值的 `255`。
|
||||
|
||||

|
||||
|
||||
溢出也会发生在有符号整型数值上。在对有符号整型数值进行溢出加法或溢出减法运算时,符号位也需要参与计算,正如[按位左移/右移运算符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID34)所描述的。
|
||||
溢出也会发生在有符号整型数值上。在对有符号整型数值进行溢出加法或溢出减法运算时,符号位也需要参与计算,正如[按位左移、右移运算符](#bitwise_left_and_right_shift_operators)所描述的。
|
||||
|
||||
```
|
||||
```swift
|
||||
var signedOverflow = Int8.min
|
||||
// signedOverflow 等于 Int8 所能容纳的最小整数 -128
|
||||
|
||||
signedOverflow = signedOverflow &- 1
|
||||
// 此时 signedOverflow 等于 127
|
||||
```
|
||||
|
||||
`Int8` 型整数能容纳的最小值是 -128,以二进制表示即 `10000000`。当使用溢出减法操作符对其进行减 1 操作时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整数所能容纳的最大值。
|
||||
`Int8` 型整数能容纳的最小值是 `-128`,以二进制表示即 `10000000`。当使用溢出减法运算符对其进行减 `1` 运算时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整数所能容纳的最大值。
|
||||
|
||||

|
||||
|
||||
@ -251,92 +263,91 @@ signedOverflow = signedOverflow &- 1
|
||||
<a name="precedence_and_associativity"></a>
|
||||
## 优先级和结合性
|
||||
|
||||
运算符的优先级(`precedence`)使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
|
||||
运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
|
||||
|
||||
结合性(`associativity`)定义了相同优先级的运算符是如何结合(或关联)的 —— 是与左边结合为一组,还是与右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。
|
||||
结合性定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。
|
||||
|
||||
在复合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,为什么下面这个表达式的运算结果是 `4`?
|
||||
在复合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`。
|
||||
|
||||
```swift
|
||||
2 + 3 * 4 % 5
|
||||
// 结果是 4
|
||||
2 + 3 % 4 * 5
|
||||
// 结果是 17
|
||||
```
|
||||
|
||||
如果严格地从左到右进行运算,则运算的过程是这样的:
|
||||
如果完全从左到右进行运算,则运算的过程是这样的:
|
||||
|
||||
- 2 + 3 = 5
|
||||
- 5 * 4 = 20
|
||||
- 20 % 5 = 0
|
||||
- 5 % 4 = 1
|
||||
- 1 * 5 = 5
|
||||
|
||||
但是正确答案是 `4` 而不是 `0`。优先级高的运算符要先于优先级低的运算符进行计算。与C语言类似,在 Swift 当中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
|
||||
但是正确答案是 `17` 而不是 `5`。优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
|
||||
|
||||
而乘法与取余的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性。乘法与取余运算都是左结合的。可以将这考虑成为这两部分表达式都隐式地加上了括号:
|
||||
|
||||
```swift
|
||||
2 + ((3 * 4) % 5)
|
||||
2 + ((3 % 4) * 5)
|
||||
```
|
||||
|
||||
`(3 * 4) = 12`,所以表达式相当于:
|
||||
|
||||
`(3 % 4)` 等于 `3`,所以表达式相当于:
|
||||
|
||||
```swift
|
||||
2 + (12 % 5)
|
||||
2 + (3 * 5)
|
||||
```
|
||||
|
||||
`12 % 5 = 2`,所以表达式相当于:
|
||||
`3 * 5` 等于 `15`,所以表达式相当于:
|
||||
|
||||
```swift
|
||||
2 + 2
|
||||
2 + 15
|
||||
```
|
||||
|
||||
此时可以容易地看出计算的结果为 `4`。
|
||||
因此计算结果为 `17`。
|
||||
|
||||
如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。
|
||||
如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。如果想查看 Swift 标准库提供所有的运算符,请查看 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
|
||||
|
||||
> 注意:
|
||||
> 对于C语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则是更加简洁和可预测的。但是,这也意味着它们于那些基于C的语言不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然是按照你所想的那样去执行。
|
||||
> 注意
|
||||
> 相对 C 语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。
|
||||
|
||||
<a name="operator_functions"></a>
|
||||
## 运算符函数
|
||||
|
||||
类和结构可以为现有的操作符提供自定义的实现,这通常被称为运算符重载(`overloading`)。
|
||||
类和结构体可以为现有的运算符提供自定义的实现,这通常被称为运算符重载。
|
||||
|
||||
下面的例子展示了如何为自定义的结构实现加法操作符(`+`)。算术加法运算符是一个两目运算符(`binary operator`),因为它可以对两个目标进行操作,同时它还是中缀(`infix`)运算符,因为它出现在两个目标中间。
|
||||
下面的例子展示了如何为自定义的结构体实现加法运算符(`+`)。算术加法运算符是一个双目运算符,因为它可以对两个值进行运算,同时它还是中缀运算符,因为它出现在两个值中间。
|
||||
|
||||
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量`(x, y)`,紧接着定义了一个可以对两个 `Vector2D` 结构体进行相加的运算符函数(`operator function`):
|
||||
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以对两个 `Vector2D` 结构体进行相加的运算符函数:
|
||||
|
||||
```swift
|
||||
struct Vector2D {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
|
||||
func + (left: Vector2D, right: Vector2D) -> Vector2D {
|
||||
return Vector2D(x: left.x + right.x, y: left.y + right.y)
|
||||
}
|
||||
```
|
||||
|
||||
该运算符函数被定义为一个全局函数,并且函数的名字与它要进行重载的 `+` 名字一致。因为算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的输入参数,同时有一个 `Vector2D` 类型的返回值。
|
||||
该运算符函数被定义为一个全局函数,并且函数的名字与它要进行重载的 `+` 名字一致。因为算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。
|
||||
|
||||
在这个实现中,输入参数分别被命名为 `left` 和 `right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 对象。函数返回了一个新的 `Vector2D` 的对象,这个对象的 `x` 和 `y` 分别等于两个参数对象的 `x` 和 `y` 的值之和。
|
||||
在这个实现中,输入参数分别被命名为 `left` 和 `right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 实例。函数返回了一个新的 `Vector2D` 实例,这个实例的 `x` 和 `y` 分别等于作为参数的两个实例的 `x` 和 `y` 的值之和。
|
||||
|
||||
这个函数被定义成全局的,而不是 `Vector2D` 结构的成员方法,所以任意两个 `Vector2D` 对象都可以使用这个中缀运算符:
|
||||
这个函数被定义成全局的,而不是 `Vector2D` 结构体的成员方法,所以任意两个 `Vector2D` 实例都可以使用这个中缀运算符:
|
||||
|
||||
```swift
|
||||
let vector = Vector2D(x: 3.0, y: 1.0)
|
||||
let anotherVector = Vector2D(x: 2.0, y: 4.0)
|
||||
let combinedVector = vector + anotherVector
|
||||
// combinedVector 是一个新的Vector2D, 值为 (5.0, 5.0)
|
||||
// combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0)
|
||||
```
|
||||
|
||||
这个例子实现两个向量 `(3.0,1.0)` 和 `(2.0,4.0)` 的相加,并得到新的向量 `(5.0,5.0)`。这个过程如下图示:
|
||||
|
||||

|
||||
|
||||
<a name="prefix_and_postfix_operators"></a>
|
||||
### 前缀和后缀运算符
|
||||
|
||||
上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准单目运算符(`unary operators`)的实现。单目运算符只有一个操作目标。当运算符出现在操作目标之前时,它就是前缀(`prefix`)的(比如 `-a`),而当它出现在操作目标之后时,它就是后缀(`postfix`)的(比如 `i++`)。
|
||||
上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准单目运算符的实现。单目运算符只运算一个值。当运算符出现在值之前时,它就是前缀的(例如 `-a`),而当它出现在值之后时,它就是后缀的(例如 `b!`)。
|
||||
|
||||
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 限定符:
|
||||
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 修饰符:
|
||||
|
||||
```swift
|
||||
prefix func - (vector: Vector2D) -> Vector2D {
|
||||
@ -344,22 +355,21 @@ prefix func - (vector: Vector2D) -> Vector2D {
|
||||
}
|
||||
```
|
||||
|
||||
这段代码为 `Vector2D` 类型实现了单目减运算符(`-a`)。由于单目减运算符是前缀运算符,所以这个函数需要加上 `prefix` 限定符。
|
||||
这段代码为 `Vector2D` 类型实现了单目负号运算符。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。
|
||||
|
||||
对于简单数值,单目减运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,单目减运算将其 `x` 和 `y` 属性的正负性都进行了改变。
|
||||
对于简单数值,单目负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x` 和 `y` 属性的正负性都进行了改变:
|
||||
|
||||
```swift
|
||||
let positive = Vector2D(x: 3.0, y: 4.0)
|
||||
let negative = -positive
|
||||
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
|
||||
|
||||
let alsoPositive = -negative
|
||||
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
|
||||
```
|
||||
|
||||
<a name="compound_assignment_operators"></a>
|
||||
### 复合赋值运算符
|
||||
|
||||
复合赋值运算符(`Compound assignment operators`)将赋值运算符(`=`)与其它运算符进行结合。比如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。
|
||||
复合赋值运算符将赋值运算符(`=`)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。
|
||||
|
||||
```swift
|
||||
func += (inout left: Vector2D, right: Vector2D) {
|
||||
@ -376,33 +386,16 @@ original += vectorToAdd
|
||||
// original 的值现在为 (4.0, 6.0)
|
||||
```
|
||||
|
||||
还可以将赋值与 `prefix` 或 `postfix` 限定符结合起来,下面的代码为 `Vector2D` 实例实现了前缀自增运算符(`++a`):
|
||||
|
||||
```swift
|
||||
prefix func ++ (inout vector: Vector2D) -> Vector2D {
|
||||
vector += Vector2D(x: 1.0, y: 1.0)
|
||||
return vector
|
||||
}
|
||||
```
|
||||
|
||||
这个前缀自增运算符使用了前面定义的加法赋值操作。它对 `Vector2D` 的 `x` 和 `y` 属性都进行了加 `1` 操作,再将结果返回:
|
||||
|
||||
```swift
|
||||
var toIncrement = Vector2D(x: 3.0, y: 4.0)
|
||||
let afterIncrement = ++toIncrement
|
||||
// toIncrement 的值现在为 (4.0, 5.0)
|
||||
// afterIncrement 的值同样为 (4.0, 5.0)
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 不能对默认的赋值运算符(`=`)进行重载。只有组合赋值符可以被重载。同样地,也无法对三目条件运算符 `a ? b : c` 进行重载。
|
||||
> 注意
|
||||
> 不能对默认的赋值运算符(`=`)进行重载。只有组合赋值运算符可以被重载。同样地,也无法对三目条件运算符 (`a ? b : c`) 进行重载。
|
||||
|
||||
<a name="equivalence_operators"></a>
|
||||
### 等价操作符
|
||||
### 等价运算符
|
||||
|
||||
自定义的类和结构体没有对等价操作符(`equivalence operators`)进行默认实现,等价操作符通常被称为“相等”操作符(`==`)与“不等”操作符(`!=`)。对于自定义类型,Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。
|
||||
自定义的类和结构体没有对等价运算符进行默认实现,等价运算符通常被称为“相等”运算符(`==`)与“不等”运算符(`!=`)。对于自定义类型,Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。
|
||||
|
||||
为了使用等价操作符来对自定义的类型进行判等操作,需要为其提供自定义实现,实现的方法与其它中缀运算符一样:
|
||||
为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样:
|
||||
|
||||
```swift
|
||||
func == (left: Vector2D, right: Vector2D) -> Bool {
|
||||
@ -413,9 +406,9 @@ func != (left: Vector2D, right: Vector2D) -> Bool {
|
||||
}
|
||||
```
|
||||
|
||||
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 对象是否有相等。对于 `Vector2D` 类型来说,“相等”意味“两个实例的 `x`属性 和 `y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”操作符(`!=`),它简单地将“相等”操作符进行取反后返回。
|
||||
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 类型来说,“相等”意味着“两个实例的 `x` 属性和 `y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”运算符(`!=`),它简单地将“相等”运算符的结果进行取反后返回。
|
||||
|
||||
现在我们可以使用这两个运算符来判断两个 `Vector2D` 对象是否相等。
|
||||
现在我们可以使用这两个运算符来判断两个 `Vector2D` 实例是否相等:
|
||||
|
||||
```swift
|
||||
let twoThree = Vector2D(x: 2.0, y: 3.0)
|
||||
@ -423,21 +416,21 @@ let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
|
||||
if twoThree == anotherTwoThree {
|
||||
print("These two vectors are equivalent.")
|
||||
}
|
||||
// prints "These two vectors are equivalent."
|
||||
// 打印 “These two vectors are equivalent.”
|
||||
```
|
||||
|
||||
<a name="custom_operators"></a>
|
||||
### 自定义运算符
|
||||
## 自定义运算符
|
||||
|
||||
除了实现标准运算符,在 Swift 当中还可以声明和实现自定义运算符(`custom operators`)。可以用来自定义运算符的字符列表请参考[操作符](../chapter3/02_Lexical_Structure.html#operators)
|
||||
除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。可以用来自定义运算符的字符列表请参考[运算符](../chapter3/02_Lexical_Structure.html#operators)。
|
||||
|
||||
新的运算符要在全局作用域内,使用 `operator` 关键字进行声明,同时还要指定 `prefix`、`infix` 或者 `postfix` 限定符:
|
||||
新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix`、`infix` 或者 `postfix` 修饰符:
|
||||
|
||||
```swift
|
||||
prefix operator +++ {}
|
||||
```
|
||||
|
||||
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因为我们针对 `Vector2D` 的实例来定义它的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法操作符来让矩阵对自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y`属性的值翻倍:
|
||||
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因此我们针对 `Vector2D` 的实例来定义它的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵对自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性的值翻倍:
|
||||
|
||||
```swift
|
||||
prefix func +++ (inout vector: Vector2D) -> Vector2D {
|
||||
@ -446,7 +439,7 @@ prefix func +++ (inout vector: Vector2D) -> Vector2D {
|
||||
}
|
||||
```
|
||||
|
||||
`Vector2D` 的 `+++` 的实现和 `++` 的实现很相似, 唯一不同的是前者对自身进行相加, 而后者是与另一个值为 `(1.0, 1.0)` 的向量相加.
|
||||
`Vector2D` 的 `+++` 的实现和 `++` 的实现很相似,唯一不同的是前者对自身进行相加,而后者是与另一个值为 `(1.0, 1.0)` 的向量相加。
|
||||
|
||||
```swift
|
||||
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
|
||||
@ -455,15 +448,16 @@ let afterDoubling = +++toBeDoubled
|
||||
// afterDoubling 现在的值也为 (2.0, 8.0)
|
||||
```
|
||||
|
||||
<a name="precedence_and_associativity_for_custom_infix_operators"></a>
|
||||
### 自定义中缀运算符的优先级和结合性
|
||||
|
||||
自定义的中缀(`infix`)运算符也可以指定优先级(`precedence`)和结合性(`associativity`)。[优先级和结合性](#PrecedenceandAssociativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
|
||||
自定义的中缀运算符也可以指定优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
|
||||
|
||||
结合性(`associativity`)可取的值有` left`,`right` 和 `none`。当左结合运算符跟其他相同优先级的左结合运算符写在一起时,会跟左边的操作数进行结合。同理,当右结合运算符跟其他相同优先级的右结合运算符写在一起时,会跟右边的操作数进行结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。
|
||||
结合性可取的值有` left`,`right` 和 `none`。当左结合运算符跟其他相同优先级的左结合运算符写在一起时,会跟左边的值进行结合。同理,当右结合运算符跟其他相同优先级的右结合运算符写在一起时,会跟右边的值进行结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。
|
||||
|
||||
结合性(`associativity`)的默认值是 `none`,优先级(`precedence`)如果没有指定,则默认为 `100`。
|
||||
结合性的默认值是 `none`,优先级的默认值 `100`。
|
||||
|
||||
以下例子定义了一个新的中缀运算符 `+-`,此操作符是左结合的,并且它的优先级为 `140`:
|
||||
以下例子定义了一个新的中缀运算符 `+-`,此运算符的结合性为 `left`,并且它的优先级为 `140`:
|
||||
|
||||
```swift
|
||||
infix operator +- { associativity left precedence 140 }
|
||||
@ -473,10 +467,10 @@ func +- (left: Vector2D, right: Vector2D) -> Vector2D {
|
||||
let firstVector = Vector2D(x: 1.0, y: 2.0)
|
||||
let secondVector = Vector2D(x: 3.0, y: 4.0)
|
||||
let plusMinusVector = firstVector +- secondVector
|
||||
// plusMinusVector 是一个 Vector2D 类型,并且它的值为 (4.0, -2.0)
|
||||
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
|
||||
```
|
||||
|
||||
这个运算符把两个向量的 `x` 值相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“加型”运算符,所以将它的结合性和优先级被设置为(`left` 和 `140`),这与 `+` 和 `-` 等默认的中缀加型操作符是相同的。完整的 Swift 操作符默认结合性与优先级请参考[表达式](../chapter3/04_Expressions.html)。
|
||||
这个运算符把两个向量的 `x` 值相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“相加型”运算符,所以将它的结合性和优先级被分别设置为 `left` 和 `140`,这与 `+` 和 `-` 等默认的中缀“相加型”运算符是相同的。关于 Swift 标准库提供的运算符的结合性与优先级,请参考 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
|
||||
|
||||
> 注意:
|
||||
> 当定义前缀与后缀操作符的时候,我们并没有指定优先级。然而,如果对同一个操作数同时使用前缀与后缀操作符,则后缀操作符会先被执行。
|
||||
> 注意
|
||||
> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。
|
||||
|
||||
@ -12,34 +12,29 @@
|
||||
|
||||
- [如何阅读语法](#how_to_read_the_grammar)
|
||||
|
||||
本书的这一节描述了Swift编程语言的形式语法。这里描述的语法是为了帮助您更详细的了解该语言,而不是让您直接实现一个解析器或编译器。
|
||||
本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您更详细地了解该语言,而不是让您直接实现一个解析器或编译器。
|
||||
|
||||
Swift语言相对小一点,这是由于在Swift代码中几乎所有常见的类型、函数以及运算符都已经在Swift标准库中定义了。虽然这些类型、函数和运算符并不是Swift语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
|
||||
Swift 语言相对较小,这是由于 Swift 代码中的几乎所有常见类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
|
||||
|
||||
<a name="how_to_read_the_grammar"></a>
|
||||
## 如何阅读语法
|
||||
|
||||
用来描述Swift编程语言形式语法的标记遵循下面几个约定:
|
||||
|
||||
- 箭头(→)用来标记语法产式,可以理解为“可以包含”。
|
||||
- *斜体*文字用来表示句法分类,并出现在一个语法产式规则两侧。
|
||||
- 义词和标点符号由粗体固定宽度的文本标记,而且只出现在一个语法产式规则的右侧。
|
||||
- 选择性的语法产式由竖线(|)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。
|
||||
- 少数情况下,常规字体文字用来描述语法产式规则的右边。
|
||||
- 可选的句法分类和文字用尾标`opt`来标记。
|
||||
|
||||
举个例子,getter-setter的语法块的定义如下:
|
||||
|
||||
> GRAMMAR OF A GETTER-SETTER BLOCK
|
||||
> *getter-setter-block* → { [*getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause) [*setter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause)*opt* } | { [*setter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause) [*getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause)}
|
||||
|
||||
这个定义表明,一个getter-setter方法块可以由一个getter子句后跟一个可选的setter子句构成,然后用大括号括起来,或者由一个setter子句后跟一个getter子句构成,然后用大括号括起来。下面的两个语法产式等价于上述的语法产式,并明确指出了如何取舍:
|
||||
|
||||
> GRAMMAR OF A GETTER-SETTER BLOCK
|
||||
> getter-setter-block → { [*getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause) [*setter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause)*opt* }
|
||||
> getter-setter-block → { [*setter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause) [*getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause)}
|
||||
用来描述 Swift 编程语言形式语法的符号遵循下面几个约定:
|
||||
|
||||
- 箭头(`→`)用来标记语法产式,可以理解为“可由……构成”。
|
||||
- 斜体文字用来表示句法类型,并出现在一个语法产式规则两侧。
|
||||
- 关键字和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。
|
||||
- 可供选择的语法产式由竖线(`|`)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。
|
||||
- 少数情况下,语法产式规则的右侧会有用于描述的常规字体文字。
|
||||
- 可选的句法类型和字面值用尾标 `opt` 来标记。
|
||||
|
||||
举个例子,getter-setter 的语法块的定义如下:
|
||||
|
||||
> getter-setter 方法块语法
|
||||
> *getter-setter 方法块* → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> } | { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
|
||||
|
||||
这个定义表明,一个 getter-setter 方法块可以由一个 getter 子句后跟一个可选的 setter 子句构成,然后用大括号括起来,或者由一个 setter 子句后跟一个 getter 子句构成,然后用大括号括起来。下面的两个语法产式等价于上述的语法产式,并明确指出了如何取舍:
|
||||
|
||||
> getter-setter 方法块语法
|
||||
> getter-setter 方法块 → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> }
|
||||
> getter-setter 方法块 → { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
|
||||
|
||||
@ -5,265 +5,343 @@
|
||||
> 翻译:[superkam](https://github.com/superkam)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb)
|
||||
|
||||
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[buginux](https://github.com/buginux)
|
||||
|
||||
|
||||
|
||||
> 2.1
|
||||
> 翻译:[mmoaay](https://github.com/mmoaay)
|
||||
|
||||
|
||||
|
||||
> 2.2
|
||||
> 翻译+校对:[星夜暮晨](https://github.com/semperidem),2016-04-06
|
||||
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [空白与注释(*Whitespace and Comments*)](#whitespace_and_comments)
|
||||
- [标识符(*Identifiers*)](#identifiers)
|
||||
- [关键字(*Keywords*)](#keywords)
|
||||
- [字面量(*Literals*)](#literals)
|
||||
- [运算符(*Operators*)](#operators)
|
||||
- [空白与注释](#whitespace_and_comments)
|
||||
- [标识符](#identifiers)
|
||||
- [关键字和标点符号](#keywords)
|
||||
- [字面量](#literals)
|
||||
- [整数字面量](#integer_literals)
|
||||
- [浮点数字面量](#floating_point_literals)
|
||||
- [字符串字面量](#string_literals)
|
||||
- [运算符](#operators)
|
||||
|
||||
Swift 的“词法结构(*lexical structure*)”描述了能构成该语言中合法标记(*tokens*)的字符序列。这些合法标记组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法标记由一个标识符、关键字、标点符号、文字或运算符组成。
|
||||
Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中合法符号 (token) 的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符 (identifier)、关键字 (keyword)、标点符号 (punctuation)、字面量 (literal) 或运算符 (operator) 组成。
|
||||
|
||||
通常情况下,标记是在随后将介绍的语法约束下,由 Swift 源文件的输入文本中提取可能的最长子串生成。这种方法称为“最长匹配项(*longest match*)”,或者“最大适合”(*maximal munch*)。
|
||||
通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配 (longest match)”*,或者*“最大适合(maximal munch)”*。
|
||||
|
||||
<a id="whitespace_and_comments"></a>
|
||||
## 空白与注释
|
||||
|
||||
空白(*whitespace*)有两个用途:分隔源文件中的标记和帮助区分运算符属于前缀还是后缀(参见 [运算符](#operators)),在其他情况下则会被忽略。以下的字符会被当作空白:空格(*space*)(U+0020)、换行符(*line feed*)(U+000A)、回车符(*carriage return*)(U+000D)、水平制表符(*horizontal tab*)(U+0009)、垂直制表符(*vertical tab*)(U+000B)、换页符(*form feed*)(U+000C)以及空(*null*)(U+0000)。
|
||||
空白 (whitespace) 有两个用途:分隔源文件中的符号以及帮助区分运算符属于前缀还是后缀(参见 [运算符](#operators)),在其他情况下空白则会被忽略。以下的字符会被当作空白:空格(U+0020)、换行符(U+000A)、回车符(U+000D)、水平制表符(U+0009)、垂直制表符(U+000B)、换页符(U+000C)以及空字符(U+0000)。
|
||||
|
||||
注释(*comments*)被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(*line feed*)(U+000A)或者回车符(*carriage return*)(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。
|
||||
注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(U+000A)或者回车符(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。
|
||||
|
||||
就像 [标记格式引用(Markup Formatting Reference)](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所说的那样,注释可以包含附加的格式和标记。
|
||||
正如 [*Markup Formatting Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所述,注释可以包含附加的格式和标记。
|
||||
|
||||
<a id="identifiers"></a>
|
||||
## 标识符
|
||||
|
||||
标识符(*identifiers*)可以由以下的字符开始:大写或小写的字母 `A` 到 `Z`、下划线 `_`、基本多文种平面(*Basic Multilingual Plane*)中的 Unicode 非组合字符以及基本多文种平面以外的非专用区(*Private Use Area*)字符。首字符之后,允许使用数字和 Unicode 字符组合。
|
||||
*标识符(identifier)* 可以由以下的字符开始:大写或小写的字母 `A` 到 `Z`、下划线 (`_`)、基本多文种平面 (Basic Multilingual Plane) 中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。
|
||||
|
||||
使用保留字(*reserved word*)作为标识符,需要在其前后增加反引号 \`。例如,`class` 不是合法的标识符,但可以使用 \`class\`。反引号不属于标识符的一部分,\`x\` 和 `x` 表示同一标识符。
|
||||
使用保留字作为标识符,需要在其前后增加反引号 (`` ` ``)。例如,`class` 不是合法的标识符,但可以使用 `` `class` ``。反引号不属于标识符的一部分,`` `x` `` 和 `x` 表示同一标识符。
|
||||
|
||||
闭包(*closure*)中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2`等等。 这些命名在闭包作用域范围内是合法的标识符。
|
||||
闭包中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2` 等等。这些命名在闭包作用域范围内是合法的标识符。
|
||||
|
||||
> 标识符语法
|
||||
|
||||
> 标识符语法
|
||||
<a id="identifier"></a>
|
||||
> *标识符* → [*头部标识符*](#identifier_head) [*标识符字符组*](#identifier_characters)<sub>可选</sub>
|
||||
> *标识符* → \`[*头部标识符*](#identifier_head) [*标识符字符组*](#identifier_characters)<sub>可选</sub>\`
|
||||
> *标识符* → [*隐式参数名*](#implicit_parameter_name)
|
||||
> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier_list)
|
||||
<a id="identifier_head"></a>
|
||||
> *标识符* → [*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
|
||||
> *标识符* → \`[*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>\`
|
||||
> *标识符* → [*隐式参数名*](#implicit-parameter-name)
|
||||
|
||||
<a id="identifier-list"></a>
|
||||
> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier-list)
|
||||
|
||||
<a id="identifier-head"></a>
|
||||
> *头部标识符* → 大写或小写字母 A - Z
|
||||
> *头部标识符* → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA
|
||||
> *头部标识符* → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF
|
||||
> *头部标识符* → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF
|
||||
> *头部标识符* → _
|
||||
> *头部标识符* → U+00A8,U+00AA,U+00AD,U+00AF,U+00B2–U+00B5,或者 U+00B7–U+00BA
|
||||
> *头部标识符* → U+00BC–U+00BE,U+00C0–U+00D6,U+00D8–U+00F6,或者 U+00F8–U+00FF
|
||||
> *头部标识符* → U+0100–U+02FF,U+0370–U+167F,U+1681–U+180D,或者 U+180F–U+1DBF
|
||||
> *头部标识符* → U+1E00–U+1FFF
|
||||
> *头部标识符* → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F
|
||||
> *头部标识符* → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793
|
||||
> *头部标识符* → U+2C00–U+2DFF or U+2E80–U+2FFF
|
||||
> *头部标识符* → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF
|
||||
> *头部标识符* → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44
|
||||
> *头部标识符* → U+200B–U+200D,U+202A–U+202E,U+203F–U+2040,U+2054,或者 U+2060–U+206F
|
||||
> *头部标识符* → U+2070–U+20CF,U+2100–U+218F,U+2460–U+24FF,或者 U+2776–U+2793
|
||||
> *头部标识符* → U+2C00–U+2DFF 或者 U+2E80–U+2FFF
|
||||
> *头部标识符* → U+3004–U+3007,U+3021–U+302F,U+3031–U+303F,或者 U+3040–U+D7FF
|
||||
> *头部标识符* → U+F900–U+FD3D,U+FD40–U+FDCF,U+FDF0–U+FE1F,或者 U+FE30–U+FE44
|
||||
> *头部标识符* → U+FE47–U+FFFD
|
||||
> *头部标识符* → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD
|
||||
> *头部标识符* → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD
|
||||
> *头部标识符* → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD
|
||||
> *头部标识符* → U+D0000–U+DFFFD or U+E0000–U+EFFFD
|
||||
> *头部标识符* → U+10000–U+1FFFD,U+20000–U+2FFFD,U+30000–U+3FFFD,或者 U+40000–U+4FFFD
|
||||
> *头部标识符* → U+50000–U+5FFFD,U+60000–U+6FFFD,U+70000–U+7FFFD,或者 U+80000–U+8FFFD
|
||||
> *头部标识符* → U+90000–U+9FFFD,U+A0000–U+AFFFD,U+B0000–U+BFFFD,或者 U+C0000–U+CFFFD
|
||||
> *头部标识符* → U+D0000–U+DFFFD 或者 U+E0000–U+EFFFD
|
||||
|
||||
<a id="identifier-character"></a>
|
||||
> *标识符字符* → 数值 0 - 9
|
||||
> *标识符字符* → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F
|
||||
> *标识符字符* → [*头部标识符*](#identifier_head)
|
||||
<a id="identifier_characters"></a>
|
||||
> *标识符字符组* → [*标识符字符*](#identifier_character) [*标识符字符列表*](#identifier_characters)<sub>可选</sub>
|
||||
<a id="implicit_parameter_name"></a>
|
||||
> *隐式参数名* → **$** [*十进制数字列表*](#decimal_digits)
|
||||
> *标识符字符* → U+0300–U+036F,U+1DC0–U+1DFF,U+20D0–U+20FF,或者 U+FE20–U+FE2F
|
||||
> *标识符字符* → [*头部标识符*](#identifier-head)
|
||||
> <a id="identifier-characters"></a>
|
||||
> *标识符字符组* → [*标识符字符*](#identifier-character) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
|
||||
|
||||
<a id="implicit-parameter-name"></a>
|
||||
> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digits)
|
||||
|
||||
<a id="keywords"></a>
|
||||
## 关键字和符号
|
||||
## 关键字和标点符号
|
||||
|
||||
下面这些被保留的关键字(*keywords*)不允许用作标识符,除非被反引号转义,具体描述请参考 [标识符](#identifiers)。
|
||||
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout`、`var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,不用添加反引号转义。
|
||||
|
||||
* **用在声明中的关键字:** *class*、*deinit*、*enum*、*extension*、*func*、*import*、*init*、*let*、*protocol*、*static*、*struct*、*subscript*、*typealias*、*var*
|
||||
* **用在语句中的关键字:** *break*、*case*、*continue*、*default*、*do*、*else*、*fallthrough*、*if*、*in*、*for*、*return*、*switch*、*where*、*while*
|
||||
* **用在表达式和类型中的关键字:** *as*、*dynamicType*、*is*、*new*、*super*、*self*、*Self*、*Type*、*\_\_COLUMN\_\_*、*\_\_FILE\_\_*、*\_\_FUNCTION\_\_*、*\_\_LINE\_\_*
|
||||
* **用在模式中的关键字:** *\_*
|
||||
* **特定上下文中被保留的关键字:** *associativity*、*didSet*、*get*、*infix*、*inout*、*left*、*mutating*、*none*、*nonmutating*、*operator*、*override*、*postfix*、*precedence*、*prefix*、*right*、*set*、*unowned*、*unowned(safe)*、*unowned(unsafe)*、*weak*、*willSet*,这些关键字在特定上下文之外可以被用于标识符。
|
||||
|
||||
以下标记被当作保留符号,不能用于自定义操作符:`(`、`)`、`{`、`}`、`[`、`]`、`.`、`,`、`:`、`;`、`=`、`@`、`#`、`&(作为前缀操作符)`、`->`、`` `、`?` 和 `!(作为后缀操作符)`。
|
||||
* 用在声明中的关键字: `associatedtype`、`class`、`deinit`、`enum`、`extension`、`func`、`import`、`init`、`inout`、`internal`、`let`、`operator`、`private`、`protocol`、`public`、`static`、`struct`、`subscript`、`typealias` 以及 `var`。
|
||||
* 用在语句中的关键字:`break`、`case`、`continue`、`default`、`defer`、`do`、`else`、`fallthrough`、`for`、`guard`、`if`、`in`、`repeat`、`return`、`switch`、`where` 以及 `while`。
|
||||
* 用在表达式和类型中的关键字:`as`、`catch`、`dynamicType`、`false`、`is`、`nil`、`rethrows`、`super`、`self`、`Self`、`throw`、`throws`、`true`、`try`、`#column`、`#file`、`#function` 以及 `#line`。
|
||||
* 用在模式中的关键字:`_`。
|
||||
* 以井字号 (`#`) 开头的关键字:`#available`、`#column`、`#else#elseif`、`#endif`、`#file`、`#function`、`#if`、`#line` 以及 `#selector`。
|
||||
* 特定上下文中被保留的关键字: `associativity`、`convenience`、`dynamic`、`didSet`、`final`、`get`、`infix`、`indirect`、`lazy`、`left`、`mutating`、`none`、`nonmutating`、`optional`、`override`、`postfix`、`precedence`、`prefix`、`Protocol`、`required`、`right`、`set`、`Type`、`unowned`、`weak` 以及 `willSet`。这些关键字在特定上下文之外可以被用做标识符。
|
||||
|
||||
以下符号被当作保留符号,不能用于自定义运算符: `(`、`)`、`{`、`}`、`[`、`]`、`.`、`,`、`:`、`;`、`=`、`@`、`#`、`&`(作为前缀运算符)、`->`、`` ` ``、`?`、`!`(作为后缀运算符)。
|
||||
|
||||
<a id="literals"></a>
|
||||
## 字面量
|
||||
|
||||
字面量是用来表示源码中某种特定类型的值,比如一个数字或字符串。
|
||||
*字面量 (literal)* 用来表示源码中某种特定类型的值,比如一个数字或字符串。
|
||||
|
||||
下面是字面量的一些示例:
|
||||
|
||||
```swift
|
||||
42 // 整型字面量
|
||||
3.14159 // 浮点型字面量
|
||||
"Hello, world!" // 字符串型字面量
|
||||
true // 布尔型字面量
|
||||
42 // 整数字面量
|
||||
3.14159 // 浮点数字面量
|
||||
"Hello, world!" // 字符串字面量
|
||||
true // 布尔值字面量
|
||||
```
|
||||
|
||||
字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 Swift 的类型推导会尝试去推导出这个字面量的类型。比如,在 `let x: Int8 = 42` 这个声明中,Swift 使用了显式类型标注(`: Int8`)来推导出 `42` 这个整型字面量的类型是 `Int8`。如果没有可用的类型信息, Swift 则会从标准库中定义的字面量类型中推导出一个默认的类型。整型字面量的默认类型是 `Int`,浮点型字面量的默认类型是 `Double`,字符串型字面量的默认类型是 `String`,布尔型字面量的默认类型是 `Bool`。比如,在 `let str = "Hello, world"` 这个声明中,字符串 `"Hello, world"`的默认推导类型就是 `String`。
|
||||
字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 Swift 的类型推导会尝试去推导出这个字面量的类型。比如,在 `let x: Int8 = 42` 这个声明中,Swift 使用了显式类型标注(`: Int8`)来推导出 `42` 这个整数字面量的类型是 `Int8`。如果没有可用的类型信息, Swift 则会从标准库中定义的字面量类型中推导出一个默认的类型。整数字面量的默认类型是 `Int`,浮点数字面量的默认类型是 `Double`,字符串字面量的默认类型是 `String`,布尔值字面量的默认类型是 `Bool`。比如,在 `let str = "Hello, world"` 这个声明中,字符串 `"Hello, world"` 的默认推导类型就是 `String`。
|
||||
|
||||
当为一个字面量值指定了类型标注的时候,这个注解的类型必须能通过这个字面量值实例化后得到。也就是说,这个类型必须遵守这些 Swift 标准库协议中的一个:整型字面量的`IntegerLiteralConvertible`协议、符点型字面量的`FloatingPointLiteralConvertible`协议、字符串字面量的`StringLiteralConvertible`协议以及布尔型字面量的`BooleanLiteralConvertible`协议。比如,`Int8` 遵守了 `IntegerLiteralConvertible`协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整型字面量 `42` 的类型标注。
|
||||
当为一个字面量值指定了类型标注的时候,这个标注的类型必须能通过这个字面量值实例化。也就是说,这个类型必须符合这些 Swift 标准库协议中的一个:整数字面量的 `IntegerLiteralConvertible` 协议、浮点数字面量的 `FloatingPointLiteralConvertible` 协议、字符串字面量的 `StringLiteralConvertible` 协议以及布尔值字面量的 `BooleanLiteralConvertible` 协议。比如,`Int8` 符合 `IntegerLiteralConvertible` 协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整数字面量 `42` 的类型标注。
|
||||
|
||||
> 字面量语法
|
||||
> *字面量* → [*数字型字面量*](#numeric_literal) | [*字符串型字面量*](#string_literal) | [*布尔型字面量*](#boolean_literal) | [*nil型字面量*](#nil_literal)
|
||||
<a id="numeric_literal"></a>
|
||||
> *数字型字面量* → -<sub>可选</sub>[*整型字面量*](#integer_literal) | -<sub>可选</sub>[*符点型字面量*](#floating_point_literal)
|
||||
> *布尔型字面量* → **true** | **false**
|
||||
> *nil型字面量* → **nil**
|
||||
> *字面量* → [*数值字面量*](#numeric-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#boolean-literal) | [*nil 字面量*](#nil-literal)
|
||||
|
||||
### 整型字面量
|
||||
<a id="numeric-literal"></a>
|
||||
> *数值字面量* → **-**<sub>可选</sub> [*整数字面量*](#integer-literal) | **-**<sub>可选</sub> [*浮点数字面量*](#floating-point-literal)
|
||||
> <a id="boolean-literal"></a>
|
||||
> *布尔值字面量* → **true** | **false**
|
||||
> <a id="nil-literal"></a>
|
||||
> *nil 字面量* → **nil**
|
||||
|
||||
整型字面量(*integer literals*)表示未指定精度整型数的值。整型字面量默认用十进制表示,可以加前缀来指定其他的进制,二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
|
||||
<a id="integer_literals"></a>
|
||||
### 整数字面量
|
||||
|
||||
十进制字面量包含数字 `0` 至 `9`。二进制字面量只包含 `0` 或 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F` (大小写均可)。
|
||||
*整数字面量 (Integer Literals)* 表示未指定精度整数的值。整数字面量默认用十进制表示,可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
|
||||
|
||||
负整数的字面量在整型字面量前加减号 `-`,比如 `-42`。
|
||||
十进制字面量包含数字 `0` 至 `9`。二进制字面量只包含 `0` 或 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F`(大小写均可)。
|
||||
|
||||
整型字面面可以使用下划线 `_` 来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。
|
||||
负整数的字面量在整数字面量前加负号 `-`,比如 `-42`。
|
||||
|
||||
除非特别指定,整型字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数类型](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID323)。
|
||||
整型字面面可以使用下划线 (`_`) 来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。
|
||||
|
||||
> 整型字面量语法
|
||||
> *整型字面量* → [*二进制字面量*](#binary_literal)
|
||||
> *整型字面量* → [*八进制字面量*](#octal_literal)
|
||||
> *整型字面量* → [*十进制字面量*](#decimal_literal)
|
||||
> *整型字面量* → [*十六进制字面量*](#hexadecimal_literal)
|
||||
<a id="binary_literal"></a>
|
||||
> *二进制字面量* → **0b** [*二进制数字*](#binary_digit) [*二进制字面量字符组*](#binary_literal_characters)<sub>可选</sub>
|
||||
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.html#integers)。
|
||||
|
||||
> 整数字面量语法
|
||||
|
||||
<a id="integer-literal"></a>
|
||||
> *整数字面量* → [*二进制字面量*](#binary-literal)
|
||||
> *整数字面量* → [*八进制字面量*](#octal-literal)
|
||||
> *整数字面量* → [*十进制字面量*](#decimal-literal)
|
||||
> *整数字面量* → [*十六进制字面量*](#hexadecimal-literal)
|
||||
|
||||
<a id="binary-literal"></a>
|
||||
> *二进制字面量* → **0b** [*二进制数字*](#binary-digit) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
|
||||
> <a id="binary-digit"></a>
|
||||
> *二进制数字* → 数值 0 到 1
|
||||
> *二进制字面量字符* → [*二进制数字*](#binary_digit) | _
|
||||
> *二进制字面量字符组* → [*二进制字面量字符*](#binary_literal_character) [*二进制字面量字符组*](#binary_literal_characters)<sub>可选</sub>
|
||||
<a id="octal_literal"></a>
|
||||
> *八进制字面量* → **0o** [*八进字数字*](#octal_digit) [*八进制字符列表*](#octal_literal_characters)<sub>可选</sub>
|
||||
> <a id="binary-literal-character"></a>
|
||||
> *二进制字面量字符* → [*二进制数字*](#binary-digit) | _
|
||||
> <a id="binary-literal-characters"></a>
|
||||
> *二进制字面量字符组* → [*二进制字面量字符*](#binary-literal-character) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
|
||||
|
||||
<a id="octal-literal"></a>
|
||||
> *八进制字面量* → **0o** [*八进字数字*](#octal-digit) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
|
||||
> <a id="octal-digit"></a>
|
||||
> *八进字数字* → 数值 0 到 7
|
||||
> *八进制字符* → [*八进字数字*](#octal_digit) | _
|
||||
> *八进制字符组* → [*八进制字符*](#octal_literal_character) [*八进制字符列表*](#octal_literal_characters)<sub>可选</sub>
|
||||
<a id="decimal_literal"></a>
|
||||
> *十进制字面量* → [*十进制数字*](#decimal_digit) [*十进制字符组*](#decimal_literal_characters)<sub>可选</sub>
|
||||
> <a id="octal-literal-character"></a>
|
||||
> *八进制字符* → [*八进字数字*](#octal-digit) | _
|
||||
> <a id="octal-literal-characters"></a>
|
||||
> *八进制字符组* → [*八进制字符*](#octal-literal-character) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
|
||||
|
||||
<a id="decimal-literal"></a>
|
||||
> *十进制字面量* → [*十进制数字*](#decimal-digit) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
|
||||
> <a id="decimal-digit"></a>
|
||||
> *十进制数字* → 数值 0 到 9
|
||||
> *十进制数字列表* → [*十进制数字*](#decimal_digit) [*十进制数字列表*](#decimal_digits)<sub>可选</sub>
|
||||
> *十进制字符* → [*十进制数字*](#decimal_digit) | _
|
||||
> *十进制字符列表* → [*十进制字符*](#decimal_literal_character) [*十进制字符列表*](#decimal_literal_characters)<sub>可选</sub>
|
||||
<a id="hexadecimal_literal"></a>
|
||||
> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal_digit) [*十六进制字面量字符列表*](#hexadecimal_literal_characters)<sub>可选</sub>
|
||||
> <a id="decimal-digits"></a>
|
||||
> *十进制数字组* → [*十进制数字*](#decimal-digit) [*十进制数字组*](#decimal-digits)<sub>可选</sub>
|
||||
> <a id="decimal-literal-character"></a>
|
||||
> *十进制字符* → [*十进制数字*](#decimal-digit) | _
|
||||
> <a id="decimal-literal-characters"></a>
|
||||
> *十进制字符组* → [*十进制字符*](#decimal-literal-character) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
|
||||
|
||||
<a id="hexadecimal-literal"></a>
|
||||
> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
|
||||
> <a id="hexadecimal-digit"></a>
|
||||
> *十六进制数字* → 数值 0 到 9, 字母 a 到 f, 或 A 到 F
|
||||
> *十六进制字符* → [*十六进制数字*](#hexadecimal_digit) | _
|
||||
> *十六进制字面量字符列表* → [*十六进制字符*](#hexadecimal_literal_character) [*十六进制字面量字符列表*](#hexadecimal_literal_characters)<sub>可选</sub>
|
||||
> <a id="hexadecimal-literal-character"></a>
|
||||
> *十六进制字符* → [*十六进制数字*](#hexadecimal-digit) | _
|
||||
> <a id="hexadecimal-literal-characters"></a>
|
||||
> *十六进制字面量字符组* → [*十六进制字符*](#hexadecimal-literal-character) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
|
||||
|
||||
### 浮点型字面量
|
||||
<a id="floating_point_literals"></a>
|
||||
### 浮点数字面量
|
||||
|
||||
浮点型字面量(*floating-point literals*)表示未指定精度浮点数的值。
|
||||
*浮点数字面量 (Floating-point literals)* 表示未指定精度浮点数的值。
|
||||
|
||||
浮点型字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `0x`)。
|
||||
浮点数字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `0x`)。
|
||||
|
||||
十进制浮点型字面量(*decimal floating-point literals*)由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点 `.` 后跟十进制数字串组成。指数部分由大写或小写字母 `e` 为前缀后跟十进制数字串组成,这串数字表示 `e` 之前的数量乘以 10 的几次方。例如:`1.25e2` 表示 `1.25 ⨉ 10^2`,也就是 `125.0`;同样,`1.25e-2` 表示 `1.25 ⨉ 10^-2`,也就是 `0.0125`。
|
||||
十进制浮点数字面量由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点 (`.`) 后跟十进制数字串组成。指数部分由大写或小写字母 `e` 为前缀后跟十进制数字串组成,这串数字表示 `e` 之前的数量乘以 10 的几次方。例如:`1.25e2` 表示 1.25 x 10²,也就是 `125.0`;同样,`1.25e-2` 表示 1.25 x 10¯²,也就是 `0.0125`。
|
||||
|
||||
十六进制浮点型字面量(*hexadecimal floating-point literals*)由前缀 `0x` 后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母 `p` 为前缀后跟十进制数字串组成,这串数字表示 `p` 之前的数量乘以 2 的几次方。例如:`0xFp2` 表示 `15 ⨉ 2^2`,也就是 `60`;同样,`0xFp-2` 表示 `15 ⨉ 2^-2`,也就是 `3.75`。
|
||||
十六进制浮点数字面量由前缀 `0x` 后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母 `p` 为前缀后跟十进制数字串组成,这串数字表示 `p` 之前的数量乘以 2 的几次方。例如:`0xFp2` 表示 15 x 2²,也就是 `60`;同样,`0xFp-2` 表示 15 x 2¯²,也就是 `3.75`。
|
||||
|
||||
负的浮点型字面量由一元运算符减号 `-` 和浮点型字面量组成,例如 `-42.5`。
|
||||
负数的浮点数字面量由负号 (`-`) 和浮点数字面量组成,例如 `-42.5`。
|
||||
|
||||
浮点型字面量允许使用下划线 `_` 来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。
|
||||
浮点数字面量允许使用下划线 (`_`) 来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。
|
||||
|
||||
除非特别指定,浮点型字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示64位浮点数。Swift 标准库也定义了 `Float` 类型,表示32位浮点数。
|
||||
除非特别指定,浮点数字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示 64 位浮点数。Swift 标准库也定义了 `Float` 类型,表示 32 位浮点数。
|
||||
|
||||
> 浮点型字面量语法
|
||||
> *浮点数字面量* → [*十进制字面量*](#decimal_literal) [*十进制分数*](#decimal_fraction)<sub>可选</sub> [*十进制指数*](#decimal_exponent)<sub>可选</sub>
|
||||
> *浮点数字面量* → [*十六进制字面量*](#hexadecimal_literal) [*十六进制分数*](#hexadecimal_fraction)<sub>可选</sub> [*十六进制指数*](#hexadecimal_exponent)
|
||||
<a id="decimal_fraction"></a>
|
||||
> *十进制分数* → **.** [*十进制字面量*](#decimal_literal)
|
||||
> *十进制指数* → [*浮点数e*](#floating_point_e) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal_literal)
|
||||
<a id="hexadecimal_literal"></a>
|
||||
> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal_digit) [*十六进制字面量字符列表*](#hexadecimal_literal_characters)<sub>可选</sub>
|
||||
> *十六进制指数* → [*浮点数p*](#floating_point_p) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal_literal)
|
||||
<a id="floating_point_e"></a>
|
||||
> *浮点数e* → **e** | **E**
|
||||
> *浮点数p* → **p** | **P**
|
||||
> 浮点数字面量语法
|
||||
|
||||
<a id="floating-point-literal"></a>
|
||||
> *浮点数字面量* → [*十进制字面量*](#decimal-literal) [*十进制分数*](#decimal-fraction)<sub>可选</sub> [*十进制指数*](#decimal-exponent)<sub>可选</sub>
|
||||
> *浮点数字面量* → [*十六进制字面量*](#hexadecimal-literal) [*十六进制分数*](#hexadecimal-fraction)<sub>可选</sub> [*十六进制指数*](#hexadecimal-exponent)
|
||||
|
||||
<a id="decimal-fraction"></a>
|
||||
> *十进制分数* → **.** [*十进制字面量*](#decimal-literal)
|
||||
> <a id="decimal-exponent"></a>
|
||||
> *十进制指数* → [*十进制指数 e*](#floating-point-e) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
|
||||
|
||||
<a id="hexadecimal-fraction"></a>
|
||||
> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
|
||||
> <a id="hexadecimal-exponent"></a>
|
||||
> *十六进制指数* → [*十六进制指数 p*](#floating-point-p) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
|
||||
|
||||
<a id="floating-point-e"></a>
|
||||
> *十进制指数 e* → **e** | **E**
|
||||
> <a id="floating-point-p"></a>
|
||||
> *十六进制指数 p* → **p** | **P**
|
||||
> <a id="sign"></a>
|
||||
> *正负号* → **+** | **-**
|
||||
|
||||
<a id="string_literals"></a>
|
||||
### 字符串字面量
|
||||
|
||||
### 字符串型字面量
|
||||
字符串字面量由被包在双引号中的一串字符组成,形式如下:
|
||||
|
||||
字符串型字面量(*string literal*)由被包在双引号中的一串字符组成,形式如下:
|
||||
> "`字符`"
|
||||
|
||||
```
|
||||
"characters"
|
||||
```
|
||||
|
||||
字符串型字面量中不能包含未转义的双引号 (`"`)、未转义的反斜线(`\`)、回车符(*carriage return*)或换行符(*line feed*)。
|
||||
字符串字面量中不能包含未转义的双引号(`"`)、未转义的反斜线(`\`)、回车符、换行符。
|
||||
|
||||
可以在字符串字面量中使用的转义特殊符号如下:
|
||||
|
||||
* 空字符(Null Character)`\0`
|
||||
* 反斜线(Backslash)`\\`
|
||||
* 水平制表符(Horizontal Tab)`\t`
|
||||
* 换行符(Line Feed)`\n`
|
||||
* 回车符(Carriage Return)`\r`
|
||||
* 双引号(Double Quote)`\"`
|
||||
* 单引号(Single Quote)`\'`
|
||||
* Unicode标量 `\u{n}`,n为一到八位的十六进制数字
|
||||
* 空字符 `\0`
|
||||
* 反斜线 `\\`
|
||||
* 水平制表符 `\t`
|
||||
* 换行符 `\n`
|
||||
* 回车符 `\r`
|
||||
* 双引号 `\"`
|
||||
* 单引号 `\'`
|
||||
* Unicode 标量 `\u{`n`}`,n 为一到八位的十六进制数字
|
||||
|
||||
字符串字面量允许在反斜杠小括号 `\()` 中插入表达式的值。插入表达式(*interpolated expression*)不能包含未转义的双引号 `"`、未转义的反斜线 `\`、回车符或者换行符。表达式结果的类型必须在 *String* 类中有对应的初始化方法。
|
||||
字符串字面量允许在反斜杠 (`\`) 后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的双引号 (`"`)、未转义的反斜线 (`\`)、回车符以及换行符。
|
||||
|
||||
例如,以下所有字符串字面量的值都是相同的:
|
||||
|
||||
```
|
||||
```swift
|
||||
"1 2 3"
|
||||
"1 2 \("3")"
|
||||
"1 2 \(3)"
|
||||
"1 2 \(1 + 2)"
|
||||
let x = 3; "1 2 \(x)"
|
||||
```
|
||||
|
||||
字符串字面量的默认推导类型为 `String`。组成字符串的字符默认推导类型为 `Character`。更多有关 `String` 和 `Character` 的信息请参照 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及[字符串结构参考](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。
|
||||
字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及 [*String Structure Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。
|
||||
|
||||
用 `+` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA` 和 `textB` 时完全一样的—— `textA` 没有任何运行时的连接操作。
|
||||
用 `+` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA` 和 `textB` 是完全一样的,`textA` 没有任何运行时的连接操作。
|
||||
|
||||
```
|
||||
```swift
|
||||
let textA = "Hello " + "world"
|
||||
let textB = "Hello world"
|
||||
```
|
||||
|
||||
> 字符型字面量语法
|
||||
> *字符串字面量* → **"** [*引用文本*](#quoted_text)<sub>可选</sub> **"**
|
||||
<a id="quoted_text"></a>
|
||||
> *引用文本* → [*引用文本条目*](#quoted_text_item) [*引用文本*](#quoted_text) <sub>可选</sub>
|
||||
> *引用文本条目* → [*转义字符*](#escaped_character)
|
||||
> *引用文本条目* → **\(** [*表达式*](./04_Expressions.html) **)**
|
||||
> *引用文本条目* → **除了", \, U+000A, 或者 U+000D的所有Unicode的字符**
|
||||
> *转义字符* → **\0** | **\\** | **\t** | **\n** | **\r** | **\"** | **\'**
|
||||
> *转义字符* → **\u {** [*unicode标量数字*](#unicode_scalar_digits) **}**
|
||||
> *unicode标量数字* → 一到八位的十六进制数字
|
||||
> 字符串字面量语法
|
||||
|
||||
<a id="string-literal"></a>
|
||||
> *字符串字面量* → [*静态字符串字面量*](#static-string-literal) | [*插值字符串字面量*](#interpolated-string-literal)
|
||||
|
||||
<a id="static-string-literal"></a>
|
||||
> *静态字符串字面量* → **"**[*引用文本*](#quoted-text)<sub>可选</sub>**"**
|
||||
> <a id="quoted-text"></a>
|
||||
> *引用文本* → [*引用文本项*](#quoted-text-item) [*引用文本*](#quoted-text)<sub>可选</sub>
|
||||
> <a id="quoted-text-item"></a>
|
||||
> *引用文本项* → [*转义字符*](#escaped-character)
|
||||
> *引用文本项* → 除了 **"**、**\\**、U+000A、U+000D 以外的所有 Unicode 字符
|
||||
|
||||
<a id="interpolated-string-literal"></a>
|
||||
> *插值字符串字面量* → **"**[*插值文本*](#interpolated-text)<sub>可选</sub>**"**
|
||||
> <a id="interpolated-text"></a>
|
||||
> *插值文本* → [*插值文本项*](#interpolated-text-item) [*插值文本*](#interpolated-text)<sub>可选</sub>
|
||||
> <a id="interpolated-text-item"></a>
|
||||
> *插值文本项* → **\\****(**[*表达式*](./04_Expressions.html)**)** | [*引用文本项*](#quoted-text-item)
|
||||
|
||||
<a id="escaped-character"></a>
|
||||
> *转义字符* → **\\****0** | **\\****\\** | **\t** | **\n** | **\r** | **\\"** | **\\'**
|
||||
> *转义字符* → **\u {** [*unicode 标量数字*](#unicode-scalar-digits) **}**
|
||||
> <a id="unicode-scalar-digits"></a>
|
||||
> *unicode 标量数字* → 一到八位的十六进制数字
|
||||
|
||||
<a id="operators"></a>
|
||||
## 运算符
|
||||
|
||||
Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../chapter2/02_Basic_Operators.html) 和 [高级运算符](../chapter2/25_Advanced_Operators.html) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。
|
||||
|
||||
自定义运算符可以由以下其中之一的 ASCII 字符 `/`、`=`、 `-`、`+`、`!`、`*`、`%`、`<`、`>`、`&`、`|`、`^`、`?` 以及 `~`, 或者后面语法中规定的任一个 Unicode 字符开始。在第一个字符之后,允许使用组合型 Unicode 字符。也可以使用两个或者多个的点号来自定义运算符(比如, `....`)。虽然可以自定义包含问号`?`的运算符,但是这个运算符不能只包含单独的一个问号。
|
||||
自定义运算符可以由以下其中之一的 ASCII 字符 `/`、`=`、 `-`、`+`、`!`、`*`、`%`、`<`、`>`、`&`、`|`、`^`、`?` 以及 `~`,或者后面语法中规定的任一个 Unicode 字符(其中包含了*数学运算符*、*零散符号(Miscellaneous Symbols)* 以及印刷符号 (Dingbats) 之类的 Unicode 块)开始。在第一个字符之后,允许使用组合型 Unicode 字符。
|
||||
|
||||
注意:
|
||||
以下这些标记 =, ->, //, /*, */, ., <(前缀运算符), &, and ?, ?(中缀运算符), >(后缀运算符), ! 以及 ? 是被系统保留的。这些标记不能被重载,也不能用于自定义操作符。
|
||||
您也可以以点号 (`.`) 开头来定义自定义运算符。这些运算符可以包含额外的点,例如 `.+.`。如果某个运算符不是以点号开头的,那么它就无法再包含另外的点号了。例如,`+.+` 就会被看作为一个 `+` 运算符后面跟着一个 `.+` 运算符。
|
||||
|
||||
运算符两侧的空白被用来区分该运算符是否为前缀运算符(*prefix operator*)、后缀运算符(*postfix operator*)或二元运算符(*binary operator*)。规则总结如下:
|
||||
虽然您可以用问号 `?` 来自定义运算符,但是这个运算符不能只包含单独的一个问号。此外,虽然运算符可以包含一个惊叹号 `!`,但是前缀运算符不能够以问号或者惊叹号开头。
|
||||
|
||||
* 如果运算符两侧都有空白或两侧都无空白,将被看作二元运算符。例如:`a+b` 和 `a + b` 中的运算符 `+` 被看作二元运算符。
|
||||
* 如果运算符只有左侧空白,将被看作前缀一元运算符。例如 `a ++b` 中的 `++` 被看作前缀一元运算符。
|
||||
* 如果运算符只有右侧空白,将被看作后缀一元运算符。例如 `a++ b` 中的 `++` 被看作后缀一元运算符。
|
||||
* 如果运算符左侧没有空白并紧跟 `.`,将被看作后缀一元运算符。例如 `a++.b` 中的 `++` 被看作后缀一元运算符(即上式被视为 `a++ .b` 而非 `a ++ .b`)。
|
||||
> 注意
|
||||
> 以下这些标记 `=`、`->`、`//`、`/*`、`*/`、`.`、`<`(前缀运算符)、`&`、`?`、`?`(中缀运算符)、`>`(后缀运算符)、`!` 、`?` 是被系统保留的。这些符号不能被重载,也不能用于自定义运算符。
|
||||
|
||||
鉴于这些规则,运算符前的字符 `(`、`[` 和 `{` ;运算符后的字符 `)`、`]` 和 `}` 以及字符 `,`、`;` 和 `:` 都被视为空白。
|
||||
运算符两侧的空白被用来区分该运算符是否为前缀运算符、后缀运算符或二元运算符。规则总结如下:
|
||||
|
||||
以上规则需注意一点,如果预定义运算符 `!` 或 `?` 左侧没有空白,则不管右侧是否有空白都将被看作后缀运算符。如果将 `?` 用作可选链(*optional-chaining*)操作符,左侧必须无空白。如果用于条件运算符 `? :`,必须两侧都有空白。
|
||||
* 如果运算符两侧都有空白或两侧都无空白,将被看作二元运算符。例如:`a+++b` 和 `a +++ b` 当中的 `+++` 运算符会被看作二元运算符。
|
||||
* 如果运算符只有左侧空白,将被看作一元前缀运算符。例如 `a +++b` 中的 `+++` 运算符会被看做是一元前缀运算符。
|
||||
* 如果运算符只有右侧空白,将被看作一元后缀运算符。例如 `a+++ b` 中的 `+++` 运算符会被看作是一元后缀运算符。
|
||||
* 如果运算符左侧没有空白并紧跟 `.`,将被看作一元后缀运算符。例如 `a+++.b` 中的 `+++` 运算符会被视为一元后缀运算符(即上式被视为 `a+++ .b` 而不是 `a +++ .b`)。
|
||||
|
||||
在某些特定的构造中 ,以 `<` 或 `>` 开头的运算符会被分离成两个或多个标记,剩余部分以同样的方式会被再次分离。因此,在 `Dictionary<String, Array<Int>>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中, 闭合字符 `>` 不会被视为单独的标记,因而不会被误解析为 `>>` 运算符的一部分。
|
||||
鉴于这些规则,运算符前的字符 `(`、`[` 和 `{`,运算符后的字符 `)`、`]` 和 `}`,以及字符 `,`、`;` 和 `:` 都被视为空白。
|
||||
|
||||
要学习如何自定义运算符,请参考 [自定义操作符](../chapter2/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](./05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符方法](../chapter2/25_Advanced_Operators.html#operator_functions)。
|
||||
以上规则需注意一点,如果预定义运算符 `!` 或 `?` 左侧没有空白,则不管右侧是否有空白都将被看作后缀运算符。如果将 `?` 用作可选链式调用运算符,左侧必须无空白。如果用于条件运算符 `? :`,必须两侧都有空白。
|
||||
|
||||
> 运算符语法语法
|
||||
> *运算符* → [*头部运算符*](#operator_head) [*运算符字符组*](#operator_characters)<sub>可选</sub>
|
||||
> *运算符* → [*头部点运算符*](#dot_operator_head) [*点运算符字符组*](#dot_operator_characters)<sub>可选</sub>
|
||||
<a id="operator_head"></a>
|
||||
> *头部运算符* → **/** | **=** | **+** | **!** |**\*** | **%** |**<** | **>** |**&** | **|** |**/** | **~** | **?** |
|
||||
在某些特定的设计中 ,以 `<` 或 `>` 开头的运算符会被分离成两个或多个符号,剩余部分可能会以同样的方式被再次分离。因此,在 `Dictionary<String, Array<Int>>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中, 闭合字符 `>` 不会被视为单独的符号,因而不会被错误解析为 `>>` 运算符。
|
||||
|
||||
要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/25_Advanced_Operators.html#operator_functions)。
|
||||
|
||||
> 运算符语法
|
||||
|
||||
<a id="operator"></a>
|
||||
> *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)<sub>可选</sub>
|
||||
> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
|
||||
|
||||
<a id="operator-head"></a>
|
||||
> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
|
||||
> *头部运算符* → U+00A1–U+00A7
|
||||
> *头部运算符* → U+00A9 or U+00AB
|
||||
> *头部运算符* → U+00AC or U+00AE
|
||||
> *头部运算符* → U+00B0–U+00B1, U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7
|
||||
> *头部运算符* → U+2016–U+2017 or U+2020–U+2027
|
||||
> *头部运算符* → U+00A9 或 U+00AB
|
||||
> *头部运算符* → U+00AC 或 U+00AE
|
||||
> *头部运算符* → U+00B0–U+00B1,U+00B6,U+00BB,U+00BF,U+00D7,或 U+00F7
|
||||
> *头部运算符* → U+2016–U+2017 或 U+2020–U+2027
|
||||
> *头部运算符* → U+2030–U+203E
|
||||
> *头部运算符* → U+2041–U+2053
|
||||
> *头部运算符* → U+2055–U+205E
|
||||
@ -273,21 +351,28 @@ Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基
|
||||
> *头部运算符* → U+2E00–U+2E7F
|
||||
> *头部运算符* → U+3001–U+3003
|
||||
> *头部运算符* → U+3008–U+3030
|
||||
<a id="operator_character"></a>
|
||||
> *运算符字符* → [*头部运算符*](#operator_head)
|
||||
|
||||
<a id="operator-character"></a>
|
||||
> *运算符字符* → [*头部运算符*](#operator-head)
|
||||
> *运算符字符* → U+0300–U+036F
|
||||
> *运算符字符* → U+1DC0–U+1DFF
|
||||
> *运算符字符* → U+20D0–U+20FF
|
||||
> *运算符字符* → U+FE00–U+FE0F
|
||||
> *运算符字符* → U+FE20–U+FE2F
|
||||
> *运算符字符* → U+E0100–U+E01EF
|
||||
<a id="operator_characters"></a>
|
||||
> *运算符字符组* → [*运算符字符*](#operator_character) [*运算符字符组*] (#operator_characters)<sub>可选</sub>
|
||||
<a id="dot_operator_head"></a>
|
||||
> *运算符字符* → U+E0100–U+E01EF
|
||||
> <a id="operator-characters"></a>
|
||||
> *运算符字符组* → [*运算符字符*](#operator-character) [*运算符字符组*](#operator-characters)<sub>可选</sub>
|
||||
|
||||
<a id="dot-operator-head"></a>
|
||||
> *头部点运算符* → **..**
|
||||
> *头部点运算符字符* → . | [*运算符字符*](#operator_character)
|
||||
> *头部点运算符字符组* → [*点运算符字符*](#dot_operator_character) [*点运算符字符组*](#dot_operator_characters)<sub>可选</sub>
|
||||
|
||||
> <a id="dot-operator-character"></a>
|
||||
> *点运算符字符* → **.** | [*运算符字符*](#operator-character)
|
||||
> <a id="dot-operator-characters"></a>
|
||||
> *点运算符字符组* → [*点运算符字符*](#dot-operator-character) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
|
||||
|
||||
<a id="binary-operator"></a>
|
||||
> *二元运算符* → [*运算符*](#operator)
|
||||
> *前置运算符* → [*运算符*](#operator)
|
||||
> *后置运算符* → [*运算符*](#operator)
|
||||
> <a id="prefix-operator"></a>
|
||||
> *前缀运算符* → [*运算符*](#operator)
|
||||
> <a id="postfix-operator"></a>
|
||||
> *后缀运算符* → [*运算符*](#operator)
|
||||
|
||||
@ -8,237 +8,266 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[EudeMorgen](https://github.com/EudeMorgen)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[mmoaay](https://github.com/mmoaay)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [类型注解(Type Annotation)](#type_annotation)
|
||||
- [类型标识符(Type Identifier)](#type_identifier)
|
||||
- [元组类型(Tuple Type)](#tuple_type)
|
||||
- [函数类型(Function Type)](#function_type)
|
||||
- [数组类型(Array Type)](#array_type)
|
||||
- [可选类型(Optional Type)](#optional_type)
|
||||
- [隐式解析可选类型(Implicitly Unwrapped Optional Type)](#implicitly_unwrapped_optional_type)
|
||||
- [协议合成类型(Protocol Composition Type)](#protocol_composition_type)
|
||||
- [元类型(Metatype Type)](#metatype_type)
|
||||
- [类型继承子句(Type Inheritance Clause)](#type_inheritance_clause)
|
||||
- [类型推断(Type Inference)](#type_inference)
|
||||
- [类型注解](#type_annotation)
|
||||
- [类型标识符](#type_identifier)
|
||||
- [元组类型](#tuple_type)
|
||||
- [函数类型](#function_type)
|
||||
- [数组类型](#array_type)
|
||||
- [字典类型](#dictionary_type)
|
||||
- [可选类型](#optional_type)
|
||||
- [隐式解析可选类型](#implicitly_unwrapped_optional_type)
|
||||
- [协议合成类型](#protocol_composition_type)
|
||||
- [元类型](#metatype_type)
|
||||
- [类型继承子句](#type_inheritance_clause)
|
||||
- [类型推断](#type_inference)
|
||||
|
||||
Swift 语言存在两种类型:命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义的类MyClass的实例拥有类型MyClass。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
|
||||
Swift 语言存在两种类型:命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义的类 MyClass 的实例拥有类型 MyClass。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
|
||||
|
||||
那些通常被其它语言认为是基本或初级的数据型类型(Data types)——比如表示数字、字符和字符串的类型——实际上就是命名型类型,这些类型在Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照“扩展和扩展声明”章节里讨论的那样,声明一个扩展来增加它们的行为以迎合你程序的需求。
|
||||
那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/21_Extensions.html) 和 [扩展声明](05_Declarations.html#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。
|
||||
|
||||
*复合型类型*是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型(Int, (Int, Int))包含两个元素:第一个是命名型类型Int,第二个是另一个复合型类型(Int, Int).
|
||||
复合型类型是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。
|
||||
|
||||
本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。
|
||||
|
||||
> 类型语法
|
||||
> *类型* → [*数组类型*](#array_type) | [*字典类型*](../chapter3/03_Types.html#dictionary_type) | [*函数类型*](../chapter3/03_Types.html#function_type) | [*类型标识*](../chapter3/03_Types.html#type_identifier) | [*元组类型*](../chapter3/03_Types.html#tuple_type) | [*可选类型*](../chapter3/03_Types.html#optional_type) | [*隐式解析可选类型*](../chapter3/03_Types.html#implicitly_unwrapped_optional_type) | [*协议合成类型*](../chapter3/03_Types.html#protocol_composition_type) | [*元型类型*](../chapter3/03_Types.html#metatype_type)
|
||||
<a name="type"></a>
|
||||
> *类型* → [*数组类型*](#array-type) | [*字典类型*](#dictionary-type) | [*函数类型*](#function-type) | [*类型标识*](#type-identifier) | [*元组类型*](#tuple-type) | [*可选类型*](#optional-type) | [*隐式解析可选类型*](#implicitly-unwrapped-optional-type) | [*协议合成类型*](#protocol-composition-type) | [*元型类型*](#metatype-type)
|
||||
|
||||
<a name="type_annotation"></a>
|
||||
##类型注解
|
||||
## 类型注解
|
||||
|
||||
类型注解显式地指定一个变量或表达式的值。类型注解始于冒号`:`终于类型,比如下面两个例子:
|
||||
类型注解显式地指定一个变量或表达式的值。类型注解始于冒号 `:` 终于类型,比如下面两个例子:
|
||||
|
||||
```swift
|
||||
let someTuple: (Double, Double) = (3.14159, 2.71828)
|
||||
func someFunction(a: Int){ /* ... */ }
|
||||
func someFunction(a: Int) { /* ... */ }
|
||||
```
|
||||
在第一个例子中,表达式`someTuple`的类型被指定为`(Double, Double)`。在第二个例子中,函数`someFunction`的参数`a`的类型被指定为`Int`。
|
||||
在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的参数 `a` 的类型被指定为 `Int`。
|
||||
|
||||
类型注解可以在类型之前包含一个类型特性(type attributes)的可选列表。
|
||||
类型注解可以在类型之前包含一个类型特性的可选列表。
|
||||
|
||||
> 类型注解语法
|
||||
> *类型注解* → **:** [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type)
|
||||
<a name="type-annotation"></a>
|
||||
> *类型注解* → **:** [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> [*类型*](#type)
|
||||
|
||||
<a name="type_identifier"></a>
|
||||
##类型标识符
|
||||
## 类型标识符
|
||||
|
||||
类型标识符引用命名型类型或者是命名型/复合型类型的别名。
|
||||
类型标识符引用命名型类型,还可引用命名型或复合型类型的别名。
|
||||
|
||||
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符`Int`引用命名型类型`Int`,同样,类型标识符`Dictionary<String, Int>`引用命名型类型`Dictionary<String, Int>`。
|
||||
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary<String, Int>` 引用命名型类型 `Dictionary<String, Int>`。
|
||||
|
||||
在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型/复合型类型的类型别名。比如,在下面的例子中,类型标识符使用`Point`来引用元组`(Int, Int)`:
|
||||
在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`:
|
||||
|
||||
```swift
|
||||
typealias Point = (Int, Int)
|
||||
let origin: Point = (0, 0)
|
||||
```
|
||||
|
||||
情况二,类型标识符使用dot(`.`)语法来表示在其它模块(modules)或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在`ExampleModule`模块中声明的命名型类型`MyType`:
|
||||
情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `ExampleModule` 模块中声明的命名型类型 `MyType`:
|
||||
|
||||
```swift
|
||||
var someValue: ExampleModule.MyType
|
||||
```
|
||||
|
||||
> 类型标识语法
|
||||
> *类型标识* → [*类型名称*](../chapter3/03_Types.html#type_name) [*泛型参数子句*](GenericParametersAndArguments.html#generic_argument_clause) _可选_ | [*类型名称*](../chapter3/03_Types.html#type_name) [*泛型参数子句*](GenericParametersAndArguments.html#generic_argument_clause) _可选_ **.** [*类型标识*](../chapter3/03_Types.html#type_identifier)
|
||||
> *类名* → [*标识符*](LexicalStructure.html#identifier)
|
||||
> 类型标识符语法
|
||||
<a name="type-identifier"></a>
|
||||
> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> | [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> **.** [*类型标识符*](#type-identifier)
|
||||
<a name="type-name"></a>
|
||||
> *类型名称* → [*标识符*](02_Lexical_Structure.html#identifier)
|
||||
|
||||
<a name="tuple_type"></a>
|
||||
##元组类型
|
||||
## 元组类型
|
||||
|
||||
元组类型使用逗号隔开并使用括号括起来的0个或多个类型组成的列表。
|
||||
元组类型是使用括号括起来的零个或多个类型,类型间用逗号隔开。
|
||||
|
||||
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号`(:)`组成。“函数和多返回值”章节里有一个展示上述特性的例子。
|
||||
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.html#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。
|
||||
|
||||
`void`是空元组类型`()`的别名。如果括号内只有一个元素,那么该类型就是括号内元素的类型。比如,`(Int)`的类型是`Int`而不是`(Int)`。所以,只有当元组类型包含的元素个数在两个及以上时才可以命名元组元素。
|
||||
`Void` 是空元组类型 `()` 的别名。如果括号内只有一个元素,那么该类型就是括号内元素的类型。比如,`(Int)` 的类型是 `Int` 而不是 `(Int)`。所以,只有当元组类型包含的元素个数在两个及以上时才可以命名元组元素。
|
||||
|
||||
> 元组类型语法
|
||||
> *元组类型* → **(** [*元组类型主体*](../chapter3/03_Types.html#tuple_type_body) _可选_ **)**
|
||||
> *元组类型主体* → [*元组类型的元素列表*](../chapter3/03_Types.html#tuple_type_element_list) **...** _可选_
|
||||
> *元组类型的元素列表* → [*元组类型的元素*](../chapter3/03_Types.html#tuple_type_element) | [*元组类型的元素*](../chapter3/03_Types.html#tuple_type_element) **,** [*元组类型的元素列表*](../chapter3/03_Types.html#tuple_type_element_list)
|
||||
> *元组类型的元素* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ **inout** _可选_ [*类型*](../chapter3/03_Types.html#type) | **inout** _可选_ [*元素名*](../chapter3/03_Types.html#element_name) [*类型注解*](../chapter3/03_Types.html#type_annotation)
|
||||
> *元素名* → [*标识符*](LexicalStructure.html#identifier)
|
||||
<a name="tuple-type"></a>
|
||||
> *元组类型* → **(** [*元组类型主体*](#tuple-type-body)<sub>可选</sub> **)**
|
||||
<a name="tuple-type-body"></a>
|
||||
> *元组类型主体* → [*元组类型元素列表*](#tuple-type-element-list) **...**<sub>可选</sub>
|
||||
<a name="tuple-type-element-list"></a>
|
||||
> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list)
|
||||
<a name="tuple-type-element"></a>
|
||||
> *元组类型元素* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> **inout**<sub>可选</sub> [*类型*](#type) | **inout**<sub>可选</sub> [*元素名*](#element-name) [*类型注解*](#type-annotation)
|
||||
<a name="element-name"></a>
|
||||
> *元素名* → [*标识符*](02_Lexical_Structure.html#identifier)
|
||||
|
||||
<a name="function_type"></a>
|
||||
##函数类型
|
||||
## 函数类型
|
||||
|
||||
函数类型表示一个函数、方法或闭包的类型,它由一个参数类型和返回值类型组成,中间用箭头`->`隔开:
|
||||
函数类型表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开:
|
||||
|
||||
`parameter type` -> `return type`
|
||||
> `参数类型` -> `返回值类型`
|
||||
|
||||
由于 *参数类型* 和 *返回值类型* 可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。。
|
||||
由于参数类型和返回值类型可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。
|
||||
|
||||
对于参数类型是空元组类型`()`以及返回值类型为表达式类型的函数类型,你可以对其参数声明使用`autoclosure`(见声明属性章节)。一个自动闭包函数捕获特定表达式上的隐式闭包而非表达式本身。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到在函数体中有使用它的值。以自动闭包函数类型做为参数的例子详见 [自动闭包(Autoclosures)](TODO:添加链接) 。
|
||||
你可以对函数参数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被使用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.html#autoclosures) 。
|
||||
|
||||
函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点`(...)`组成,如`Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即`Int...`就是`[Int]`。关于使用可变长参数的例子,见章节[Variadic Parameters](TODO:添加链接)。
|
||||
函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.html#variadic_parameters)。
|
||||
|
||||
为了指定一个`in-out`参数,可以在参数类型前加`inout`前缀。但是你不可以对可变长参数或返回值类型使用`inout`。关于In-Out参数的讨论见章节[In-Out参数部分](TODO:添加链接)。
|
||||
为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。
|
||||
|
||||
柯里化函数(Curried fuction)的函数类型从右向左递归地组成一组。例如,函数类型`Int -> Int -> Int`可以被理解为`Int -> (Int -> Int)`——也就是说,一个函数的参数为`Int`类型,其返回类型是一个参数类型为`Int`返回类型为`Int`的函数类型。关于柯里化函数的讨论见章节[Curried Fuctions](TODO:添加链接)。
|
||||
柯里化函数的函数类型从右向左进行组合。例如,函数类型 `Int -> Int -> Int` 可以理解为 `Int -> (Int -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数类型。关于柯里化函数的讨论见章节 [柯里化函数](05_Declarations.html#curried_functions)。
|
||||
|
||||
函数类型若要抛出错误就必须使用`throws`关键字来标记,若要重抛错误则必须使用`rethrows`关键字来标记。`throws`关键字是函数类型的一部分,不抛出函数(nonthrowing function)是抛出函数(throwing function)函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。对于柯里化函数,`throws`关键字只应用于最里层的函数。抛出和重抛函数(rethrowing function)的相关描述见章节[抛出函数与方法](TODO:添加链接)和[重抛函数与方法](TODO:添加链接)。
|
||||
函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。对于柯里化函数,`throws` 关键字只应用于最里层的函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](05_Declarations.html#throwing_functions_and_methods) 和 [重抛函数与方法](05_Declarations.html#rethrowing_functions_and_methods)。
|
||||
|
||||
> 函数类型语法
|
||||
> *函数类型* → [*类型*](../chapter3/03_Types.html#type) _抛出_ _可选_ **->** [*类型*](../chapter3/03_Types.html#type)
|
||||
> *函数类型* → [*类型*](../chapter3/03_Types.html#type)_重抛_ **->** [*类型*](../chapter3/03_Types.html#type)
|
||||
<a name="function-type"></a>
|
||||
> *函数类型* → [*类型*](#type) **throws**<sub>可选</sub> **->** [*类型*](#type)
|
||||
> *函数类型* → [*类型*](#type) **rethrows**<sub>可选</sub> **->** [*类型*](#type)
|
||||
|
||||
<a name="array_type"></a>
|
||||
##数组类型
|
||||
## 数组类型
|
||||
|
||||
Swift 语言为标准库中定义的 `Array<Element>` 类型提供了如下语法糖:
|
||||
|
||||
> [`类型`]
|
||||
|
||||
Swift语言中使用[`type`]来简化标准库中定义`Array<T>`类型的操作。
|
||||
换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
let someArray: [String] = ["Alex", "Brian", "Dave"]
|
||||
let someArray: Array<String> = ["Alex", "Brian", "Dave"]
|
||||
let someArray: [String] = ["Alex", "Brian", "Dave"]
|
||||
```
|
||||
上面两种情况下,常量`someArray`都被声明为字符串数组。数组的元素也可以通过`[]`获取访问:`someArray[0]`是指第0个元素`“Alex”`。
|
||||
|
||||
你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组。
|
||||
上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`。
|
||||
|
||||
你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组:
|
||||
|
||||
```swift
|
||||
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||
```
|
||||
访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]`是指`[[1, 2], [3, 4]]`,`array3D[0][1]`是指`[3, 4]`,`array3D[0][1][1]`则是指值`4`。
|
||||
|
||||
关于Swift标准库中`Array`类型的细节讨论,见章节[Arrays](TODO:添加链接)。
|
||||
访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]` 是 `[[1, 2], [3, 4]]`,`array3D[0][1]` 是 `[3, 4]`,`array3D[0][1][1]` 则是 `4`。
|
||||
|
||||
关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.html#arrays)。
|
||||
|
||||
> 数组类型语法
|
||||
> *数组类型* → [*类型*](../chapter3/03_Types.html#type)
|
||||
<a name="array-type"></a>
|
||||
> *数组类型* → **[** [*类型*](#type) **]**
|
||||
|
||||
<a name="dictionary_type"></a>
|
||||
##字典类型
|
||||
## 字典类型
|
||||
|
||||
Swift 语言为标准库中定义的 `Dictionary<Key, Value>` 类型提供了如下语法糖:
|
||||
|
||||
> [`键类型` : `值类型`]
|
||||
|
||||
Swift语言中使用[`key type: value type`]来简化标准库中定义`Dictionary<Key,Value>`类型的操作。
|
||||
换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
|
||||
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
|
||||
```
|
||||
上面两种情况,常量`someDictionary`被声明为一个字典,其中键为String类型,值为Int类型。
|
||||
|
||||
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]`返回键`Alex`对应的值。如果键在字典中不存在的话,则这个下标返回`nil`。
|
||||
上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。
|
||||
|
||||
字典中键的类型必须遵循Swift标准库中的可哈希协议。
|
||||
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。如果键在字典中不存在的话,则这个下标返回 `nil`。
|
||||
|
||||
关于Swift标准库中`Dictionary`类型的更多细节可查看章节[Dictionaries](TODO:添加链接)。
|
||||
字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。
|
||||
|
||||
关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.html#dictionaries)。
|
||||
|
||||
> 字典类型语法
|
||||
> *字典类型* → **[**[*类型*](../chapter3/03_Types.html#type) **:** [*类型*](../chapter3/03_Types.html#type) **]**
|
||||
<a name="dictionary-type"></a>
|
||||
> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]**
|
||||
|
||||
<a name="optional_type"></a>
|
||||
##可选类型
|
||||
## 可选类型
|
||||
|
||||
Swift定义后缀`?`来作为标准库中的定义的命名型类型`Optional<T>`的简写。换句话说,下面两个声明是等价的:
|
||||
Swift 定义后缀 `?` 来作为标准库中的定义的命名型类型 `Optional<Wrapped>` 的语法糖。换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
var optionalInteger: Int?
|
||||
var optionalInteger: Optional<Int>
|
||||
```
|
||||
在上述两种情况下,变量`optionalInteger`都被声明为可选整型类型。注意在类型和`?`之间没有空格。
|
||||
|
||||
类型`Optional<T>`是一个枚举,有两种形式,`None`和`Some(T)`,又来代表可能出现或可能不出现的值。任意类型都可以被显式的声明(或隐式的转换)为可选类型。当声明一个可选类型时,确保使用括号给`?`提供合适的作用范围。比如说,声明一个整型的可选数组,应写作`(Int[])?`,写成`Int[]?`的话则会出错。
|
||||
在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。
|
||||
|
||||
如果你在声明或定义可选变量或特性的时候没有提供初始值,它的值则会自动赋成缺省值`nil`。
|
||||
类型 `Optional<Wrapped>` 是一个枚举,有两个成员,`None` 和 `Some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明或定义可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`。
|
||||
|
||||
如果一个可选类型的实例包含一个值,那么你就可以使用后缀操作符`!`来获取该值,正如下面描述的:
|
||||
如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的:
|
||||
|
||||
```swift
|
||||
optionalInteger = 42
|
||||
optionalInteger! // 42
|
||||
optionalInteger! // 42
|
||||
```
|
||||
使用`!`操作符获取值为`nil`的可选项会导致运行错误(runtime error)。
|
||||
|
||||
你也可以使用可选链和可选绑定来选择性的执行可选表达式上的操作。如果值为`nil`,不会执行任何操作因此也就没有运行错误产生。
|
||||
使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。
|
||||
|
||||
更多细节以及更多如何使用可选类型的例子,见章节[Optionals](TODO:添加链接)。
|
||||
你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。
|
||||
|
||||
更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.html#optionals)。
|
||||
|
||||
> 可选类型语法
|
||||
> *可选类型* → [*类型*](../chapter3/03_Types.html#type) **?**
|
||||
<a name="optional-type"></a>
|
||||
> *可选类型* → [*类型*](#type) **?**
|
||||
|
||||
<a name="implicitly_unwrapped_optional_type"></a>
|
||||
##隐式解析可选类型
|
||||
## 隐式解析可选类型
|
||||
|
||||
Swift语言定义后缀`!`作为标准库中命名类型`ImplicitlyUnwrappedOptional<T>`的简写。换句话说,下面两个声明等价:
|
||||
Swift 语言定义后缀 `!` 作为标准库中命名类型 `ImplicitlyUnwrappedOptional<Wrapped>` 的语法糖。换句话说,下面两个声明等价:
|
||||
|
||||
```swift
|
||||
var implicitlyUnwrappedString: String!
|
||||
var implicitlyUnwrappedString: ImplicitlyUnwrappedOptional<String>
|
||||
```
|
||||
上述两种情况下,变量`implicitlyUnwrappedString`被声明为一个隐式解析可选类型的字符串。注意类型与`!`之间没有空格。
|
||||
|
||||
你可以在使用可选类型的地方同样使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选特性,反之亦然。
|
||||
上述两种情况下,变量 `implicitlyUnwrappedString` 被声明为一个隐式解析可选类型的字符串。注意类型与 `!` 之间没有空格。
|
||||
|
||||
有了可选,你在声明隐式解析可选变量或特性的时候就不用指定初始值,因为它有缺省值`nil`。
|
||||
你可以在使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。
|
||||
|
||||
由于隐式解析可选的值会在使用时自动解析,所以没必要使用操作符`!`来解析它。也就是说,如果你使用值为`nil`的隐式解析可选,就会导致运行错误。
|
||||
正如可选类型一样,你在声明隐式解析可选类型的变量或属性的时候也不用指定初始值,因为它有默认值 `nil`。
|
||||
|
||||
使用可选链会选择性的执行隐式解析可选表达式上的某一个操作。如果值为`nil`,就不会执行任何操作,因此也不会产生运行错误。
|
||||
由于隐式解析可选类型的值会在使用时自动解析,所以没必要使用操作符 `!` 来解析它。也就是说,如果你使用值为 `nil` 的隐式解析可选类型,就会导致运行错误。
|
||||
|
||||
关于隐式解析可选的更多细节,见章节[Implicitly Unwrapped Optionals](TODO:添加链接)。
|
||||
可以使用可选链式调用来在隐式解析可选表达式上选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。
|
||||
|
||||
> 隐式解析可选类型(Implicitly Unwrapped Optional Type)语法
|
||||
> *隐式解析可选类型* → [*类型*](../chapter3/03_Types.html#type) **!**
|
||||
关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.html#implicityly_unwrapped_optionals)。
|
||||
|
||||
> 隐式解析可选类型语法
|
||||
<a name="implicitly-unwrapped-optional-type"></a>
|
||||
> *隐式解析可选类型* → [*类型*](#type) **!**
|
||||
|
||||
<a name="protocol_composition_type"></a>
|
||||
##协议合成类型
|
||||
## 协议合成类型
|
||||
|
||||
协议合成类型是一种遵循具体协议列表中每个协议的类型。协议合成类型可能会用在类型注解和泛型参数中。
|
||||
协议合成类型是一种符合协议列表中每个指定协议的类型。协议合成类型可能会用在类型注解和泛型参数中。
|
||||
|
||||
协议合成类型的形式如下:
|
||||
|
||||
```swift
|
||||
protocol<Protocol 1, Procotol 2>
|
||||
```
|
||||
> protocol<`Protocol 1`, `Procotol 2`>
|
||||
|
||||
协议合成类型允许你指定一个值,其类型遵循多个协议的条件且不需要定义一个新的命名型协议来继承其它想要遵循的各个协议。比如,协议合成类型`protocol<Protocol A, Protocol B, Protocol C>`等效于一个从`Protocol A`,`Protocol B`, `Protocol C`继承而来的新协议`Protocol D`,很显然这样做有效率的多,甚至不需引入一个新名字。
|
||||
协议合成类型允许你指定一个值,其类型符合多个协议的要求且不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `protocol<Protocol A, Protocol B, Protocol C>` 等效于一个从 `Protocol A`,`Protocol B`, `Protocol C` 继承而来的新协议 `Protocol D`,很显然这样做有效率的多,甚至不需引入一个新名字。
|
||||
|
||||
协议合成列表中的每项必须是协议名或协议合成类型的类型别名。如果列表为空,它就会指定一个空协议合成列表,这样每个类型都能遵循。
|
||||
协议合成列表中的每项必须是协议名或协议合成类型的类型别名。如果列表为空,它就会指定一个空协议合成列表,每个类型都符合它。
|
||||
|
||||
> 协议合成类型语法
|
||||
> *协议合成类型* → **protocol** **<** [*协议标识符列表*](../chapter3/03_Types.html#protocol_identifier_list) _可选_ **>**
|
||||
> *协议标识符列表* → [*协议标识符*](../chapter3/03_Types.html#protocol_identifier) | [*协议标识符*](../chapter3/03_Types.html#protocol_identifier) **,** [*协议标识符列表*](../chapter3/03_Types.html#protocol_identifier_list)
|
||||
> *协议标识符* → [*类型标识*](../chapter3/03_Types.html#type_identifier)
|
||||
<a name="protocol-composition-type"></a>
|
||||
> *协议合成类型* → **protocol** **<** [*协议标识符列表*](#protocol-identifier-list)<sub>可选</sub> **>**
|
||||
<a name="protocol-identifier-list"></a>
|
||||
> *协议标识符列表* → [*协议标识符*](#protocol-identifier) | [*协议标识符*](#protocol-identifier) **,** [*协议标识符列表*](#protocol-identifier-list)
|
||||
<a name="protocol-identifier"></a>
|
||||
> *协议标识符* → [*类型标识符*](#type-identifier)
|
||||
|
||||
<a name="metatype_type"></a>
|
||||
##元类型
|
||||
## 元类型
|
||||
|
||||
元类型是指所有类型的类型,包括类、结构体、枚举和协议。
|
||||
元类型是指类型的类型,包括类类型、结构体类型、枚举类型和协议类型。
|
||||
|
||||
类、结构体或枚举类型的元类型是相应的类型名紧跟`.Type`。协议类型的元类型——并不是运行时遵循该协议的具体类型——是该协议名字紧跟`.Protocol`。比如,类`SomeClass`的元类型就是`SomeClass.Type`,协议`SomeProtocol`的元类型就是`SomeProtocal.Protocol`。
|
||||
类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时符合该协议的具体类型——而是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`。
|
||||
|
||||
你可以使用后缀`self`表达式来获取类型。比如,`SomeClass.self`返回`SomeClass`本身,而不是`SomeClass`的一个实例。同样,`SomeProtocol.self`返回`SomeProtocol`本身,而不是运行时遵循`SomeProtocol`的某个类型的实例。还可以对类型的实例使用`dynamicType`表达式来获取该实例在运行阶段的类型,如下所示:
|
||||
你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时符合 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `dynamicType` 表达式来获取该实例在运行阶段的类型,如下所示:
|
||||
|
||||
```swift
|
||||
class SomeBaseClass {
|
||||
@ -252,26 +281,26 @@ class SomeSubClass: SomeBaseClass {
|
||||
}
|
||||
}
|
||||
let someInstance: SomeBaseClass = SomeSubClass()
|
||||
// someInstance is of type SomeBaseClass at compile time, but
|
||||
// someInstance is of type SomeSubClass at runtime
|
||||
// someInstance 在编译期是 SomeBaseClass 类型,
|
||||
// 但是在运行期则是 SomeSubClass 类型
|
||||
someInstance.dynamicType.printClassName()
|
||||
// prints "SomeSubClass
|
||||
// 打印 “SomeSubClass”
|
||||
```
|
||||
|
||||
可以使用恒等运算符(`===`和 `!==`)来测试一个实例的运行时类型和它的编译时类型是否一致。
|
||||
可以使用恒等运算符(`===` 和 `!==`)来测试一个实例的运行时类型和它的编译时类型是否一致。
|
||||
|
||||
```
|
||||
if someInstance.dynamicType === someInstance.self {
|
||||
```swift
|
||||
if someInstance.dynamicType === SomeBaseClass.self {
|
||||
print("The dynamic type of someInstance is SomeBaseCass")
|
||||
} else {
|
||||
print("The dynamic type of someInstance isn't SomeBaseClass")
|
||||
}
|
||||
// prints "The dynamic type of someInstance isn't SomeBaseClass"
|
||||
// 打印 “The dynamic type of someInstance isn't SomeBaseClass”
|
||||
```
|
||||
|
||||
可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,必须使用 `required` 关键字标记被调用的构造器,或者使用 `final` 关键字标记整个类。
|
||||
可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。
|
||||
|
||||
```
|
||||
```swift
|
||||
class AnotherSubClass: SomeBaseClass {
|
||||
let string: String
|
||||
required init(string: String) {
|
||||
@ -285,39 +314,43 @@ let metatype: AnotherSubClass.Type = AnotherSubClass.self
|
||||
let anotherInstance = metatype.init(string: "some string")
|
||||
```
|
||||
|
||||
> 元(Metatype)类型语法
|
||||
> *元类型* → [*类型*](../chapter3/03_Types.html#type) **.** **Type** | [*类型*](../chapter3/03_Types.html#type) **.** **Protocol**
|
||||
> 元类型语法
|
||||
<a name="metatype-type"></a>
|
||||
> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol**
|
||||
|
||||
<a name="type_inheritance_clause"></a>
|
||||
##类型继承子句
|
||||
## 类型继承子句
|
||||
|
||||
类型继承子句被用来指定一个命名型类型继承的哪个类、遵循的哪些协议。类型继承子句也用来指定一个类需要遵循的协议。类型继承子句开始于冒号`:`,其后是类所需遵循的协议或者类型标识符列表或者两者均有。
|
||||
类型继承子句被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句也用来指定一个类类型专属协议。类型继承子句开始于冒号 `:`,其后是类的超类或者一系列类型标识符。
|
||||
|
||||
类可以继承单个超类,遵循任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要遵循的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,见章节Inheritance。
|
||||
类可以继承单个超类,采纳任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要采纳的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.html)。
|
||||
|
||||
其它命名型类型可能只继承或遵循一个协议列表。协议类型可能继承于其它任意数量的协议。当一个协议类型继承于其它协议时,其它协议的条件集合会被整合在一起,然后其它从当前协议继承的任意类型必须遵循所有这些条件。正如在协议声明中所讨论的那样,可以把类的关键字放到类型继承子句中的首位,这样就可以用一个类的条件来标记一个协议声明。
|
||||
其它命名型类型可能只继承或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。正如在 [协议声明](05_Declarations.html#protocol_declaration) 中所讨论的那样,可以把 `class` 关键字放到协议类型的类型继承子句的首位,这样就可以声明一个类类型专属协议。
|
||||
|
||||
枚举定义中的类型继承子句可以是一个协议列表,或是指定原始值的枚举——一个单独的指定原始值类型的命名型类型。使用类型继承子句来指定原始值类型的枚举定义的例子,见章节Raw Values。
|
||||
枚举定义中的类型继承子句可以是一系列协议,或是枚举的原始值类型的命名型类型。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.html#raw_values)。
|
||||
|
||||
> 类型继承子句语法
|
||||
> *类型继承子句* → **:** [*类需求*](../chapter3/03_Types.html#class_requirement) **,** [*类型继承列表*](../chapter3/03_Types.html#type_inheritance_list)
|
||||
> *类型继承子句* → **:** [*类需求*](../chapter3/03_Types.html#class_requirement)
|
||||
> *类型继承子句* → **:** [*类型继承列表*](../chapter3/03_Types.html#type_inheritance_list)
|
||||
> *类型继承列表* → [*类型标识*](../chapter3/03_Types.html#type_identifier) | [*类型标识*](../chapter3/03_Types.html#type_identifier) **,** [*类型继承列表*](../chapter3/03_Types.html#type_inheritance_list)
|
||||
> *类需求* → **类**
|
||||
<a name="type_inheritance_clause"></a>
|
||||
> *类型继承子句* → **:** [*类要求*](#class-requirement) **,** [*类型继承列表*](#type-inheritance-list)
|
||||
> *类型继承子句* → **:** [*类要求*](#class-requirement)
|
||||
> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list)
|
||||
<a name="type-inheritance-list"></a>
|
||||
> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list)
|
||||
<a name="class-requirement"></a>
|
||||
> *类要求* → **class**
|
||||
|
||||
<a name="type_inference"></a>
|
||||
##类型推断
|
||||
## 类型推断
|
||||
|
||||
Swift广泛的使用类型推断,从而允许你可以忽略代码中很多变量和表达式的类型或部分类型。比如,对于`var x: Int = 0`,你可以完全忽略类型而简写成`var x = 0`——编译器会正确的推断出`x`的类型`Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以忽略类型的一部分。比如,如果你写了`let dict: Dictionary = ["A": 1]`,编译提也能推断出`dict`的类型是`Dictionary<String, Int>`。
|
||||
Swift 广泛使用类型推断,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary<String, Int>`。
|
||||
|
||||
在上面的两个例子中,类型信息从表达式树(expression tree)的叶子节点传向根节点。也就是说,`var x: Int = 0`中`x`的类型首先根据`0`的类型进行推断,然后将该类型信息传递到根节点(变量`x`)。
|
||||
在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0` 中 `x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。
|
||||
|
||||
在Swift中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量`eFloat`上的显式类型注解(`:Float`)导致数字字面量`2.71828`的类型是`Float`而非`Double`。
|
||||
在 Swift 中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量 `eFloat` 上的显式类型注解(`: Float`)将导致数字字面量 `2.71828` 的类型是 `Float` 而非 `Double`。
|
||||
|
||||
```swift
|
||||
let e = 2.71828 // The type of e is inferred to be Double.
|
||||
let eFloat: Float = 2.71828 // The type of eFloat is Float.
|
||||
let e = 2.71828 // e 的类型会被推断为 Double
|
||||
let eFloat: Float = 2.71828 // eFloat 的类型为 Float
|
||||
```
|
||||
|
||||
Swift中的类型推断在单独的表达式或语句水平上进行。这意味着所有用于推断类型的信息必须可以从表达式或其某个子表达式的类型检查中获取。
|
||||
Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -6,218 +6,228 @@
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[KYawn](https://github.com/KYawn),[小铁匠Linus](https://github.com/kevin833752)
|
||||
> 翻译+校对:[KYawn](https://github.com/KYawn)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[小铁匠Linus](https://github.com/kevin833752)
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [声明特性](#declaration_attributes)
|
||||
- [Interface Builder 使用的声明特性](#declaration_attributes_used_by_interface_builder)
|
||||
- [类型特性](#type_attributes)
|
||||
|
||||
特性提供了关于声明和类型的更多信息。在Swift中有两类特性,用于修饰声明的以及用于修饰类型的。
|
||||
特性提供了有关声明和类型的更多信息。在Swift中有两种特性,分别用于修饰声明和类型。
|
||||
|
||||
通过以下方式指定一个特性:符号`@`后面跟特性名,如果包含参数,则把参数带上:
|
||||
您可以通过以下方式指定一个特性:符号`@`后跟特性的名称和特性接收的任何参数:
|
||||
|
||||
> @`attribute name`
|
||||
> @`attribute name`(`attribute arguments`)
|
||||
> @ `特性名`
|
||||
|
||||
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰一个特定的声明的。这些特性的参数写在小括号内,它们的格式由它们所属的特性来定义。
|
||||
> @ `特性名`(`特性参数`)
|
||||
|
||||
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰某个特定的声明的。这些特性的参数写在圆括号内,它们的格式由它们所属的特性来定义。
|
||||
|
||||
<a name="declaration_attributes"></a>
|
||||
## 声明特性
|
||||
|
||||
声明特性只能应用于声明。然而,你也可以将`noreturn`特性应用于函数或方法类型。
|
||||
|
||||
`autoclosure`
|
||||
|
||||
这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以声明返回表达式自身类型的没有参数的方法类型,也可以用于函数参数的声明。含有`autoclosure`特性的声明同时也具有`noescape`的特性,除非传递可选参数`escaping`.关于怎样使用`autoclosure`特性的例子,参见[函数类型](./03_Types.html#function_type).
|
||||
##声明特性
|
||||
声明特性只能应用于声明。
|
||||
|
||||
`available`
|
||||
|
||||
将`available`特性用于声明时,意味着该声明的生命周期会依赖于特定的平台和操作系统版本。
|
||||
将 `available` 特性用于声明时,表示该声明的生命周期与特定的平台和操作系统版本有关。
|
||||
|
||||
`available`特性经常与参数列表一同出现,该参数列表至少有两个参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头:
|
||||
`available` 特性经常与参数列表一同出现,该参数列表至少有两个特性参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头:
|
||||
|
||||
- `iOS`
|
||||
- `iOSApplicationExtension`
|
||||
- `OSX`
|
||||
- `OSXApplicationExtension`
|
||||
- `watchOS`
|
||||
- iOS
|
||||
- iOSApplicationExtension
|
||||
- macOS
|
||||
- macOSApplicationExtension
|
||||
- watchOS
|
||||
- watchOSApplicationExtension
|
||||
- tvOS
|
||||
- tvOSApplicationExtension
|
||||
|
||||
当然,你也可以用一个星号(*)来表示,该声明在上面提到的所有平台上都是有效的。
|
||||
当然,你也可以用一个星号(*)来表示上面提到的所有平台。
|
||||
其余的参数,可以按照任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要事件。
|
||||
|
||||
剩下的参数,可以以任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要的里程碑。
|
||||
- `unavailable`参数表示该声明在指定的平台上是无效的。
|
||||
- `introduced` 参数表示指定平台从哪一版本开始引入该声明。格式如下:
|
||||
|
||||
- `unavailable`参数表示:该声明在特定的平台上是无效的
|
||||
`introduced`=`版本号`
|
||||
|
||||
- `introduced`参数表示:该声明第一次被引入时所在平台的版本。格式如下:
|
||||
<p>`introduced=version number`<p>这里的`version number`由一个正的十进制整数或浮点数构成。
|
||||
*版本号*由一个或多个正整数构成,由句点分隔的。
|
||||
|
||||
- `deprecated`参数表示:该声明第一次被建议弃用时所在平台的版本。格式如下:
|
||||
<p>`deprecated=version number`<p>这里的`version number`由一个正的十进制整数或浮点数构成。
|
||||
- `deprecated`参数表示指定平台从哪一版本开始弃用该声明。格式如下:
|
||||
|
||||
- `obsoleted`参数表示:该声明第一次被弃用时所在平台的版本。当一个声明被弃用时,它就从此平台中被移除,不能再被使用。格式如下:
|
||||
<p>`obsoleted=version number`<p>这里的`version number`由一个正的十进制整数或浮点数构成。
|
||||
`deprecated`=`版本号`
|
||||
|
||||
- `message`参数用来提供文本信息。当使用建议弃用或者被弃用的声明时,编译器会抛出错误或警告信息。格式如下:
|
||||
<p>`message=message`<p>这里的`message`由一个字符串文字构成。
|
||||
可选的*版本号*由一个或多个正整数构成,由句点分隔的。省略版本号表示该声明目前已弃用,当弃用出现时无需给出任何有关信息。如果你省略了版本号,冒号(:)也可省略。
|
||||
|
||||
- `renamed`参数用来提供文本信息,用以表示被重命名的声明的新名字。当使用这个重命名的声明遇到错误时,编译器会显示出该新名字。格式如下:
|
||||
<p>`renamed=new name`<p>这里的`new name`由一个字符串文字构成。
|
||||
- `obsoleted` 参数表示指定平台从哪一版本开始废弃该声明。当一个声明被废弃后,它就从平台中移除,不能再被使用。格式如下:
|
||||
|
||||
你可以将`renamed`参数和`unavailable`参数以及类型别名声明组合使用,以向用户表示:在你的代码中,一个声明已经被重命名。当一个声明的名字在一个框架或者库的不同发布版本间发生变化时,这会相当有用。
|
||||
`obsoleted`=`版本号`
|
||||
|
||||
*版本号*由一个或多个正整数构成,由句点分隔的。
|
||||
|
||||
- `message` 参数用来提供文本信息。当使用被弃用或者被废弃的声明时,编译器会抛出警告或错误信息。格式如下:
|
||||
|
||||
`message`=`信息内容`
|
||||
|
||||
信息内容由一个字符串构成。
|
||||
|
||||
- `renamed` 参数用来提供文本信息,用以表示被重命名的声明的新名字。当使用声明的旧名字时,编译器会报错提示新名字。格式如下:
|
||||
|
||||
`renamed`=`新名字`
|
||||
|
||||
新名字由一个字符串构成。
|
||||
|
||||
你可以将`renamed` 参数和 `unavailable` 参数以及类型别名声明组合使用,以此向用户表示某个声明已经被重命名。当某个声明的名字在一个框架或者库的不同发布版本间发生变化时,这会相当有用。
|
||||
|
||||
```swift
|
||||
// First release
|
||||
// 首发版本
|
||||
protocol MyProtocol {
|
||||
// protocol definition
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// Subsequent release renames MyProtocol
|
||||
protocol MyRenamedProtocol {
|
||||
// protocol definition
|
||||
// 这里是协议定义
|
||||
}
|
||||
|
||||
@available(*, unavailable, renamed="MyRenamedProtocol")
|
||||
```
|
||||
|
||||
```swift
|
||||
// 后续版本重命名了 MyProtocol
|
||||
protocol MyRenamedProtocol {
|
||||
// 这里是协议定义
|
||||
}
|
||||
@available(*, unavailable, renamed:"MyRenamedProtocol")
|
||||
typealias MyProtocol = MyRenamedProtocol
|
||||
```
|
||||
|
||||
你可以在一个单独的声明上使用多个`available`特性,以详细说明该声明在不同平台上的有效性。编译器只有在当前的目标平台和`available`特性中指定的平台匹配时,才会使用`available`特性。
|
||||
你可以在某个声明上使用多个 `available` 特性,以指定该声明在不同平台上的可用性。编译器只有在当前目标平台和 `available` 特性中指定的平台匹配时,才会使用 `available` 特性。
|
||||
|
||||
如果`available`特性除了平台名称参数外,只指定了一个`introduced `参数,那么可以使用以下简写语法代替:
|
||||
如果 `available` 特性除了平台名称参数外,只指定了一个 `introduced` 参数,那么可以使用以下简写语法代替:
|
||||
|
||||
@available(`platform name` `version number`, *)
|
||||
@available(平台名称 版本号,*)
|
||||
|
||||
`available`特性的简写语法可以简明地表达出多个平台的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简明语法形式。
|
||||
`available` 特性的简写语法可以简明地表达出声明在多个平台上的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简写语法形式。
|
||||
|
||||
```swift
|
||||
@available(iOS 8.0, OSX 10.10, *)
|
||||
@available(iOS 10.0, macOS 10.12, *)
|
||||
class MyClass {
|
||||
// class definition
|
||||
// 这里是类定义
|
||||
}
|
||||
```
|
||||
|
||||
`discardableResult`
|
||||
|
||||
该特性用于的函数或方法声明,以抑制编译器中 函数或方法的返回值被调而没有使用其结果的警告。
|
||||
|
||||
`GKInspectable`
|
||||
|
||||
应用此属性,暴露一个自定义GameplayKit组件属性给SpriteKit编辑器UI。
|
||||
|
||||
`objc`
|
||||
|
||||
该特性用于修饰任何可以在Objective-C中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限整型值类型)、类和协议的属性和方法(包括`getter`和`setter`)、构造器、析构器以及下标。`objc`特性告诉编译器这个声明可以在Objective-C代码中使用。
|
||||
该特性用于修饰任何可以在 Objective-C 中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限原始值为整型的枚举)、类和协议中的属性和方法(包括存取方法)、构造器、析构器以及下标运算符。`objc` 特性告诉编译器这个声明可以在 Objective-C 代码中使用。
|
||||
|
||||
标有`objc`特性的类必须继承自Objective-C中定义的类。如果你将`objc`特性应用于一个类或协议,它也会隐式地应用于那个类的成员或协议。对于标记了`objc`特性的类,编译器会隐式地为它的子类添加`objc`特性。标记了`objc`特性的协议不能继承没有标记`objc`的协议。
|
||||
标有 `objc` 特性的类必须继承自 Objective-C 中定义的类。如果你将 `objc` 特性应用于一个类或协议,它也会隐式地应用于类或协议中兼容 Objective-C 的成员。对于标记了 `objc` 特性的类,编译器会隐式地为它的子类添加 `objc` 特性。标记了 `objc` 特性的协议不能继承没有标记 `objc` 的协议。
|
||||
|
||||
如果你将`objc`特性应用于枚举,每一个枚举的`case`都会以枚举名称和`case`名称组合的方式暴露在Objective-C代码中。例如:一个名为`Venus`的`case`在`Planet`枚举中,这个`case`暴露在Objective-C代码中时叫做`PlanetVenus`。
|
||||
如果你将 `objc` 特性应用于枚举,每一个枚举用例都会以枚举名称和用例名称组合的方式暴露在 Objective-C 代码中。例如,在 `Planet` 枚举中有一个名为 `Venus` 的用例,该用例暴露在 Objective-C 代码中时叫做 `PlanetVenus`。
|
||||
|
||||
`objc`特性有一个可选的参数,由标记符组成。当你想把`objc`所修饰的实体以一个不同的名字暴露给Objective-C时,你就可以使用这个特性参数。你可以使用这个参数来命名类,协议,方法,getters,setters,以及构造器。下面的例子把`ExampleClass`中`enabled`属性的getter暴露给Objective-C,名字是`isEnabled`,而不是它原来的属性名。
|
||||
`objc` 特性有一个可选的参数,由标识符构成。当你想把 objc 所修饰的实体以一个不同的名字暴露给 Objective-C 时,你就可以使用这个特性参数。你可以使用这个参数来命名类、枚举类型、枚举用例、协议、方法、存取方法以及构造器。下面的例子把 `ExampleClass` 中的 `enabled` 属性的取值方法暴露给 Objective-C,名字是 `isEnabled`,而不是它原来的属性名。
|
||||
|
||||
```swift
|
||||
@objc
|
||||
class ExampleClass: NSObject {
|
||||
var enabled: Bool {
|
||||
@objc(isEnabled) get {
|
||||
// Return the appropriate value
|
||||
}
|
||||
}
|
||||
var enabled: Bool {
|
||||
@objc(isEnabled) get {
|
||||
// 返回适当的值 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`noescape`
|
||||
|
||||
在函数或者方法声明上使用该特性,它表示参数将不会被存储用作后续的计算,其用来确保不会超出函数调用的生命周期。对于其属性或方法来说,使用`noescape`声明属性的函数类型不需要显式的使用`self.`。
|
||||
|
||||
`nonobjc`
|
||||
|
||||
该特性用于方法、属性、下标、或构造器的声明,这些声明本是可以在Objective-C代码中表示的。使用`nonobjc`特性告诉编译器这个声明不能在Objective-C代码中使用。
|
||||
该特性用于方法、属性、下标、或构造器的声明,这些声明本可以在 Objective-C 代码中使用,而使用 `nonobjc` 特性则告诉编译器这个声明不能在 Objective-C 代码中使用。
|
||||
|
||||
可以使用`nonobjc`特性解决标有`objc`的类中桥接方法的循环问题,该特性还允许标有`objc`的类的构造器和方法进行重载(overload)。
|
||||
可以使用 `nonobjc` 特性解决标有 `objc` 的类中桥接方法的循环问题,该特性还允许对标有 `objc` 的类中的构造器和方法进行重载。
|
||||
|
||||
标有`nonobjc`特性的方法不能重写(override)一个标有`objc`特性的方法。然而,标有`objc`特性的方法可以重写标有`nonobjc`特性的方法。同样,标有`nonobjc`特性的方法不能满足一个需要标有`@objc`特性的方法的协议。
|
||||
|
||||
`noreturn`
|
||||
|
||||
该特性用于修饰函数或方法声明,表明该函数或方法的对应类型,`T`,是`@noreturn T`。你可以用这个特性修饰函数或方法的类型,这样一来,函数或方法就不会返回到它的调用者中去。
|
||||
|
||||
对于一个没有用`noreturn`特性标记的函数或方法,你可以将它重写为用该特性标记的。相反,对于一个已经用`noreturn`特性标记的函数或方法,你则不可以将它重写为没使用该特性标记的。当你在一个comforming类型中实现一个协议方法时,该规则同样适用。
|
||||
标有 `nonobjc` 特性的方法不能重写标有 `objc` 特性的方法。然而,标有 `objc` 特性的方法可以重写标有 `nonobjc` 特性的方法。同样,标有 `nonobjc` 特性的方法不能满足标有 `@objc` 特性的协议中的方法要求。
|
||||
|
||||
`NSApplicationMain`
|
||||
|
||||
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用`NSApplicationMain(_:_:)`函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
|
||||
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `NSApplicationMain`(\_:_:) 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
|
||||
|
||||
如果你不想使用这个特性,可以提供一个`main.swift`文件,并且提供一个`main()`函数去调用`NSApplicationMain(_:_:)`函数。比如,如果你的应用程序使用一个派生于`NSApplication`的自定义子类作为主要类,你可以调用`NSApplicationMain`函数而不是使用该特性。
|
||||
如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码**顶层**调用`NSApplicationMain`(\_:_:) 函数,如下所示:
|
||||
|
||||
```swift
|
||||
import AppKit
|
||||
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
|
||||
```
|
||||
`NSCopying`
|
||||
|
||||
该特性用于修饰一个类的存储型变量属性。该特性将使属性的setter与属性值的一个副本合成,这个值由`copyWithZone(_:)`方法返回,而不是属性本身的值。该属性的类型必需遵循`NSCopying`协议。
|
||||
该特性用于修饰一个类的存储型变量属性。该特性将使属性的设值方法使用传入值的副本进行赋值,这个值由传入值的 `copyWithZone`(\_:) 方法返回。该属性的类型必需符合 `NSCopying` 协议。
|
||||
|
||||
`NSCopying`特性的原理与Objective-C中的`copy`特性相似。
|
||||
`NSCopying` 特性的行为与 Objective-C 中的 `copy` 特性相似。
|
||||
|
||||
`NSManaged`
|
||||
|
||||
该特性用于修饰`NSManagedObject`子类中的实例方法或存储型变量属性,表明属性的存储和实现由Core Data在运行时基于相关实体描述动态提供。对于标记了`NSManaged`特性的属性,Core Data也会在运行时提供存储。
|
||||
该特性用于修饰 `NSManagedObject` 子类中的实例方法或存储型变量属性,表明它们的实现由 `Core Data` 在运行时基于相关实体描述动态提供。对于标记了 `NSManaged` 特性的属性,`Core Data` 也会在运行时为其提供存储。应用这个特性也意味着`objc`特性。
|
||||
|
||||
`testable`
|
||||
|
||||
该特性用于`import`声明可以测试的编译模块,它能访问任何标有`internal`权限标识符的实体,这和将它声明为`public`权限标识符有同样的效果。
|
||||
在导入允许测试的编译模块时,该特性用于修饰 `import` 声明,这样就能访问被导入模块中的任何标有 `internal` 访问级别修饰符的实体,犹如它们被标记了 `public` 访问级别修饰符。测试也可以访问使用`internal`或者`public`访问级别修饰符标记的类和类成员,就像它们是`open`访问修饰符声明的。
|
||||
|
||||
`UIApplicationMain`
|
||||
|
||||
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用`UIApplicationMain(_:_:)`函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
|
||||
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `UIApplicationMain`函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
|
||||
|
||||
如果你不想使用这个特性,可以提供一个`main.swift`文件,并且提供一个`main`函数去调用`UIApplicationMain(_:_:)`函数。比如,如果你的应用程序使用一个派生于`UIApplication`的自定义子类作为主要类,你可以调用`UIApplicationMain`函数而不是使用该特性。
|
||||
如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码顶层调用 `UIApplicationMain`(\_:\_:\_:) 函数。比如,如果你的应用程序使用一个继承于 UIApplication 的自定义子类作为主要类,你可以调用 `UIApplicationMain`(\_:\_:\_:) 函数而不是使用该特性。
|
||||
|
||||
`warn_unused_result`
|
||||
<a name="declaration_attributes_used_by_interface_builder"></a>
|
||||
###Interface Builder 使用的声明特性
|
||||
`Interface Builder` 特性是 `Interface Builder` 用来与 Xcode 同步的声明特性。`Swift` 提供了以下的 `Interface Builder` 特性:`IBAction`,`IBOutlet`,`IBDesignable`,以及`IBInspectable` 。这些特性与 Objective-C 中对应的特性在概念上是相同的。
|
||||
|
||||
该特性应用于方法或函数声明,当方法或函数被调用,但其结果未被使用时,该特性会让编译器会产生警告。
|
||||
`IBOutlet` 和 `IBInspectable` 用于修饰一个类的属性声明,`IBAction` 特性用于修饰一个类的方法声明,`IBDesignable` 用于修饰类的声明。
|
||||
|
||||
你可以使用这个特性提供一个警告信息,这个警告信息是关于不正确地使用未变异的方法,这个方法也有一个对应的变异方法。
|
||||
|
||||
`warn_unused_result`特性会有选择地采用下面两个参数之一。
|
||||
|
||||
- `message`参数用来提供警告信息。在当方法或函数被调用,但其结果未被使用时,会显示警告信息。格式如下:
|
||||
<p>`message=message`<p>这里的`message`由一个字符串文字构成。
|
||||
|
||||
- `mutable_variant`参数用于提供变异方法的名称,如果未变异方法以一个可变的值被调用而且其结果并未被使用时,应该使用此变异方法。格式如下(方法名有字符串构成):<p>`mutable_variant=method name`<p>
|
||||
比如,Swift标准库同时提供了变异方法`sortInPlace()`和未变异方法`sort()`集合,它们的元素生成器符合`Comparable`协议。如果你调用了`sort()`方法,而没有使用它的结果,其实很有可能,你是打算使用变异方法`sortInPlace()`。
|
||||
|
||||
### Interface Builder使用的声明特性
|
||||
|
||||
Interface Builder特性是Interface Builder用来与Xcode同步的声明特性。Swift提供了以下的Interface Builder特性:`IBAction`,`IBDesignable`,`IBInspectable`,以及`IBOutlet`。这些特性与Objective-C中对应的特性在概念上是相同的。
|
||||
|
||||
`IBOutlet`和`IBInspectable`用于修饰一个类的属性声明;`IBAction`特性用于修饰一个类的方法声明;`IBDesignable`用于修饰类的声明。
|
||||
`IBAction` 和 `IBOutlet` 特性都意味着`objc`特性。
|
||||
|
||||
<a name="type_attributes"></a>
|
||||
## 类型特性
|
||||
##类型特性
|
||||
类型特性只能用于修饰类型。
|
||||
|
||||
类型特性只能用于修饰类型。然而,你也可以用`noreturn`特性去修饰函数或方法声明。
|
||||
`autoclosure`
|
||||
|
||||
这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以修饰类型为返回表达式结果类型的无参数函数类型的函数参数。关于如何使用 autoclosure 特性的例子,请参阅 [自动闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html/) 和 [函数类型](http://wiki.jikexueyuan.com/project/swift/chapter3/03_Types.html)。
|
||||
|
||||
`convention`
|
||||
该特性用于修饰函数类型,它指出了函数调用的约定。
|
||||
|
||||
该特性用于函数的类型,它指出函数调用的约定。
|
||||
convention 特性总是与下面的参数之一一起出现。
|
||||
|
||||
`convention`特性总是与下面的参数之一一起出现。
|
||||
- `swift` 参数用于表示一个 Swift 函数引用。这是 Swift 中函数值的标准调用约定。
|
||||
|
||||
- `swift`参数用于表明一个Swift函数引用。这是Swift中标准的函数值调用约定。
|
||||
- `block` 参数用于表示一个 Objective-C 兼容的块引用。函数值会作为一个块对象的引用,块是一种 `id` 兼容的 Objective-C 对象,其中嵌入了调用函数。调用函数使用 C 的调用约定。
|
||||
|
||||
- `block`参数用于表明一个Objective-C兼容的块引用。函数值表示为一个块对象的引用,这是一个`id-`兼容的Objective-C对象,对象中嵌入了调用函数。调用函数使用C的调用约定。
|
||||
- `c` 参数用于表示一个 C 函数引用。函数值没有上下文,不具备捕获功能,同样使用 C 的调用约定。
|
||||
|
||||
- `c`参数用于表明一个C函数引用。函数值没有上下文,这个函数也使用C的调用约定。
|
||||
使用 C 函数调用约定的函数也可用作使用 Objective-C 块调用约定的函数,同时使用 Objective-C 块调用约定的函数也可用作使用 Swift 函数调用约定的函数。然而,只有非泛型的全局函数、局部函数以及未捕获任何局部变量的闭包,才可以被用作使用 C 函数调用约定的函数。
|
||||
|
||||
使用C函数调用约定的函数也可用作使用Objective-C块调用约定的函数,同时使用Objective-C块调用约定的函数也可用作使用Swift函数调用约定的函数。然而,只有非泛型的全局函数和本地函数或者不使用任何本地变量的闭包可以被用作使用C函数调用约定的函数。
|
||||
`escaping`
|
||||
在函数或者方法声明上使用该特性,它表示参数将不会被存储以供延迟执行,这将确保参数不会超出函数调用的生命周期。在使用 `escaping` 声明特性的函数类型中访问属性和方法时不需要显式地使用 `self.`。关于如何使用 `escaping` 特性的例子,请参阅 [逃逸闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html)。
|
||||
|
||||
`noreturn`
|
||||
>特性语法
|
||||
|
||||
该特性用于修饰函数或方法的类型,表明该函数或方法不会返回到它的调用者中去。你也可以用它标记函数或方法的声明,表示函数或方法的相应类型,`T`,是`@noreturn T`。
|
||||
> *特性 *→ @ <font color = 0x3386c8>特性名 特性参数子句</font><sub>可选</sub>
|
||||
|
||||
> 特性语法
|
||||
> *特性* → **@** [*特性名*](#attribute_name) [*特性参数子句*](#attribute_argument_clause) (可选)
|
||||
> *特性名* → [*标识符*](02_Lexical_Structure.html#identifiers)
|
||||
> *特性参数子句* → **(** [*平衡令牌列表*](#balanced_tokens) (可选) **)**
|
||||
> *特性(Attributes)列表* → [*特色*](#attribute) [*特性(Attributes)列表*](#attributes) (可选)
|
||||
> *平衡令牌列表* → [*平衡令牌*](#balanced_token) [*平衡令牌列表*](#balanced_tokens) (可选)
|
||||
> *平衡令牌* → **(** [*平衡令牌列表*](#balanced_tokens) (可选) **)**
|
||||
> *平衡令牌* → **[** [*平衡令牌列表*](#balanced_tokens) (可选) **]**
|
||||
> *平衡令牌* → **{** [*平衡令牌列表*](#balanced_tokens) (可选) **}**
|
||||
> *平衡令牌* → **任意标识符, 关键字, 字面量或运算符**
|
||||
> *平衡令牌* → **任意标点除了(, ), [, ], {, 或 }**
|
||||
> *特性名* → <font color = 0x3386c8>标识符
|
||||
|
||||
> *特性参数子句* → ( <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> )
|
||||
|
||||
> *特性列表* → <font color = 0x3386c8>特性 特性列表</font><sub>可选</sub>
|
||||
|
||||
> *均衡令牌列表* → <font color = 0x3386c8>均衡令牌 均衡令牌列表</font><sub>可选</sub>
|
||||
|
||||
> *均衡令牌* → ( <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> )
|
||||
|
||||
> *均衡令牌* → [ <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> ]
|
||||
|
||||
> *均衡令牌* → { <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub>}
|
||||
|
||||
> *均衡令牌* → 任意标识符,关键字,字面量或运算符
|
||||
|
||||
> *均衡令牌* → 任意标点除了 (,),[,],{,或 }
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
# 模式(Patterns)
|
||||
-----------------
|
||||
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[honghaoz](https://github.com/honghaoz)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[ray16897188](https://github.com/ray16897188),
|
||||
> [BridgeQ](https://github.com/WXGBridgeQ)
|
||||
> 翻译+校对:[ray16897188](https://github.com/ray16897188),
|
||||
|
||||
> 2.1
|
||||
> 翻译:[BridgeQ](https://github.com/WXGBridgeQ)
|
||||
|
||||
本页内容包括:
|
||||
|
||||
@ -17,97 +19,101 @@
|
||||
- [元组模式(Tuple Pattern)](#tuple_pattern)
|
||||
- [枚举用例模式(Enumeration Case Pattern)](#enumeration_case_pattern)
|
||||
- [可选模式(Optional Pattern)](#optional_pattern)
|
||||
- [类型转换模式(Type-Casting Pattern)](#type-casting_pattern)
|
||||
- [类型转换模式(Type-Casting Pattern)](#type-casting_patterns)
|
||||
- [表达式模式(Expression Pattern)](#expression_pattern)
|
||||
|
||||
模式(pattern)代表了单个值或者复合值的结构。例如,元组`(1, 2)`的结构是逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以把模式和各种同类型的值匹配起来。比如,`(x, y)`可以匹配元组`(1, 2)`,以及任何含两个元素的元组。除了将模式与一个值匹配外,你可以从复合值中提取出部分或全部,然后分别把各个部分和一个常量或变量绑定起来。
|
||||
模式代表单个值或者复合值的结构。例如,元组 `(1, 2)` 的结构是由逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如,`(x, y)` 可以匹配元组 `(1, 2)`,以及任何含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或变量绑定起来。
|
||||
|
||||
swift语言中模式有2个基本的分类:一类能成功和任何值的类型相匹配,另一类在运行时(runtime)和某特定值匹配时可能会失败。
|
||||
Swift 中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。
|
||||
|
||||
第一类模式用于解构简单变量,常量和可选绑定中的值。此类模式包括通配符模式(wildcard patterns),标识符模式(identifier patterns),以及任何包含了它们的值绑定模式(value binding patterns)或者元祖模式(tuple patterns)。你可以为这类模式指定一个类型标注(type annotation)从而限制它们只能匹配某种特定类型的值。
|
||||
第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。
|
||||
|
||||
第二类模式用于全模式匹配,这种情况下你用来相比较的值在运行时可能还不存在。此类模式包括枚举用例模式(enumeration case patterns),可选模式(optional patterns),表达式模式(expression patterns)和类型转换模式(type-casting patterns)。你在`switch`语句的case标签中,`do`语句的`catch`从句中,或者在`if, while, guard`和`for-in`语句的case条件句中使用这类模式。
|
||||
第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在 `switch` 语句的 `case` 标签中,`do` 语句的 `catch` 子句中,或者在 `if`、`while`、`guard` 和 `for-in` 语句的 `case` 条件句中使用这类模式。
|
||||
|
||||
> 模式(Patterns) 语法
|
||||
> *模式* → [*通配符模式*](../chapter3/07_Patterns.html#wildcard_pattern) [*类型标注*](../chapter3/03_Types.html#type_annotation) _可选_
|
||||
> *模式* → [*标识符模式*](../chapter3/07_Patterns.html#identifier_pattern) [*类型标注*](../chapter3/03_Types.html#type_annotati(Value Binding)on) _可选_
|
||||
> *模式* → [*值绑定模式*](../chapter3/07_Patterns.html#value_binding_pattern)
|
||||
> *模式* → [*元组模式*](../chapter3/07_Patterns.html#tuple_pattern) [*类型标注*](../chapter3/03_Types.html#type_annotation) _可选_
|
||||
> *模式* → [*枚举用例模式*](../chapter3/07_Patterns.html#enum_case_pattern)
|
||||
> *模式* → [*可选模式*](../chapter3/07_Patterns.html#optional_pattern)
|
||||
> *模式* → [*类型转换模式*](../chapter3/07_Patterns.html#type_casting_pattern)
|
||||
> *模式* → [*表达式模式*](../chapter3/07_Patterns.html#expression_pattern)
|
||||
> 模式语法
|
||||
<a name="pattern"></a>
|
||||
> *模式* → [*通配符模式*](#wildcard_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
|
||||
> *模式* → [*标识符模式*](#identifier_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
|
||||
> *模式* → [*值绑定模式*](#value-binding-pattern)
|
||||
> *模式* → [*元组模式*](#tuple-pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
|
||||
> *模式* → [*枚举用例模式*](#enum-case-pattern)
|
||||
> *模式* → [*可选模式*](#optional-pattern)
|
||||
> *模式* → [*类型转换模式*](#type-casting-pattern)
|
||||
> *模式* → [*表达式模式*](#expression-pattern)
|
||||
|
||||
<a name="wildcard_pattern"></a>
|
||||
## 通配符模式(Wildcard Pattern)
|
||||
|
||||
通配符模式由一个下划线(_)构成,且匹配并忽略任何值。当你不在乎被匹配的值时可以使用该模式。例如,下面这段代码在闭区间`1...3`中循环,每次循环时忽略该区间内的当前值:
|
||||
通配符模式由一个下划线(`_`)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可以使用该模式。例如,下面这段代码在闭区间 `1...3` 中迭代,每次迭代都忽略该区间的当前值:
|
||||
|
||||
```swift
|
||||
for _ in 1...3 {
|
||||
// Do something three times.
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> 通配符模式语法
|
||||
<a name="wildcard-pattern"></a>
|
||||
> *通配符模式* → **_**
|
||||
|
||||
<a name="identifier_pattern"></a>
|
||||
## 标识符模式(Identifier Pattern)
|
||||
|
||||
标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue`是一个标识符模式,匹配了类型是`Int`的`42`。
|
||||
标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue` 是一个标识符模式,匹配了 `Int` 类型的 `42`:
|
||||
|
||||
```swift
|
||||
let someValue = 42
|
||||
```
|
||||
|
||||
当匹配成功时,`42`被绑定(赋值)给常量`someValue`。
|
||||
当匹配成功时,`42` 被绑定(赋值)给常量 `someValue`。
|
||||
|
||||
如果一个变量或常量声明的左边的模式是一个标识符模式,那么这个标识符模式是一个隐式的值绑定模式(value-binding pattern)。
|
||||
如果一个变量或常量声明的左边是一个标识符模式,那么这个标识符模式是值绑定模式的子模式。
|
||||
|
||||
> 标识符模式语法
|
||||
> *标识符模式* → [*标识符*](LexicalStructure.html#identifier)
|
||||
<a name="identifier-pattern"></a>
|
||||
> *标识符模式* → [*标识符*](02_Lexical_Structure.md#identifier)
|
||||
|
||||
<a name="value-binding_pattern"></a>
|
||||
## 值绑定模式(Value-Binding Pattern)
|
||||
|
||||
值绑定模式把匹配到的值绑定给一个变量或常量名。把绑定匹配到的值绑定给常量时,用关键字`let`,绑定给变量时,用关键字`var`。
|
||||
值绑定模式把匹配到的值绑定给一个变量或常量。把匹配到的值绑定给常量时,用关键字 `let`,绑定给变量时,用关键字 `var`。
|
||||
|
||||
在值绑定模式中的标识符模式会把新命名的变量或常量与匹配值做绑定。例如,你可以拆开一个元组的元素,然后把每个元素绑定到其相应一个的标识符模式中。
|
||||
在值绑定模式中的标识符模式会把新命名的变量或常量与匹配到的值做绑定。例如,你可以拆开一个元组,然后把每个元素绑定到相应的标识符模式中。
|
||||
|
||||
```swift
|
||||
let point = (3, 2)
|
||||
switch point {
|
||||
// Bind x and y to the elements of point.
|
||||
// 将 point 中的元素绑定到 x 和 y
|
||||
case let (x, y):
|
||||
print("The point is at (\(x), \(y)).")
|
||||
}
|
||||
// prints "The point is at (3, 2).”
|
||||
// 打印 “The point is at (3, 2).”
|
||||
```
|
||||
|
||||
在上面这个例子中,`let`将元组模式`(x, y)`分配到各个标识符模式。正是由于这么做,`switch`语句中`case let (x, y):`和`case (let x, let y):`匹配到的值是一样的。
|
||||
在上面这个例子中,`let` 会分配到元组模式 `(x, y)` 中的各个标识符模式。因此,`switch` 语句中 `case let (x, y):` 和 `case (let x, let y):` 的匹配效果是一样的。
|
||||
|
||||
> 值绑定(Value Binding)模式语法
|
||||
> *值绑定模式* → **var** [*模式*](../chapter3/07_Patterns.html#pattern) | **let** [*模式*](../chapter3/07_Patterns.html#pattern)
|
||||
> 值绑定模式语法
|
||||
<a name="value-binding-pattern"></a>
|
||||
> *值绑定模式* → **var** [*模式*](#pattern) | **let** [*模式*](#pattern)
|
||||
|
||||
<a name="tuple_pattern"></a>
|
||||
## 元组模式(Tuple Pattern)
|
||||
## 元组模式
|
||||
|
||||
元组模式是逗号分隔的,有零个或多个模式的列表,并被一对圆括号括起来。元组模式匹配相应元组类型的值。
|
||||
元组模式是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。
|
||||
|
||||
你可以使用类型标注去限制一个元组模式能匹配哪些种元组类型。例如,在常量声明`let (x, y): (Int, Int) = (1, 2)`中的元组模式`(x, y): (Int, Int)`只匹配两个元素都是`Int`这种类型的元组。如果仅需要限制一个元组模式中的某几个元素,只需要直接对这几个元素提供类型标注即可。例如,在`let (x: String, y)`中的元组模式可以和任何有两个元素,且第一个元素类型是`String`的元组类型匹配。
|
||||
你可以使用类型标注去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。
|
||||
|
||||
当元组模式被用在`for-in`语句或者变量或常量声明时,它仅可以包含通配符模式,标识符模式,可选模式或者其他包含这些模式的元祖模式。比如下面这段代码就不正确,因为`(x, 0)`中的元素`0`是一个表达式模式:
|
||||
当元组模式被用于 `for-in` 语句或者变量和常量声明时,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式。比如下面这段代码就不正确,因为 `(x, 0)` 中的元素 `0` 是一个表达式模式:
|
||||
|
||||
```swift
|
||||
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
|
||||
// This code isn't valid.
|
||||
// 下面的代码是错误的
|
||||
for (x, 0) in points {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
对于只包含一个元素的元组,括号是不起作用的。模式只匹配这个单个元素的类型。举例来说,下面3条语句是等效的:
|
||||
只包含一个元素的元组模式的圆括号没有效果,模式只匹配这个单个元素的类型。举例来说,下面的语句是等效的:
|
||||
|
||||
```swift
|
||||
let a = 2 // a: Int = 2
|
||||
@ -116,80 +122,89 @@ let (a): Int = 2 // a: Int = 2
|
||||
```
|
||||
|
||||
> 元组模式语法
|
||||
> *元组模式* → **(** [*元组模式元素列表*](../chapter3/07_Patterns.html#tuple_pattern_element_list) _可选_ **)**
|
||||
> *元组模式元素列表* → [*元组模式元素*](../chapter3/07_Patterns.html#tuple_pattern_element) | [*元组模式元素*](../chapter3/07_Patterns.html#tuple_pattern_element) **,** [*元组模式元素列表*](../chapter3/07_Patterns.html#tuple_pattern_element_list)
|
||||
> *元组模式元素* → [*模式*](../chapter3/07_Patterns.html#pattern)
|
||||
<a name="tuple-pattern"></a>
|
||||
> *元组模式* → **(** [*元组模式元素列表*](#tuple-pattern-element-list)<sub>可选</sub> **)**
|
||||
<a name="tuple-pattern-element-list"></a>
|
||||
> *元组模式元素列表* → [*元组模式元素*](#tuple-pattern-element) | [*元组模式元素*](#tuple-pattern-element) **,** [*元组模式元素列表*](#tuple-pattern-element-list)
|
||||
<a name="tuple-pattern-element"></a>
|
||||
> *元组模式元素* → [*模式*](#pattern)
|
||||
|
||||
<a name="enumeration_case_pattern"></a>
|
||||
## 枚举用例模式(Enumeration Case Pattern)
|
||||
|
||||
一个枚举用例模式匹配现有的某个枚举类型的某个用例(case)。枚举用例模式出现在`switch`语句中的case标签中,以及`if`,`while`,`guard`和`for-in`语句的case条件中。
|
||||
枚举用例模式匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 `switch` 语句中的 `case` 标签中,以及 `if`、`while`、`guard` 和 `for-in` 语句的 `case` 条件中。
|
||||
|
||||
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用`switch`语句来匹配包含关联值枚举用例的例子,请参阅`Associated Values`.
|
||||
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../chapter2/08_Enumerations.md#associated_values)。
|
||||
|
||||
> 枚举用例模式语法
|
||||
> *enum-case-pattern* → [*类型标识*](../chapter3/03_Types.html#type_identifier) _可选_ **.** [*枚举的case名*](../chapter3/05_Declarations.html#enum_case_name) [*元组模式*](../chapter3/07_Patterns.html#tuple_pattern) _可选_
|
||||
<a name="enum-case-pattern"></a>
|
||||
> *枚举用例模式* → [*类型标识*](03_Types.md#type-identifier)<sub>可选</sub> **.** [*枚举用例名*](05_Declarations.md#enum-case-name) [*元组模式*](#tuple-pattern)<sub>可选</sub>
|
||||
|
||||
<a name="optional_pattern"></a>
|
||||
## 可选模式(Optional Pattern)
|
||||
|
||||
可选模式与封装在一个`Optional(Wrapped)`或者一个`ExplicitlyUnwrappedOptional(Wrapped)`枚举中的`Some(Wrapped)`用例相匹配。可选模式由一个标识符模式和紧随其后的一个问号组成,在某些情况下表现为枚举用例模式。
|
||||
可选模式匹配包装在一个 `Optional(Wrapped)` 或者 `ExplicitlyUnwrappedOptional(Wrapped)` 枚举中的 `Some(Wrapped)` 用例中的值。可选模式由一个标识符模式和紧随其后的一个问号组成,可以像枚举用例模式一样使用。
|
||||
|
||||
由于可选模式是`optional`和`ImplicitlyUnwrappedOptional`枚举用例模式的语法糖(syntactic sugar),下面的2种写法是一样的:
|
||||
由于可选模式是 `Optional` 和 `ImplicitlyUnwrappedOptional` 枚举用例模式的语法糖,下面两种写法是等效的:
|
||||
|
||||
```swift
|
||||
let someOptional: Int? = 42
|
||||
// Match using an enumeration case pattern
|
||||
// 使用枚举用例模式匹配
|
||||
if case .Some(let x) = someOptional {
|
||||
print(x)
|
||||
}
|
||||
|
||||
// Match using an optional pattern
|
||||
// 使用可选模式匹配
|
||||
if case let x? = someOptional {
|
||||
print(x)
|
||||
}
|
||||
```
|
||||
如果一个数组的元素是可选类型,可选模式为`for-in`语句提供了一种在该数组中迭代的简便方式,只为数组中的非空`non-nil`元素执行循环体。
|
||||
|
||||
可选模式为 `for-in` 语句提供了一种迭代数组的简便方式,只为数组中非 `nil` 的元素执行循环体。
|
||||
|
||||
```swift
|
||||
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
|
||||
// Match only non-nil values
|
||||
// 只匹配非 nil 的元素
|
||||
for case let number? in arrayOfOptinalInts {
|
||||
print("Found a \(number)")
|
||||
}
|
||||
//Found a 2
|
||||
//Found a 3
|
||||
//Found a 5
|
||||
|
||||
// Found a 2
|
||||
// Found a 3
|
||||
// Found a 5
|
||||
```
|
||||
|
||||
> 可选模式语法
|
||||
> *可选模式* → [*标识符模式*](../chapter3/03_Types.html#type_identifier) ?
|
||||
<a name="optional-pattern"></a>
|
||||
> *可选模式* → [*标识符模式*](03_Types.md#type-identifier) **?**
|
||||
|
||||
<a name="type-casting_patterns"></a>
|
||||
## 类型转换模式(Type-Casting Patterns)
|
||||
|
||||
有两种类型转换模式,`is`模式和`as`模式。这两种模式只出现在`switch`语句中的case标签中。`is`模式和`as`模式有以下形式:
|
||||
有两种类型转换模式,`is` 模式和 `as` 模式。`is` 模式只出现在 `switch` 语句中的 `case` 标签中。`is` 模式和 `as` 模式形式如下:
|
||||
|
||||
> is `type`
|
||||
> `pattern` as `type`
|
||||
> is `类型`
|
||||
> `模式` as `类型`
|
||||
|
||||
`is`模式仅当一个值的类型在运行时(runtime)和`is`模式右边的指定类型一致 - 或者是该类型的子类 - 的情况下,才会匹配这个值。`is`模式和`is`操作符有相似表现,它们都进行类型转换,却舍弃返回的类型。
|
||||
`is` 模式仅当一个值的类型在运行时和 `is` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。`is` 模式和 `is` 运算符有相似表现,它们都进行类型转换,但是 `is` 模式没有返回类型。
|
||||
|
||||
`as`模式仅当一个值的类型在运行时(runtime)和`as`模式右边的指定类型一致 - 或者是该类型的子类 - 的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成`as`模式左边指定的模式。
|
||||
`as` 模式仅当一个值的类型在运行时和 `as` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 `as` 模式右边指定的类型。
|
||||
|
||||
关于使用`switch`语句来匹配`is`模式和`as`模式值的例子,请参阅`Type Casting for Any and AnyObject`。
|
||||
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../chapter2/19_Type_Casting.md#type_casting_for_any_and_anyobject)。
|
||||
|
||||
> 类型转换模式语法
|
||||
> *type-casting-pattern* → [*is模式*](../chapter3/07_Patterns.html#is_pattern) | [*as模式*](../chapter3/07_Patterns.html#as_pattern)
|
||||
> *is模式* → **is** [*类型*](../chapter3/03_Types.html#type)
|
||||
> *as模式* → [*模式*](../chapter3/07_Patterns.html#pattern) **as** [*类型*](../chapter3/03_Types.html#type)
|
||||
<a name="type-casting-pattern"></a>
|
||||
> *类型转换模式* → [*is模式*](#is-pattern) | [*as模式*](#as-pattern)
|
||||
<a name="is-pattern"></a>
|
||||
> *is模式* → **is** [*类型*](03_Types.md#type)
|
||||
<a name="as-pattern"></a>
|
||||
> *as模式* → [*模式*](#pattern) **as** [*类型*](03_Types.md#type)
|
||||
|
||||
<a name="expression_pattern"></a>
|
||||
## 表达式模式(Expression Pattern)
|
||||
|
||||
一个表达式模式代表了一个表达式的值。表达式模式只出现在`switch`语句中的`case`标签中。
|
||||
表达式模式代表表达式的值。表达式模式只出现在 `switch` 语句中的 `case` 标签中。
|
||||
|
||||
由表达式模式所代表的表达式与使用了Swift标准库中`~=`操作符的输入表达式的值进行比较。如果`~=`操作符返回`true`,则匹配成功。默认情况下,`~=`操作符使用`==`操作符来比较两个相同类型的值。它也可以将一个整型数值与一个`Range`对象中的一段整数区间做匹配,正如下面这个例子所示:
|
||||
表达式模式代表的表达式会使用 Swift 标准库中的 `~=` 运算符与输入表达式的值进行比较。如果 `~=` 运算符返回 `true`,则匹配成功。默认情况下,`~=` 运算符使用 `==` 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 `Range` 实例中的一段整数区间做匹配,正如下面这个例子所示:
|
||||
|
||||
```swift
|
||||
let point = (1, 2)
|
||||
@ -201,24 +216,26 @@ case (-2...2, -2...2):
|
||||
default:
|
||||
print("The point is at (\(point.0), \(point.1)).")
|
||||
}
|
||||
// prints "(1, 2) is near the origin.”
|
||||
// 打印 “(1, 2) is near the origin.”
|
||||
```
|
||||
|
||||
你可以重载`~=`操作符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,拿`point`表达式去比较字符串形式的点。
|
||||
你可以重载 `~=` 运算符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,将 `point` 表达式与字符串形式表示的点进行比较。
|
||||
|
||||
```swift
|
||||
// Overload the ~= operator to match a string with an integer
|
||||
// 重载 ~= 运算符对字符串和整数进行比较
|
||||
func ~=(pattern: String, value: Int) -> Bool {
|
||||
return pattern == "\(value)"
|
||||
}
|
||||
|
||||
switch point {
|
||||
case ("0", "0"):
|
||||
print("(0, 0) is at the origin.")
|
||||
default:
|
||||
print("The point is at (\(point.0), \(point.1)).")
|
||||
}
|
||||
// prints "(1, 2) is near the origin.”
|
||||
// 打印 “The point is at (1, 2).”
|
||||
```
|
||||
|
||||
> 表达式模式语法
|
||||
> *表达式模式* → [*表达式*](../chapter3/04_Expressions.html#expression)
|
||||
<a name="expression-pattern"></a>
|
||||
> *表达式模式* → [*表达式*](04_Expressions.md#expression)
|
||||
|
||||
@ -8,34 +8,37 @@
|
||||
> 2.0
|
||||
> 翻译+校对:[wardenNScaiyi](https:github.com/wardenNScaiyi)
|
||||
|
||||
> 3.0
|
||||
> 翻译+校对:[chenmingjia](https:github.com/chenmingjia)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [泛型形参子句](#generic_parameter)
|
||||
- [Where 子句](#where_clauses)
|
||||
- [泛型实参子句](#generic_argument)
|
||||
|
||||
本节涉及泛型类型、泛型函数以及泛型初始化器(**initializer**)的参数,包括形参和实参。声明泛型类型、函数或初始化器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型初始化器时,就用具体的类型实参替代之。
|
||||
本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之。
|
||||
|
||||
关于 Swift 语言的泛型概述,见[泛型](../chapter2/23_Generics.md)(第二部分第23章)。
|
||||
关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/23_Generics.md)。
|
||||
|
||||
<a name="generic_parameter"></a>
|
||||
## 泛型形参子句
|
||||
|
||||
泛型形参子句指定泛型类型或函数的类型形参,以及这些参数的关联约束和关联类型要求(**requirement**)。泛型形参子句用尖括号(<>)包住,并且有以下两种形式:
|
||||
泛型形参子句指定泛型类型或函数的类型形参,以及这些参数相关的约束和要求。泛型形参子句用尖括号(`<>`)包住,形式如下:
|
||||
|
||||
> <`泛型形参列表`>
|
||||
> <`泛型形参列表` where `关联类型要求`>
|
||||
|
||||
泛型形参列表中泛型形参用逗号分开,其中每一个采用以下形式:
|
||||
|
||||
> `类型形参` : `约束`
|
||||
|
||||
泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 T,U,V,Key,Value 等)的名字而已。你可以在泛型类型、函数的其余部分或者初始化器声明,包括函数或初始化器的签名中使用它(与其任何相关类型)。
|
||||
泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 `T`,`U`,`V`,`Key`,`Value` 等)的名字而已。你可以在泛型类型、函数的其余部分或者构造器声明,包括函数或构造器的签名中使用它(以及它的关联类型)。
|
||||
|
||||
约束用于指明该类型形参继承自某个类或者遵守某个协议或协议的一部分。例如,在下面的泛型函数中,泛型形参`T: Comparable`表示任何用于替代类型形参`T`的类型实参必须满足`Comparable`协议。
|
||||
约束用于指明该类型形参继承自某个类或者符合某个协议或协议组合。例如,在下面的泛型函数中,泛型形参 `T: Comparable` 表示任何用于替代类型形参 `T` 的类型实参必须满足 `Comparable` 协议。
|
||||
|
||||
|
||||
```swift
|
||||
func simpleMax<T: Comparable>(x: T, _ y: T) -> T {
|
||||
func simpleMax<T: Comparable>(_ x: T, _ y: T) -> T {
|
||||
if x < y {
|
||||
return y
|
||||
}
|
||||
@ -43,74 +46,84 @@ func simpleMax<T: Comparable>(x: T, _ y: T) -> T {
|
||||
}
|
||||
```
|
||||
|
||||
例如,因为 `Int` 和 `Double` 均满足`Comparable`协议,所以该函数可以接受这两种类型。与泛型类型相反,调用泛型函数或构造器时不需要指定泛型实参子句。类型实参由传递给函数或构造器的实参推断而出。
|
||||
|
||||
|
||||
如,`Int`和`Double`均满足`Comparable`协议,该函数接受任何一种类型。与泛型类型相反,调用泛型函数或初始化器时不需要指定泛型实参子句。类型实参由传递给函数或初始化器的实参推断而出。
|
||||
|
||||
|
||||
```
|
||||
simpleMax(17, 42) // T被推断出为Int类型
|
||||
simpleMax(3.14159, 2.71828) // T被推断出为Double类型
|
||||
```swift
|
||||
simpleMax(17, 42) // T 被推断为 Int 类型
|
||||
simpleMax(3.14159, 2.71828) // T 被推断为 Double 类型
|
||||
```
|
||||
|
||||
## Where 子句
|
||||
<a name="where_clauses"></a>
|
||||
### Where 子句
|
||||
|
||||
要想对类型形参及其关联类型指定额外关联类型要求,可以在泛型形参列表之后添加`where`子句。`where`子句由关键字`where`及其后的用逗号分割的多个关联类型要求组成。
|
||||
要想对类型形参及其关联类型指定额外要求,可以在函数体或者类型的大括号之前添加 `where` 子句。`where` 子句由关键字 `where` 及其后的用逗号分隔的一个或多个要求组成。
|
||||
|
||||
`where`子句中的关联关系用于指明该类型形参继承自某个类或遵守某个协议或协议的一部分。尽管`where`子句提供了语法糖使其有助于表达类型形参上的简单约束(如`T: Comparable`等同于`T where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束。如,`<T where T: C, T: P>`表示泛型类型`T`继承自类`C`且遵守协议`P`。
|
||||
> `where` : `类型要求`
|
||||
|
||||
如上所述,可以强制约束类型形参的关联类型遵守某个协议。例如`<T: Generator where T.Element: Equatable>`表示`T`遵守`Generator`协议,而且`T`的关联类型`T.Element`遵守`Eauatable`协议(`T`有关联类型`Element`是因为`Generator`声明了`Element`,而`T`遵守`Generator`协议)。
|
||||
`where` 子句中的要求用于指明该类型形参继承自某个类或符合某个协议或协议组合。尽管 `where` 子句提供了语法糖使其有助于表达类型形参上的简单约束(如 `<T: Comparable>` 等同于 `<T> where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束,例如你可以强制形参的关联类型遵守协议,如,` <S: Sequence> where S.Iterator.Element: Equatable` 表示泛型类型 `S` 遵守`Sequence`协议并且关联类型`S.Iterator.Element`遵守`Equatable`协议,这个约束确保队列的每一个元素都是符合 `Equatable` 协议的。
|
||||
|
||||
也可以用操作符`==`来指定两个类型等效的关联关系。例如,有这样一个约束:`T`和`U`遵守`Generator`协议,同时要求它们的关联类型等同,可以这样来表达:`<T: Generator, U: Generator where T.Element == U.Element>`。
|
||||
也可以用操作符 `==` 来指定两个类型必须相同。例如,泛型形参子句 ` <S1: Sequence, S2: Sequence> where S1.Iterator.Element == S2.Iterator.Element` 表示 `S1` 和 `S2` 必须都符合 `SequenceType` 协议,而且两个序列中的元素类型必须相同。
|
||||
|
||||
当然,替代类型形参的类型实参必须满足所有类型形参的约束和关联类型要求。
|
||||
当然,替代类型形参的类型实参必须满足所有的约束和要求。
|
||||
|
||||
泛型函数或初始化器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或关联类型要求,抑或二者皆不同。当调用重载的泛型函数或始化器时,编译器会用这些约束来决定调用哪个重载函数或始化器。
|
||||
泛型函数或构造器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或要求,抑或二者皆不同。当调用重载的泛型函数或构造器时,编译器会根据这些约束来决定调用哪个重载函数或构造器。
|
||||
|
||||
更多关于泛型where从句的信息和关于泛型函数声明的例子,可以看一看 [泛型where子句](https://github.com/numbbbbb/the-swift-programming-language-in-chinese/blob/gh-pages/source/chapter2/23_Generics.md#where_clauses)
|
||||
|
||||
> 泛型形参子句语法
|
||||
> *泛型参数子句* → **<** [*泛型参数列表*](GenericParametersAndArguments.html#generic_parameter_list) [*约束子句*](GenericParametersAndArguments.html#requirement_clause) _可选_ **>**
|
||||
> *泛型参数列表* → [*泛形参数*](GenericParametersAndArguments.html#generic_parameter) | [*泛形参数*](GenericParametersAndArguments.html#generic_parameter) **,** [*泛型参数列表*](GenericParametersAndArguments.html#generic_parameter_list)
|
||||
> *泛形参数* → [*类型名称*](../chapter3/03_Types.html#type_name)
|
||||
> *泛形参数* → [*类型名称*](../chapter3/03_Types.html#type_name) **:** [*类型标识*](../chapter3/03_Types.html#type_identifier)
|
||||
> *泛形参数* → [*类型名称*](../chapter3/03_Types.html#type_name) **:** [*协议合成类型*](../chapter3/03_Types.html#protocol_composition_type)
|
||||
> *约束子句* → **where** [*约束列表*](GenericParametersAndArguments.html#requirement_list)
|
||||
> *约束列表* → [*约束*](GenericParametersAndArguments.html#requirement) | [*约束*](GenericParametersAndArguments.html#requirement) **,** [*约束列表*](GenericParametersAndArguments.html#requirement_list)
|
||||
> *约束* → [*一致性约束*](GenericParametersAndArguments.html#conformance_requirement) | [*同类型约束*](GenericParametersAndArguments.html#same_type_requirement)
|
||||
> *一致性约束* → [*类型标识*](../chapter3/03_Types.html#type_identifier) **:** [*类型标识*](../chapter3/03_Types.html#type_identifier)
|
||||
> *一致性约束* → [*类型标识*](../chapter3/03_Types.html#type_identifier) **:** [*协议合成类型*](../chapter3/03_Types.html#protocol_composition_type)
|
||||
> *同类型约束* → [*类型标识*](../chapter3/03_Types.html#type_identifier) **==** [*类型标识*](../chapter3/03_Types.html#type_identifier)
|
||||
|
||||
<a name="generic-parameter-clause"></a>
|
||||
> *泛型形参子句* → **<** [*泛型形参列表*](#generic-parameter-list) [*约束子句*](#requirement-clause)<sub>可选</sub> **>**
|
||||
<a name="generic-parameter-list"></a>
|
||||
> *泛型形参列表* → [*泛形形参*](#generic-parameter) | [*泛形形参*](#generic-parameter) **,** [*泛型形参列表*](#generic-parameter-list)
|
||||
<a name="generic-parameter"></a>
|
||||
> *泛形形参* → [*类型名称*](03_Types.html#type-name)
|
||||
> *泛形形参* → [*类型名称*](03_Types.html#type-name) **:** [*类型标识符*](03_Types.html#type-identifier)
|
||||
> *泛形形参* → [*类型名称*](03_Types.html#type-name) **:** [*协议合成类型*](03_Types.html#protocol-composition-type)
|
||||
|
||||
<a name="requirement-clause"></a>
|
||||
> *约束子句* → **where** [*约束列表*](#requirement-list)
|
||||
<a name="requirement-list"></a>
|
||||
> *约束列表* → [*约束*](#requirement) | [*约束*](#requirement) **,** [*约束列表*](#requirement-list)
|
||||
<a name="requirement"></a>
|
||||
> *约束* → [*一致性约束*](#conformance-requirement) | [*同类型约束*](#same-type-requirement)
|
||||
|
||||
<a name="conformance-requirement"></a>
|
||||
> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*类型标识符*](03_Types.html#type-identifier)
|
||||
> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*协议合成类型*](03_Types.html#protocol-composition-type)
|
||||
<a name="same-type-requirement"></a>
|
||||
> *同类型约束* → [*类型标识符*](03_Types.html#type-identifier) **==** [*类型*](03_Types.html#type)
|
||||
|
||||
|
||||
<a name="generic_argument"></a>
|
||||
## 泛型实参子句
|
||||
|
||||
泛型实参子句指定_泛型类型_的类型实参。泛型实参子句用尖括号(<>)包住,形式如下:
|
||||
泛型实参子句指定泛型类型的类型实参。泛型实参子句用尖括号(`<>`)包住,形式如下:
|
||||
|
||||
> <`泛型实参列表`>
|
||||
|
||||
泛型实参列表中类型实参有逗号分开。类型实参是实际具体类型的名字,用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。如,Swift标准库的泛型字典类型定义如下:
|
||||
|
||||
泛型实参列表中类型实参用逗号分开。类型实参是实际具体类型的名字,用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。例如,Swift 标准库中的泛型字典类型的的简化定义如下:
|
||||
|
||||
```swift
|
||||
struct Dictionary<KeyTypel: Hashable, ValueType>: Collection, DictionaryLiteralConvertible {
|
||||
|
||||
/* .. */
|
||||
|
||||
struct Dictionary<Key: Hashable, Value>: CollectionType, DictionaryLiteralConvertible {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
泛型`Dictionary`类型的特化版本,`Dictionary<String, Int>`就是用具体的`String`和`Int`类型替代泛型类型`KeyType: Hashable`和`ValueType`产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何`where`子句所指定的额外的关联类型要求。上面的例子中,类型形参`Key`类型要求满足`Hashable`协议,因此`String`也必须满足`Hashable`协议。
|
||||
泛型 `Dictionary` 类型的特化版本,`Dictionary<String, Int>` 就是用具体的 `String` 和 `Int` 类型替代泛型类型 `Key: Hashable` 和 `Value` 产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何 `where` 子句所指定的额外的关联类型要求。上面的例子中,类型形参 `Key` 的类型必须符合 `Hashable` 协议,因此 `String` 也必须满足 `Hashable` 协议。
|
||||
|
||||
可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本`Array<Int>`替代泛型类型`Array<T>`的类型形参 `T` 来实现。
|
||||
可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本 `Array<Int>` 替代泛型类型 `Array<T>` 的类型形参 `T` 来实现。
|
||||
|
||||
```
|
||||
```swift
|
||||
let arrayOfArrays: Array<Array<Int>> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
|
||||
```
|
||||
|
||||
如[泛型形参子句](#generic_parameter)所述,不能用泛型实参子句来指定泛型函数或初始化器的类型实参。
|
||||
如 [泛型形参子句](#generic_parameter) 所述,不能用泛型实参子句来指定泛型函数或构造器的类型实参。
|
||||
|
||||
> 泛型实参子句语法
|
||||
> *(泛型参数子句Generic Argument Clause)* → **<** [*泛型参数列表*](GenericParametersAndArguments.html#generic_argument_list) **>**
|
||||
> *泛型参数列表* → [*泛型参数*](GenericParametersAndArguments.html#generic_argument) | [*泛型参数*](GenericParametersAndArguments.html#generic_argument) **,** [*泛型参数列表*](GenericParametersAndArguments.html#generic_argument_list)
|
||||
> *泛型参数* → [*类型*](../chapter3/03_Types.html#type)
|
||||
<a name="generic-argument-clause"></a>
|
||||
> *泛型实参子句* → **<** [*泛型实参列表*](#generic-argument-list) **>**
|
||||
<a name="generic-argument-list"></a>
|
||||
> *泛型实参列表* → [*泛型实参*](#generic-argument) | [*泛型实参*](#generic-argument) **,** [*泛型实参列表*](#generic-argument-list)
|
||||
<a name="generic-argument"></a>
|
||||
> *泛型实参* → [*类型*](03_Types.html#type)
|
||||
|
||||
@ -239,7 +239,7 @@
|
||||
> *声明* → [*构造器声明*](../chapter3/05_Declarations.html#initializer_declaration)
|
||||
> *声明* → [*析构器声明*](../chapter3/05_Declarations.html#deinitializer_declaration)
|
||||
> *声明* → [*扩展声明*](../chapter3/05_Declarations.html#extension_declaration)
|
||||
> *声明* → [*下标脚本声明*](../chapter3/05_Declarations.html#subscript_declaration)
|
||||
> *声明* → [*下标声明*](../chapter3/05_Declarations.html#subscript_declaration)
|
||||
> *声明* → [*运算符声明*](../chapter3/05_Declarations.html#operator_declaration)
|
||||
> *声明(Declarations)集* → [*声明*](../chapter3/05_Declarations.html#declaration) [*声明(Declarations)集*](../chapter3/05_Declarations.html#declarations) _可选_
|
||||
|
||||
@ -373,7 +373,7 @@
|
||||
> *协议成员声明* → [*协议属性声明*](../chapter3/05_Declarations.html#protocol_property_declaration)
|
||||
> *协议成员声明* → [*协议方法声明*](../chapter3/05_Declarations.html#protocol_method_declaration)
|
||||
> *协议成员声明* → [*协议构造器声明*](../chapter3/05_Declarations.html#protocol_initializer_declaration)
|
||||
> *协议成员声明* → [*协议下标脚本声明*](../chapter3/05_Declarations.html#protocol_subscript_declaration)
|
||||
> *协议成员声明* → [*协议下标声明*](../chapter3/05_Declarations.html#protocol_subscript_declaration)
|
||||
> *协议成员声明* → [*协议关联类型声明*](../chapter3/05_Declarations.html#protocol_associated_type_declaration)
|
||||
> *协议成员声明(Declarations)集* → [*协议成员声明*](../chapter3/05_Declarations.html#protocol_member_declaration) [*协议成员声明(Declarations)集*](../chapter3/05_Declarations.html#protocol_member_declarations) _可选_
|
||||
|
||||
@ -394,8 +394,8 @@
|
||||
|
||||
<!-- -->
|
||||
|
||||
> 协议下标脚本声明语法
|
||||
> *协议下标脚本声明* → [*下标脚本头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标脚本结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
|
||||
> 协议下标声明语法
|
||||
> *协议下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
|
||||
|
||||
<!-- -->
|
||||
|
||||
@ -426,12 +426,12 @@
|
||||
|
||||
<!-- -->
|
||||
|
||||
> 下标脚本声明语法
|
||||
> *下标脚本声明* → [*下标脚本头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标脚本结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*代码块*](../chapter3/05_Declarations.html#code_block)
|
||||
> *下标脚本声明* → [*下标脚本头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标脚本结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter块*](../chapter3/05_Declarations.html#getter_setter_block)
|
||||
> *下标脚本声明* → [*下标脚本头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标脚本结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
|
||||
> *下标脚本头(Head)* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改器(declaration-modifiers)*](TODO) _可选_ **subscript** [*参数从句*](../chapter3/05_Declarations.html#parameter_clause)
|
||||
> *下标脚本结果(Result)* → **->** [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type)
|
||||
> 下标声明语法
|
||||
> *下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*代码块*](../chapter3/05_Declarations.html#code_block)
|
||||
> *下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter块*](../chapter3/05_Declarations.html#getter_setter_block)
|
||||
> *下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
|
||||
> *下标头(Head)* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改器(declaration-modifiers)*](TODO) _可选_ **subscript** [*参数从句*](../chapter3/05_Declarations.html#parameter_clause)
|
||||
> *下标结果(Result)* → **->** [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type)
|
||||
|
||||
<!-- -->
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user