更新部分内容到 Swift 5.7 (#1200)
* 更新内容到 Swift 5.7 * 更新内容到 Swift 5.7 * 更新内容到 Swift 5.7 * update to Swift version 5.7 * 更新内容到 Swift 5.7 * 更新内容到 Swift 5.7 * 修正部分术语 * 更新内容到 Swift 5.7 * 更新内容到 Swift 5.7 * 标题格式修改 * 修改了部分用词 * 修改了代码块格式 * 修改了代码段及行内代码格式 * 修改了代码段及行内代码样式 * 按照排版要求重新修改了部分格式 * Delete 02_Lexical_Structure.md * Delete 03_Types.md * Delete 04_Expressions.md * Delete 05_Statements.md * Delete 07_Attributes.md * Delete 10_Summary_of_the_Grammar.md * 根据排版指南修改了部分格式 * 根据排版指南修改了部分格式 * Update source/03_language_reference/02_Lexical_Structure.md Co-authored-by: Jie Liang <lj925184928@gmail.com>
This commit is contained in:
@ -587,16 +587,38 @@ if let actualNumber = Int(possibleNumber) {
|
||||
|
||||
如果转换成功,`actualNumber` 常量可以在 `if` 语句的第一个分支中使用。它已经被可选类型 *包含的* 值初始化过,所以不需要再使用 `!` 后缀来获取它的值。在这个例子中,`actualNumber` 只被用来输出转换结果。
|
||||
|
||||
你可以在可选绑定中使用常量和变量。如果你想在 `if` 语句的第一个分支中操作 `actualNumber` 的值,你可以改成 `if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。
|
||||
如果你在访问它包含的值后不需要引用原来的可选常量或是可选变量,你可以对新的常量或是新的变量使用相同的名称:
|
||||
|
||||
你可以包含多个可选绑定或多个布尔条件在一个 `if` 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 `nil`,或者任意一个布尔条件为 `false`,则整个 `if` 条件判断为 `false`。下面的两个 `if` 语句是等价的:
|
||||
```swift
|
||||
let myNumber = Int(possibleNumber)
|
||||
// 此处 myNumber 为一可选整型
|
||||
if let myNumber = myNumber {
|
||||
// 此处 myNumber 为一不可选整型
|
||||
print("My number is \(myNumber)")
|
||||
}
|
||||
// 输出 "My number is 123"
|
||||
```
|
||||
|
||||
正如前一个例子中的代码一样,本例代码首先检查 `myNumber` 是否包含任何值。若 `myNumber` 包含有任何值,则该值将成为新常量 `myNumber` 的值。在 `if` 语句的主体中,写入的 `myNumber` 指向这一个新的非可选常量。在 `if` 语句开始前和语句结束后,写入的 `myNumber` 指向可选的整数常量。
|
||||
|
||||
由于这种代码非常常见,你可以通过一个更简短的写法来解包一个可选值:只写你要展开的常量或变量的名称。新的常量/变量将使用相同的名称作为其隐式解包可选值。
|
||||
|
||||
``` swift
|
||||
if let myNumber{
|
||||
print("My number is \(muNumber)")
|
||||
}
|
||||
// 输出 "My number is 123"
|
||||
```
|
||||
|
||||
你可以在可选绑定中使用常量和变量。如果你想在 `if` 语句的第一个分支中操作 `actualNumber` 的值,你可以改成 `if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。你在 `if` 语句中对 `myNumber` 所做的更改将仅作用于该局部变量而非你解包的原始可选常量/变量。
|
||||
|
||||
你可以包含多个可选绑定或多个布尔条件在一个 `if` 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 `nil`,或者任意一个布尔条件为 `false`,则整个 `if` 条件判断为 `false`。下面的两个 `if` 语句是等效的:
|
||||
|
||||
```swift
|
||||
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
|
||||
print("\(firstNumber) < \(secondNumber) < 100")
|
||||
}
|
||||
// 输出“4 < 42 < 100”
|
||||
|
||||
if let firstNumber = Int("4") {
|
||||
if let secondNumber = Int("42") {
|
||||
if firstNumber < secondNumber && secondNumber < 100 {
|
||||
|
||||
@ -16,7 +16,7 @@ Swift 还提供了 C 语言没有的区间运算符,例如 `a..<b` 或 `a...b`
|
||||
- *二元*运算符操作两个操作对象(如 `2 + 3`),是*中置*的,因为它们出现在两个操作对象之间。
|
||||
- *三元*运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三目运算符(`a ? b : c`)。
|
||||
|
||||
受运算符影响的值叫*操作数*,在表达式 `1 + 2` 中,加号 `+` 是二元运算符,它的两个操作数是值 `1` 和 `2`。
|
||||
受运算符影响的值叫*操作数*,在表达式 `1 + 2` 中,加号 `+` 是中置运算符,它的两个操作数是值 `1` 和 `2`。
|
||||
|
||||
## 赋值运算符 {#assignment-operator}
|
||||
|
||||
|
||||
@ -765,3 +765,35 @@ if #available(平台名称 版本号, ..., *) {
|
||||
APIs 不可用,使用先前版本API的语句将执行
|
||||
}
|
||||
```
|
||||
|
||||
当你在 `guard` 语句中使用可用性条件时,它将细化用于该代码块中其余代码的可用性信息。
|
||||
|
||||
```swift
|
||||
@avaliable(macOS 10.12, *)
|
||||
struct ColorPreference {
|
||||
var bestColor = "blue"
|
||||
}
|
||||
func chooseBestColor() -> String {
|
||||
guard #avaliable(macOS 10.12, *) else{
|
||||
return "gray"
|
||||
}
|
||||
let colors = ColorPreference()
|
||||
return colors.bestColor
|
||||
}
|
||||
```
|
||||
|
||||
在上面的例子中,结构体 `ColorPreference` 需要 macOS 10.12 或更高的版本。函数 `ChooseBestColor()` 先以一个可用性防护开头,若平台版本过低无法运行 `ColorPreference` 时,将执行该低版本平台可用的行为。而在 `guard` 语句后,你将能够使用 macOS 10.12 或更高版本的API。
|
||||
|
||||
除了 `#available` 以外, Swift 还支持通过不可用性条件来进行不可用性检查。举例如下,两种检查都能实现同样的效果:
|
||||
|
||||
```swift
|
||||
if #available(iOS 10, *){
|
||||
} else {
|
||||
//回滚代码
|
||||
}
|
||||
if #unavailable(iOS 10) {
|
||||
//回滚代码
|
||||
}
|
||||
```
|
||||
|
||||
若可用性检查只提供了回滚代码,改用用 `#unavailable` 能提升程序整体的可读性。
|
||||
@ -225,6 +225,9 @@ print(anotherGreeting(for: "Dave"))
|
||||
|
||||
正如你将会在 [简略的 Getter 声明](./10_Properties.md) 里看到的, 一个属性的 getter 也可以使用隐式返回的形式。
|
||||
|
||||
>注意
|
||||
|
||||
>作为隐式返回值编写的代码需要返回一些值。例如,你不能使用 `print(13)` 作为隐式返回值。然而,你可以使用不返回值的函数(如 `fatalError("Oh no!")`)作为隐式返回值,因为 Swift 知道它们并不会产生任何隐式返回。
|
||||
|
||||
## 函数参数标签和参数名称 {#Function-Argument-Labels-and-Parameter-Names}
|
||||
|
||||
|
||||
@ -214,6 +214,35 @@ let strings = numbers.map {
|
||||
|
||||
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 `map(_:)` 方法的括号内。
|
||||
|
||||
如果一个函数接受多个闭包,您需要省略第一个尾随闭包的参数标签,并为其余尾随闭包添加标签。例如,以下函数将为图片库加载一张图片:
|
||||
|
||||
```swift
|
||||
func loadPicture(from server: Server, completion:(Picture) -> Void,
|
||||
onFailure: () -> Void) {
|
||||
if let picture = download("photo.jpg", from: server){
|
||||
completion(picture)
|
||||
}else{
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当您调用该函数以加载图片时,需要提供两个闭包。第一个闭包是一个完成处理程序,它在成功下载后加载图片;第二个闭包是一个错误处理程序,它向用户显示错误。
|
||||
|
||||
```swift
|
||||
loadPicture(from: someServer){ picture in
|
||||
someView.currentPicture = picture
|
||||
} onFailure: {
|
||||
print("Couldn't download the next picture.")
|
||||
}
|
||||
```
|
||||
|
||||
在本例中,`loadPicture(from:completion:onFailure:)` 函数将它的网络任务分配到后台,并在网络任务完成时调用两个完成处理程序中的一个。通过这种方法编写函数,您将能够把负责处理网络故障的代码和成功下载后更新用户界面的代码干净地区分开,而不是只使用一个闭包处理两种情况。
|
||||
|
||||
>注意
|
||||
>
|
||||
>完成处理程序可能很难阅读,特别是您必须嵌套多个完成处理程序时。另一种方法是使用异步代码,如章节[并发](./28_Concurrency.md#function-types-as-return-types) 中所述。
|
||||
|
||||
## 值捕获 {#capturing-values}
|
||||
|
||||
闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
|
||||
|
||||
@ -132,6 +132,7 @@ struct Rect {
|
||||
var square = Rect(origin: Point(x: 0.0, y: 0.0),
|
||||
size: Size(width: 10.0, height: 10.0))
|
||||
let initialSquareCenter = square.center
|
||||
// initialSquareCenter 位于(5.0, 5.0)
|
||||
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)”
|
||||
@ -302,7 +303,7 @@ struct TwelveOrLess {
|
||||
}
|
||||
```
|
||||
|
||||
这个 setter 确保新值小于 12,而且返回被存储的值。
|
||||
这个 setter 确保新值小于或等于 12,而且返回被存储的值。
|
||||
> 注意
|
||||
>
|
||||
> 上面例子以 `private` 的方式声明 `number` 变量,这使得 `number` 仅在 `TwelveOrLess` 的实现中使用。写在其他地方的代码通过使用 `wrappedValue` 的 getter 和 setter 来获取这个值,但不能直接使用 `number`。有关 `private` 的更多信息,请参考 [访问控制](./26_Access_Control.md)
|
||||
|
||||
@ -535,7 +535,7 @@ print("Bicycle: \(bicycle.description)")
|
||||
// 打印“Bicycle: 2 wheel(s)”
|
||||
```
|
||||
|
||||
如果子类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的指定构造器,你可以在所有子类的存储属性赋值之后省略 `super.init()` 的调用。
|
||||
如果子类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个同步、无参数的指定构造器,你可以在所有子类的存储属性赋值之后省略 `super.init()` 的调用。若父类有一个异步的构造器,你就需要明确地写入 `await super.init()`。
|
||||
|
||||
这个例子定义了另一个 `Vehicle` 的子类 `Hoverboard` ,只设置它的 `color` 属性。这个构造器依赖隐式调用父类的构造器来完成,而不是显示调用 `super.init()`。
|
||||
|
||||
|
||||
@ -276,7 +276,7 @@ signedOverflow = signedOverflow &- 1
|
||||
|
||||
类和结构体可以为现有的运算符提供自定义的实现。这通常被称为运算符*重载*。
|
||||
|
||||
下面的例子展示了如何让自定义的结构体支持加法运算符(`+`)。算术加法运算符是一个*二元运算符*,因为它是对两个值进行运算,同时它还可以称为*中缀*运算符,因为它出现在两个值中间。
|
||||
下面的例子展示了如何让自定义的结构体支持加法运算符(`+`)。算术加法运算符是一个二元运算符,因为它是对两个值进行运算,同时它还可以称为中缀运算符,因为它出现在两个值中间。
|
||||
|
||||
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以将两个 `Vector2D` 结构体实例进行相加的*运算符函数*:
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ Swift 对于结构化的编写异步和并行代码有着原生的支持。异
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你曾经写过并发的代码的话,那可能使用过线程。Swift 中的并发模型是基于线程的,但你不会直接和线程打交道。在 Swift 中,一个异步函数可以交出它在某个线程上的运行权,这样另一个异步函数在这个函数被阻塞时就能获得此线程的运行权。
|
||||
> 如果你曾经写过并发的代码的话,那可能使用过线程。Swift 中的并发模型是基于线程的,但你不会直接和线程打交道。在 Swift 中,一个异步函数可以交出它在某个线程上的运行权,这样另一个异步函数在这个函数被阻塞时就能获得此线程的运行权。但是,Swift并不能确定当异步函数恢复运行时其将在哪条线程上运行。
|
||||
|
||||
你当然也可以不用 Swift 原生支持去写并发的代码,只不过代码的可读性会下降。比如,下面的这段代码会拉取一系列图片名称的列表,下载列表中的图片然后展示给用户:
|
||||
|
||||
@ -64,19 +64,42 @@ show(photo)
|
||||
|
||||
* 异步函数,方法或变量内部的代码
|
||||
* 静态函数 `main()` 中被打上 `@main` 标记的结构体、类或者枚举中的代码
|
||||
* 游离的子任务中的代码,之后会在[非结构化并行](#Unstructured-Concurrency)中说明
|
||||
* 非结构化的子任务中的代码,之后会在 [非结构化并行](#Unstructured-Concurrency) 中说明
|
||||
|
||||
在可能的悬点之间的代码将按顺序运行,并不可能被其它并发代码中断。例如,以下代码将一张图片从一个图库移动到另一个图库:
|
||||
|
||||
```swift
|
||||
let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
|
||||
add(firstPhoto toGallery: "Road Trip")
|
||||
//此时,firstPhoto暂时地同时存在于两个画廊中
|
||||
remove(firstPhoto fromGallery: "Summer Vacation")
|
||||
```
|
||||
|
||||
其它代码不能在 `add(_:toGallery:)` 和 `remove(_:fromGallery:)` 两个方法之间运行。在此期间,第一张图片同时存在于两个图库,暂时打破了应用程序的一个不变量。为了更明确地表示这段代码不能加入 `await` 标记,你可以将这段代码重构为一个同步函数:
|
||||
|
||||
```swift
|
||||
func move(_photoName: String, from source: String, to destination: String) {
|
||||
add(photoName, to: destination)
|
||||
remove(photoName, from: source)
|
||||
}
|
||||
//...
|
||||
let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
|
||||
move(firstPhoto, from: "Summer Vacation", to: "Road Trip")
|
||||
```
|
||||
|
||||
在上例中,由于 `move(_:from:to:)` 函数为同步函数,你将能够保证它将不会包含潜在的悬点。在未来,试图在该函数中写入并发代码将引发编译错误而非产生bug。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 学习并行的过程中,[Task.sleep(_:)](https://developer.apple.com/documentation/swift/task/3814836-sleep) 方法非常有用。这个方法什么都没有做,只是等待不少于指定的时间(单位纳秒)后返回。下面是使用 `sleep()` 方法模拟网络请求实现 `listPhotos(inGallery:)` 的一个版本:
|
||||
> 学习并行的过程中,[Task.sleep(_:)](https://developer.apple.com/documentation/swift/task/3814836-sleep) 方法非常有用。这个方法什么都没有做,只是等待不少于指定的时间(单位纳秒)后返回。下面是使用 `sleep(until:clock:)` 方法模拟网络请求实现 `listPhotos(inGallery:)` 的一个版本:
|
||||
>
|
||||
|
||||
```Swift
|
||||
func listPhotos(inGallery name: String) async -> [String] {
|
||||
await Task.sleep(2 * 1_000_000_000) // 两秒
|
||||
return ["IMG001", "IMG99", "IMG0404"]
|
||||
```swift
|
||||
func listPhotos(inGallery name: String) async throws -> [String] {
|
||||
try await Task.sleep(until: .now + .seconds(2), clock: .continuous)
|
||||
return ["IMG001", "IMG99", "IMG0404"]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## 异步序列 {#Asynchronous-Sequences}
|
||||
|
||||
@ -142,7 +165,7 @@ show(photos)
|
||||
await withTaskGroup(of: Data.self) { taskGroup in
|
||||
let photoNames = await listPhotos(inGallery: "Summer Vacation")
|
||||
for name in photoNames {
|
||||
taskGroup.async { await downloadPhoto(named: name) }
|
||||
taskGroup.addTask { await downloadPhoto(named: name) }
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -151,15 +174,16 @@ await withTaskGroup(of: Data.self) { taskGroup in
|
||||
|
||||
### 非结构化并发 {#Unstructured-Concurrency}
|
||||
|
||||
对于并发来说,除了上一部分讲到的结构化的方式,Swift 还支持非结构化并发。与任务组中的任务不同的是,*非结构化任务(unstructured task)*并没有父任务。你能以任何方式来处理非结构化任务以满足你程序的需要,但与此同时,你需要对于他们的正确性付全责。如果想创建一个在当前 actor 上运行的非结构化任务,需要调用初始化方法 [Task.init(priority:operation:)](https://developer.apple.com/documentation/swift/task/3856790-init)。如果想要创建一个不在当前 actor 上运行的非结构化任务(更具体地说就是*游离任务(detached task)*),需要调用类方法 [Task.detached(priority:operation:)](https://developer.apple.com/documentation/swift/task/3856786-detached)。以上两种方法都能返回一个能让你与任务交互(继续等待结果或取消任务)的任务句柄,如下:
|
||||
对于并发来说,除了上一部分讲到的结构化的方式,Swift 还支持非结构化并发。与任务组中的任务不同的是,*非结构化任务(unstructured task)*并没有父任务。你能以任何方式来处理非结构化任务以满足你程序的需要,但与此同时,你需要对于他们的正确性付全责。如果想创建一个在当前 actor 上运行的非结构化任务,需要调用构造器 [Task.init(priority:operation:)](https://developer.apple.com/documentation/swift/task/3856790-init)。如果想要创建一个不在当前 actor 上运行的非结构化任务(更具体地说就是*游离任务(detached task)*),需要调用类方法 [Task.detached(priority:operation:)](https://developer.apple.com/documentation/swift/task/3856786-detached)。以上两种方法都能返回一个能让你与任务交互(继续等待结果或取消任务)的任务句柄,如下:
|
||||
|
||||
```Swift
|
||||
```swift
|
||||
let newPhoto = // ... 图片数据 ...
|
||||
let handle = Task {
|
||||
return await add(newPhoto, toGalleryNamed: "Spring Adventures")
|
||||
return await add(newPhoto, toGalleryNamed: "Spring Adventures")
|
||||
}
|
||||
let result = await handle.value
|
||||
```
|
||||
|
||||
如果你想更多的了解游离任务,可以参考 [Task](https://developer.apple.com/documentation/swift/task)。
|
||||
|
||||
### 任务取消 {#Task-Cancellation}
|
||||
@ -176,6 +200,8 @@ Swift 中的并发使用合作取消模型。每个任务都会在执行中合
|
||||
|
||||
## Actors {#Actors}
|
||||
|
||||
你可以使用任务来将自己的程序分割为孤立、并发的部分。任务间相互孤立,这也使得它们能够安全地同时运行。但有时你需要在任务间共享信息。Actors便能够帮助你安全地在并发代码间分享信息。
|
||||
|
||||
跟类一样,actor 也是一个引用类型,所以 [类是引用类型](https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html#ID89) 中关于值类型和引用类型的比较同样适用于 actor 和类。不同于类的是,actor 在同一时间只允许一个任务访问它的可变状态,这使得多个任务中的代码与一个 actor 交互时更加安全。比如,下面是一个记录温度的 actor:
|
||||
|
||||
```Swift
|
||||
@ -231,3 +257,42 @@ print(logger.max) // 报错
|
||||
```
|
||||
|
||||
不添加 `await` 关键字的情况下访问 `logger.max` 会失败,因为 actor 的属性是它隔离的本地状态的一部分。Swift 可以保证只有 actor 内部的代码可以访问 actor 的内部状态。这个保证也被称为 *actor isolation*。
|
||||
|
||||
## 可发送类型 {#Sendable-Types}
|
||||
|
||||
任务和Actor能够帮助你将程序分割为能够安全地并发运行的小块。在一个任务中,或是在一个Actor实例中,程序包含可变状态的部分(如变量和属性)被称为*并发域(Concurrency domain)*。部分类型的数据不能在并发域间共享,因为它们包含了可变状态,但它不能阻止重叠访问。
|
||||
|
||||
能够在并发域间共享的类型被称为*可发送类型(Sendable Type)*。例如在调用Actor方法时被作为实参传递,或是作为任务的结果返回。本章之前的例子并未讨论可发送性,因为这些例子均使用了简单值类型,对于在并发域间传递的数据而言,简单值类型总是安全的。而与之相反,另一些类型并不能安全地在并发域间传递。例如,当你在不同的任务间传递该类的实例时,包含可变属性且并未序列化对这些属性的访问的类可能产生不可预测和不正确的结果。
|
||||
|
||||
你可以通过声明其符合 `Sendable` 协议来将某个类型标记为可发送类型。该协议并不包含任何代码要求,但Swift对其做出了强制的语义要求。总之,有三种方法将一个类型声明为可发送类型:
|
||||
|
||||
- 该类型为值类型,且其可变状态由其它可发送数据构成——例如具有存储属性的结构体或是具有关联值的枚举。
|
||||
|
||||
- 该类型不包含任何可变状态,且其不可变状态由其它可发送数据构成——例如只包含只读属性的结构体或类
|
||||
|
||||
- 该类型包含能确保其可变状态安全的代码——例如标记了 `@MainActor` 的类或序列化了对特定线程/队列上其属性的访问的类。
|
||||
|
||||
如需了解Swift对Sendable协议的语义要求的详细信息,请访问 [Sendable](https://developer.apple.com/documentation/swift/sendable) 协议参考。
|
||||
|
||||
部分类型总是可发送类型,如只有可发送属性的结构体和只有可发送关联值的枚举。例如:
|
||||
|
||||
```swift
|
||||
struct TemperatureReading: Sendable {
|
||||
var measurement: Int
|
||||
}
|
||||
extension TemperatureLogger {
|
||||
func addReading(from reading: TemperatureReading) {
|
||||
measurements.append(reading.measurement)
|
||||
}
|
||||
}
|
||||
let logger = TemperatureLogger(label: "Tea kettle", measurement: 85)
|
||||
let reading = TemperatureReading(measurement: 45)
|
||||
await logger.addReading(from: reading)
|
||||
```
|
||||
由于 `TemperatureReading` 是只有可发送属性的结构体,且该结构体并未被标记为 `public` 或 `@usableFromInline`,因此它是隐式可发送的。下文给出了该结构体的一个符合 `Sendable` 协议的版本:
|
||||
|
||||
```swift
|
||||
struct TemperatureReading {
|
||||
var measurement: Int
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user