在 Swift 中,表达式有四种类型:前缀表达式、中缀表达式、主表达式和后缀表达式。表达式的求值会返回一个值、引发副作用,或者两者兼而有之。
前缀和中缀表达式允许您将运算符应用于较小的表达式。主表达式在概念上是最简单的表达式,它们提供了一种访问值的方法。后缀表达式与前缀和中缀表达式类似,允许您使用后缀构建更复杂的表达式,例如函数调用和成员访问。以下部分将详细介绍每种表达式。
表达式语法
表达式 → try 运算符 ? await 运算符 ? 前缀表达式中缀表达式 ?
前缀表达式
前缀表达式将可选的前缀运算符与表达式组合在一起。前缀运算符接受一个参数,即其后的表达式。
有关这些运算符的行为的信息,请参阅基本运算符和高级运算符 。
有关 Swift 标准库提供的运算符的信息,请参阅运算符声明 。
前缀表达式语法
前缀表达式 → 前缀运算符 ? 后缀表达式
前缀表达式 → in-out 表达式
输入输出表达式
输入输出表达式标记作为输入输出参数传递给函数调用表达式的变量。
&<#expression#>
有关输入输出参数的更多信息以及查看示例,请参阅输入输出参数 。
在需要指针的上下文中提供非指针参数时也会使用输入输出表达式,如隐式转换为指针类型中所述。
输入输出表达式的语法
输入输出表达式 → & 主表达式
Try 运算符
try 表达式由 try 运算符和后跟可以抛出错误的表达式组成。其形式如下:
try <#expression#>
try 表达式的值就是表达式的值。
可选 try 表达式由 try? 运算符和后跟可抛出错误的表达式组成。其形式如下:
try? <#expression#>
如果表达式没有抛出错误,则可选 try 表达式的值是一个包含表达式值的可选项。否则,可选 try 表达式的值为 nil 。
强制尝试表达式由 try! 运算符及其后的一个可以抛出错误的表达式组成。其形式如下:
try! <#expression#>
强制尝试表达式的值就是表达式本身的值。如果表达式抛出错误,则会产生运行时错误。
当中缀运算符左侧的表达式标有 try 、 try? 或 try! 时,该运算符将应用于整个中缀表达式。也就是说,你可以使用括号来明确说明运算符的应用范围。
// try applies to both function calls
sum = try someThrowingFunction() + anotherThrowingFunction()
// try applies to both function calls
sum = try (someThrowingFunction() + anotherThrowingFunction())
// Error: try applies only to the first function call
sum = (try someThrowingFunction()) + anotherThrowingFunction()
try 表达式不能出现在中缀运算符的右侧,除非中缀运算符是赋值运算符,或者 try 表达式括在括号中。
如果表达式同时包含 try 和 await 运算符,则 try 运算符必须首先出现。
有关更多信息以及如何使用 try 、 try? 和 try! 的示例,请参阅错误处理 。
try 表达式的语法
try 运算符 → try | try ? | try !
Await 运算符
await 表达式由 await 运算符和后跟使用异步操作结果的表达式组成。其形式如下:
await <#expression#>
await 表达式的值就是表达式的值。
标有 await 的表达式称为潜在暂停点 。异步函数的执行可以在每个标有 await 的表达式处暂停。此外,并发代码的执行永远不会在任何其他点暂停。这意味着潜在暂停点之间的代码可以安全地更新需要暂时打破不变量的状态,前提是它在下一个潜在暂停点之前完成更新。
await 表达式只能出现在异步上下文中,例如传递给 async(priority: operation:) 函数的尾部闭包。它不能出现在 defer 语句的主体内,也不能出现在同步函数类型的自动闭包中。
当中缀运算符左侧的表达式标有 await 运算符时,该运算符将应用于整个中缀表达式。也就是说,你可以使用括号来明确说明运算符的应用范围。
// await applies to both function calls
sum = await someAsyncFunction() + anotherAsyncFunction()
// await applies to both function calls
sum = await (someAsyncFunction() + anotherAsyncFunction())
// Error: await applies only to the first function call
sum = (await someAsyncFunction()) + anotherAsyncFunction()
await 表达式不能出现在中缀运算符的右侧,除非中缀运算符是赋值运算符,或者 await 表达式括在括号中。
如果表达式同时包含 await 和 try 运算符,则 try 运算符必须首先出现。
await 表达式的语法
await 操作符 → await
中缀表达式
中缀表达式将中缀二元运算符与其作为左右参数的表达式组合起来。其形式如下:
<#left-hand argument#> <#operator#> <#right-hand argument#>
有关这些运算符的行为的信息,请参阅基本运算符和高级运算符 。
有关 Swift 标准库提供的运算符的信息,请参阅运算符声明 。
笔记
在解析时,由中缀运算符组成的表达式表示为一个扁平列表。通过应用运算符优先级,该列表将转换为树。例如,表达式 2 + 3 * 5 最初被理解为一个包含五个项的扁平列表,即 2 、 + 、 3 、 * 和 5 。此过程将其转换为树 (2 + (3 * 5))。
中缀表达式语法
中缀表达式 → 中缀运算符前缀表达式
中缀表达式 → 赋值运算符 try 运算符 ? await 运算符 ? 前缀表达式
中缀表达式 → 条件运算符 try 运算符 ? await 运算符 ? 前缀表达式
中缀表达式 → 类型转换运算符
中缀表达式 → 中缀表达式中缀表达式 ?
赋值运算符
赋值运算符为给定的表达式设置一个新值。其形式如下:
<#expression#> = <#value#>
表达式的值被设置为通过计算值得到的值。如果表达式是一个元组,则值必须是具有相同数量元素的元组。(允许嵌套元组。)赋值操作是从值的每个部分到表达式的相应部分执行的。例如:
(a, _, (b, c)) = ("test", 9.45, (12, 3))
// a is "test", b is 12, c is 3, and 9.45 is ignored
赋值运算符不返回任何值。
赋值运算符的语法
赋值运算符 → =
三元条件运算符
三元条件运算符根据条件的值计算两个给定值之一。其形式如下:
<#condition#> ? <#expression used if true#> : <#expression used if false#>
如果条件计算结果为 true ,则条件运算符将计算第一个表达式并返回其值。否则,它将计算第二个表达式并返回其值。未使用的表达式不会被计算。
有关使用三元条件运算符的示例,请参阅三元条件运算符 。
条件运算符语法
条件运算符 → ? 表达式 :
类型转换运算符
有四种类型转换运算符: is 运算符、 as 运算符、 as? 运算符和 as! 运算符。
它们具有以下形式:
<#expression#> is <#type#>
<#expression#> as <#type#>
<#expression#> as? <#type#>
<#expression#> as! <#type#>
is 运算符在运行时检查表达式是否可以转换为指定的类型 。如果表达式可以转换为指定的类型 ,则返回 true ;否则,返回 false 。
如果在编译时确定强制类型转换总是成功,例如向上转型或桥接, as 运算符会执行强制类型转换。向上转型允许您将表达式用作其类型的超类型的实例,而无需使用中间变量。以下方法是等效的:
func f(_ any: Any) { print("Function for Any") }
func f(_ int: Int) { print("Function for Int") }
let x = 10
f(x)
// Prints "Function for Int"
let y: Any = x
f(y)
// Prints "Function for Any"
f(x as Any)
// Prints "Function for Any"
通过桥接,您可以将 Swift 标准库类型(例如 String 的表达式用作其对应的 Foundation 类型(例如 NSString ,而无需创建新的实例。有关桥接的更多信息,请参阅使用 Foundation 类型 。
as? 运算符将表达式条件转换为指定的类型 。as as? 运算符返回指定类型的可选项。在运行时,如果转换成功,则将表达式的值包装在可选项中并返回;否则,返回的值为 nil 。如果转换为指定类型必然失败或必然成功,则会引发编译时错误。
as! 运算符将表达式强制转换为指定的 type 。as as! 运算符返回指定 type 的值,而不是可选类型。如果转换失败,则会引发运行时错误。x x as! T 的行为与 (x as? T)! 的行为相同。
有关类型转换的更多信息以及使用类型转换运算符的示例,请参阅类型转换 。
类型转换运算符的语法
类型转换运算符 → is 类型
类型转换运算符 → as 类型
类型转换运算符 → as ? type
类型转换运算符 → as ! type
主表达式
基本表达式是最基本的表达式。它们可以单独用作表达式,也可以与其他标记组合,构成前缀表达式、中缀表达式和后缀表达式。
基本表达式的语法
主表达式 → 标识符泛型参数子句 ?
主表达式 → 文字表达式
主要表达 → 自我表达
主表达式 → 超类表达式
主表达式 → 条件表达式
初级表达式 → 闭包表达式
主表达式 → 括号表达式
主表达式 → 元组表达式
主表达式 → 隐式成员表达式
主表达式 → 通配符表达式
主表达式 → 宏扩展表达式
主表达式 → 键路径表达式
主表达式 → 选择器表达式
主表达式 → 键路径字符串表达式
文字表达
文字表达式由普通文字(例如字符串或数字)、数组或字典文字或游乐场文字组成。
笔记
在 Swift 5.9 之前,可以识别以下特殊文字:
#column
、#dsohandle
、#file ID
、#file Path
、#file
和#line
。现在,它们在 Swift 标准库中以宏的形式实现:column()
、dsohandle()
、file ID()
、file Path()
、file()
和line()
。
数组字面量是值的有序集合。其形式如下:
[<#value 1#>, <#value 2#>, <#...#>]
数组中的最后一个表达式后面可以跟一个可选的逗号。数组字面量的值的类型为 [T]
,其中 T 是其内部表达式的类型。如果表达式包含多个类型,则 T 是它们最接近的公共超类型。空数组字面量使用一对空的方括号书写,可用于创建指定类型的空数组。
var emptyArray: [Double] = []
字典字面量是键值对的无序集合。其形式如下:
[<#key 1#>: <#value 1#>, <#key 2#>: <#value 2#>, <#...#>]
字典中的最后一个表达式后面可以跟一个可选的逗号。字典字面值的类型为 [Key: Value]
,其中 Key 是其键表达式的类型, Value 是其值表达式的类型。如果有多个类型的表达式, Key 和 Value 是它们各自值最接近的公共超类型。空字典字面值写为括号 ( [:]
) 内的冒号,以将其与空数组字面值区分开来。您可以使用空字典字面值来创建指定键和值类型的空字典字面值。
var emptyDictionary: [String: Double] = [:]
Xcode 使用 Playground 字面量在程序编辑器中创建颜色、文件或图像的交互式表示。Xcode 之外的纯文本 Playground 字面量使用特殊的字面量语法表示。
有关在 Xcode 中使用游乐场文字的信息,请参阅 Xcode 帮助中的添加颜色、文件或图像文字 。
文字表达式的语法
文字表达式 → 文字
文字表达式 → 数组文字 | 字典文字 | 游乐场文字
数组文字 → [ 数组文字项 ? ]
数组文字项 → 数组文字项 , ? | 数组文字项 , 数组文字项
数组字面量项 → 表达式
字典文字 → [ 字典文字项 ] | [ : ]
字典文字项 → 字典文字项 , ? | 字典文字项 , 字典文字项
字典字面项 → 表达式 : 表达式
playground-literal → #color Literal ( red : 表达式 , green : 表达式 , blue : 表达式 , alpha : 表达式 )
playground-literal → #file Literal ( resource Name : 表达式 )
playground-literal → #image Literal ( resource Name : 表达式 )
自我表达
self 表达式是对当前类型或其所属类型的实例的显式引用。它具有以下形式:
self
self.<#member name#>
self[<#subscript index#>]
self(<#initializer arguments#>)
self.init(<#initializer arguments#>)
在初始化器、下标或实例方法中, self
引用它所在类型的当前实例。在类型方法中, self
引用它所在类型的当前类型。
self
表达式用于在访问成员时指定作用域,当作用域中存在另一个同名变量(例如函数参数)时,可以消除歧义。例如:
class SomeClass {
var greeting: String
init(greeting: String) {
self.greeting = greeting
}
}
在值类型的变异方法中,你可以将该值类型的新实例赋值给 self
。例如:
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
自我表达的语法
自我表达 → self | 自我方法表达 | 自我下标表达 | 自我初始化表达式
自我方法表达式 → self . 标识符
自身下标表达式 → self [ 函数调用参数列表 ]
自初始化 init . self
超类表达式
超类表达式允许一个类与其超类进行交互。它具有以下形式之一:
super.<#member name#>
super[<#subscript index#>]
super.init(<#initializer arguments#>)
第一种形式用于访问超类的成员。第二种形式用于访问超类的下标实现。第三种形式用于访问超类的初始化方法。
子类可以在其成员、下标和初始化器的实现中使用超类表达式,以利用其超类中的实现。
超类表达式的语法
超类表达式 → 超类方法表达式 | 超类下标表达式 | 超类初始化表达式
超类方法表达式 → super . 标识符
超类下标表达式 → super [ 函数调用参数列表 ]
超 . 初始化表达式 init super
条件表达式
条件表达式根据条件的值计算为多个给定值之一。它具有以下形式之一:
if <#condition 1#> {
<#expression used if condition 1 is true#>
} else if <#condition 2#> {
<#expression used if condition 2 is true#>
} else {
<#expression used if both conditions are false#>
}
switch <#expression#> {
case <#pattern 1#>:
<#expression 1#>
case <#pattern 2#> where <#condition#>:
<#expression 2#>
default:
<#expression 3#>
}
条件表达式具有与 if
语句或 switch
语句相同的行为和语法,除了以下段落描述的差异之外。
条件表达式仅出现在以下上下文中:
- 作为分配给变量的值。
- 作为变量或常量声明中的初始值。
- 作为
throw
表达式抛出的错误。 - 作为函数、闭包或属性获取器返回的值。
- 作为条件表达式分支内的值。
条件表达式的分支是详尽的,确保无论条件如何,表达式始终都会产生值。这意味着每个 if
分支都需要一个对应的 else
分支。
每个分支包含一个表达式,当该分支的条件为真时,该表达式用作条件表达式的值、一个 throw
语句或一个永不返回的函数调用。
每个分支必须生成相同类型的值。由于每个分支的类型检查是独立的,因此有时需要显式指定值的类型,例如当分支包含不同类型的字面量,或者分支的值为 nil
时。当需要提供此信息时,请为结果赋值的变量添加类型注释,或对分支的值进行 as
强制类型转换。
let number: Double = if someCondition { 10 } else { 12.34 }
let number = if someCondition { 10 as Double } else { 12.34 }
在结果构建器中,条件表达式只能作为变量或常量的初始值出现。这意味着,当您在结果构建器中(在变量或常量声明之外)编写 if
或 switch
时,该代码会被理解为分支语句,并且结果构建器的某个方法会转换该代码。
不要将条件表达式放在 try
表达式中,即使条件表达式的某个分支抛出了错误。
条件表达式的语法
条件表达式 → if 表达式 | switch 表达式
if 表达式 → if 条件列表 { 语句 } if 表达式尾
if 表达式尾部 → else if 表达式
if 表达式尾部 → else { 语句 }
switch 表达式 → switch 表达式 { switch 表达式情况 }
switch 表达式案例 → switch 表达式案例 switch 表达式案例 ?
switch 表达式 case → case 标签语句
switch 表达式 case → 默认标签语句
闭包表达式
闭包表达式创建一个闭包,在其他编程语言中也称为 lambda 表达式或匿名函数 。与函数声明类似,闭包包含语句,并从其封闭作用域中捕获常量和变量。其形式如下:
{ (<#parameters#>) -> <#return type#> in
<#statements#>
}
这些参数具有与函数声明中的参数相同的形式,如函数声明中所述。
在闭包表达式中写入 throws
或 async
明确将闭包标记为抛出或异步。
{ (<#parameters#>) async throws -> <#return type#> in
<#statements#>
}
如果闭包主体包含 throws
语句或 try
表达式,且该语句未嵌套在具有详尽错误处理的 do
语句中,则该闭包将被理解为抛出异常。如果抛出异常的闭包仅抛出单一类型的错误,则该闭包将被理解为抛出该类型的错误;否则,将被理解为抛出 any Error
。同样,如果闭包主体包含 await
表达式,则该闭包将被理解为异步的。
有几种特殊形式可以使闭包写得更简洁:
- 闭包可以省略其参数的类型、返回值的类型,或者两者皆省略。如果省略参数名称和返回值的类型,则在语句前省略
in
关键字。如果无法推断省略的类型,则会引发编译时错误。 - 闭包可以省略其参数的名称。此时,其参数将隐式地以
$
及其位置命名:$0
、$1
、$2
等等。 - 仅由单个表达式组成的闭包将被理解为返回该表达式的值。在对周围表达式进行类型推断时,也会考虑该表达式的内容。
以下闭包表达式是等效的:
myFunction { (x: Int, y: Int) -> Int in
return x + y
}
myFunction { x, y in
return x + y
}
myFunction { return $0 + $1 }
myFunction { $0 + $1 }
有关将闭包作为参数传递给函数的信息,请参阅函数调用表达式 。
闭包表达式可以不存储在变量或常量中,例如在函数调用中立即使用闭包时。上面代码中传递给 my Function
闭包表达式就是这种立即使用的示例。因此,闭包表达式是否逃逸取决于表达式周围的上下文。如果闭包表达式被立即调用或作为非逃逸函数参数传递,则闭包表达式是非逃逸的。否则,闭包表达式就是逃逸的。
有关逃逸闭包的更多信息,请参阅逃逸闭包 。
捕获列表
默认情况下,闭包表达式会从其周围作用域捕获常量和变量,并对这些值进行强引用。你可以使用捕获列表来显式控制闭包中值的捕获方式。
捕获列表写为以逗号分隔的表达式列表,并用方括号括起来,位于参数列表之前。如果使用捕获列表,则必须使用 in
关键字,即使省略了参数名称、参数类型和返回类型。
捕获列表中的条目在闭包创建时初始化。对于捕获列表中的每个条目,都会有一个常量被初始化为周围作用域中同名常量或变量的值。例如,在下面的代码中, a
包含在捕获列表中,但 b
不包含在内,这导致它们的行为有所不同。
var a = 0
var b = 0
let closure = { [a] in
print(a, b)
}
a = 10
b = 10
closure()
// Prints "0 10"
有两个名为 a
不同变量,一个是周围作用域中的变量,一个是闭包作用域中的常量,但只有一个名为 b
的变量。在创建闭包时,内部作用域中的 a
使用外部作用域中 a
的值进行初始化,但它们的值之间没有任何特殊联系。这意味着,外部作用域中 a
值的改变不会影响内部作用域中 a
的值,闭包内部 a
的改变也不会影响闭包外部 a
的值。相反,只有一个名为 b
的变量——外部作用域中的 b
因此闭包内部或外部的更改在两个地方都是可见的。
当捕获变量的类型具有引用语义时,这种区别是不可见的。例如,下面的代码中有两个名为 x
的对象,一个是外部作用域中的变量,一个是内部作用域中的常量,但由于引用语义,它们都指向同一个对象。
class SimpleClass {
var value: Int = 0
}
var x = SimpleClass()
var y = SimpleClass()
let closure = { [x] in
print(x.value, y.value)
}
x.value = 10
y.value = 10
closure()
// Prints "10 10"
如果表达式值的类型是类,则可以在捕获列表中用 weak
或 unowned
标记该表达式,以捕获对表达式值的弱引用或无主引用。
myFunction { print(self.title) } // implicit strong capture
myFunction { [self] in print(self.title) } // explicit strong capture
myFunction { [weak self] in print(self!.title) } // weak capture
myFunction { [unowned self] in print(self.title) } // unowned capture
您还可以将任意表达式绑定到捕获列表中的命名值。该表达式在闭包创建时进行求值,并以指定的强度捕获该值。例如:
// Weak capture of "self.parent" as "parent"
myFunction { [weak parent = self.parent] in print(parent!.title) }
有关闭包表达式的更多信息和示例,请参阅闭包表达式 。有关捕获列表的更多信息和示例,请参阅解决闭包的循环强引用 。
闭包表达式的语法
闭包表达式 → { 属性 ? 闭包签名 ? 语句 ? }
闭包签名 → 捕获列表 ? 闭包参数子句 async ? 抛出子句 ? 函数结果 ? in
闭包签名 → 捕获 in
闭包参数子句 → ( ) | ( 闭包参数列表 ) | 标识符列表
闭包参数列表 → 闭包参数 | 闭包参数 , 闭包参数列表
闭包参数 → 闭包参数名称类型注解 ?
闭包参数 → 闭包参数名称 类型注解 ...
闭包参数名称 → 标识符
捕获列表 → [ 捕获列表项 ]
捕获列表项 → 捕获列表项 | 捕获列表项 , 捕获列表项
捕获列表项 → 捕获说明符 ? 标识符
捕获列表项 → 捕获说明符 ? 标识符 = 表达式
捕获列表项 → 捕获说明符 ? 自我表达
捕获说明符 → weak | unowned | unowned(safe) | unowned(unsafe)
隐式成员表达式
隐式成员表达式是在类型推断可以确定隐含类型的上下文中访问类型成员(例如枚举成员或类型方法)的简化方法。它具有以下形式:
.<#member name#>
例如:
var x = MyEnumeration.someValue
x = .anotherValue
如果推断类型是可选的,则还可以在隐式成员表达式中使用非可选类型的成员。
var someOptional: MyEnumeration? = .someValue
隐式成员表达式后面可以跟后缀运算符或后缀表达式中列出的其他后缀语法。这称为链式隐式成员表达式 。虽然所有链式后缀表达式通常具有相同的类型,但唯一的要求是整个链式隐式成员表达式需要可转换为其上下文隐含的类型。具体来说,如果隐含类型是可选类型,则可以使用非可选类型的值;如果隐含类型是类类型,则可以使用其子类的值。例如:
class SomeClass {
static var shared = SomeClass()
static var sharedSubclass = SomeSubclass()
var a = AnotherClass()
}
class SomeSubclass: SomeClass { }
class AnotherClass {
static var s = SomeClass()
func f() -> SomeClass { return AnotherClass.s }
}
let x: SomeClass = .shared.a.f()
let y: SomeClass? = .shared
let z: SomeClass = .sharedSubclass
在上面的代码中, x
的类型与其上下文隐含的类型完全匹配, y
的类型可以从 Some Class
转换为 Some Class?
,而 z
的类型可以从 Some Subclass
转换为 Some Class
。
隐式成员表达式的语法
隐式成员表达式 → . 标识符
隐式成员表达式 → . 标识符 . 后缀表达式
括号表达式
括号表达式由一个表达式和两个括号组成。你可以使用括号通过显式分组表达式来指定运算的优先级。分组括号不会改变表达式的类型——例如, (1)
的类型就是 Int
。
括号表达式的语法
括号表达式 → ( 表达式 )
元组表达式
元组表达式由括号括起来的表达式列表组成,每个表达式之间用逗号分隔。每个表达式前面可以有一个可选的标识符,标识符之间用冒号 ( :
) 分隔。元组表达式的形式如下:
(<#identifier 1#>: <#expression 1#>, <#identifier 2#>: <#expression 2#>, <#...#>)
元组表达式中的每个标识符在其作用域内必须是唯一的。在嵌套元组表达式中,同一嵌套级别的标识符也必须是唯一的。例如, (a: 10, a: 20)
是无效的,因为标签 a
在同一层级出现了两次。然而, (a: 10, b: (a: 1, x: 2))
是有效的——尽管 a
出现了两次,但它在外层元组中出现了一次,在内层元组中也出现了一次。
元组表达式可以包含零个表达式,也可以包含两个或多个表达式。括号内的单个表达式称为带括号的表达式。
笔记
在 Swift 中,空元组表达式和空元组类型都写成
()
。由于Void
是()
的类型别名,因此你可以用它来表示空元组类型。但是,与所有类型别名一样,Void
始终是一个类型——你不能用它来表示空元组表达式。
元组表达式的语法
元组表达式 → ( ) | ( 元组元素 , 元组元素列表 )
元组元素列表 → 元组元素 | 元组元素 , 元组元素列表
元组元素 → 表达式 | 标识符 : 表达式
通配符表达式
通配符表达式用于在赋值时显式忽略某个值。例如,在下面的赋值语句中,10 被赋给 x
,而 20 被忽略:
(x, _) = (10, 20)
// x is 10, and 20 is ignored
通配符表达式的语法
通配符表达式 → _
宏扩展表达式
宏扩展表达式由一个宏名和括号中以逗号分隔的宏参数列表组成。宏在编译时被扩展。宏扩展表达式的形式如下:
<#macro name#>(<#macro argument 1#>, <#macro argument 2#>)
如果宏不接受任何参数,则宏扩展表达式将省略宏名称后的括号。
宏扩展表达式可以作为参数的默认值出现。当用作函数或方法参数的默认值时,宏的求值依据是调用点的源代码位置,而不是它们在函数定义中出现的位置。但是,当默认值是一个更大的表达式,除了其他代码外还包含宏时,这些宏的求值依据是它们在函数定义中出现的位置。
func f(a: Int = #line, b: Int = (#line), c: Int = 100 + #line) {
print(a, b, c)
}
f() // Prints "4 1 101"
在上面的函数中, a
的默认值是一个宏表达式,因此该宏的求值位置是调用 f(a: b: c:)
源代码位置。相反, b
和 c
的值是包含宏的表达式——这些表达式中的宏的求值位置是定义 f(a: b: c:)
的源代码位置。
当您使用宏作为默认值时,将对其进行类型检查而不扩展宏,以检查以下要求:
- 宏的访问级别与使用它的函数相同或限制更少。
- 该宏要么不接受任何参数,要么其参数是没有字符串插值的文字。
- 宏的返回类型与参数的类型匹配。
您可以使用宏表达式来调用独立宏。要调用附加宏,请使用属性中描述的自定义属性语法。独立宏和附加宏的扩展方式如下:
- Swift 解析源代码以生成抽象语法树 (AST)。
- 宏实现接收 AST 节点作为其输入并执行该宏所需的转换。
- 宏实现产生的转换后的 AST 节点被添加到原始 AST 中。
每个宏的展开都是独立且自足的。但是,为了优化性能,Swift 可能会启动一个实现该宏的外部进程,并重复使用该进程来展开多个宏。实现宏时,该代码不得依赖于之前已展开的宏,也不得依赖于任何其他外部状态(例如当前时间)。
对于嵌套宏和具有多个角色的附加宏,扩展过程会重复。嵌套宏扩展表达式从外向内扩展。例如,在下面的代码中, outer Macro(_:)
首先扩展,而未扩展的对 inner Macro(_:)
调用出现在 outer Macro(_:)
作为其输入接收的抽象语法树中。
#outerMacro(12, #innerMacro(34), "some text")
附加的宏具有多个角色,每个角色都会展开一次。每次展开都接收相同的原始 AST 作为输入。Swift 通过收集所有生成的 AST 节点并将它们放置在 AST 中的相应位置来形成整体展开。
有关 Swift 中宏的概述,请参阅宏 。
宏扩展表达式的语法
宏扩展表达式 → # 标识符泛型参数子句 ? 函数调用参数子句 ? 尾随闭包 ?
键路径表达式
键路径表达式指的是类型的属性或下标。你可以在动态编程任务中使用键路径表达式,例如键值观察。它们的形式如下:
\<#type name#>.<#path#>
类型名称是具体类型的名称,包括任何通用参数,例如 String
、 [Int]
或 Set<Int>
。
路径由属性名称、下标、可选链表达式和强制解包表达式组成。这些键路径组件可以根据需要以任意顺序重复多次。
在编译时,键路径表达式被 Key Path
类的实例替换。
要使用键路径访问值,请将键路径传递给 subscript(key Path:)
脚本,该脚本适用于所有类型。例如:
struct SomeStructure {
var someValue: Int
}
let s = SomeStructure(someValue: 12)
let pathToProperty = \SomeStructure.someValue
let value = s[keyPath: pathToProperty]
// value is 12
在类型推断能够确定隐含类型的上下文中,可以省略类型名称 。以下代码使用了 \.some Property
而不是 \Some Class .some Property
:
class SomeClass: NSObject {
@objc dynamic var someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
}
}
let c = SomeClass(someProperty: 10)
c.observe(\.someProperty) { object, change in
// ...
}
该路径可以引用 self
来创建身份键路径 ( \.self
)。身份键路径引用的是整个实例,因此你可以使用它来在单个步骤中访问和更改变量中存储的所有数据。例如:
var compoundValue = (a: 1, b: 2)
// Equivalent to compoundValue = (a: 10, b: 20)
compoundValue[keyPath: \.self] = (a: 10, b: 20)
该路径可以包含多个属性名称(以句点分隔),用于引用属性值的某个属性。此代码使用键路径表达式 \OuterStructure.outer.someValue
来访问 Outer Structure
类型的 outer
属性的 some Value
属性:
struct OuterStructure {
var outer: SomeStructure
init(someValue: Int) {
self.outer = SomeStructure(someValue: someValue)
}
}
let nested = OuterStructure(someValue: 24)
let nestedKeyPath = \OuterStructure.outer.someValue
let nestedValue = nested[keyPath: nestedKeyPath]
// nestedValue is 24
路径可以使用括号包含下标,只要下标的参数类型符合 Hashable
协议即可。此示例在键路径中使用下标来访问数组的第二个元素:
let greetings = ["hello", "hola", "bonjour", "안녕"]
let myGreeting = greetings[keyPath: \[String].[1]]
// myGreeting is 'hola'
下标中使用的值可以是命名值或字面量。键路径中的值使用值语义捕获。以下代码在键路径表达式和闭包中都使用变量 index
来访问 greetings
数组的第三个元素。当 index
被修改时,键路径表达式仍然引用第三个元素,而闭包使用新的索引。
var index = 2
let path = \[String].[index]
let fn: ([String]) -> String = { strings in strings[index] }
print(greetings[keyPath: path])
// Prints "bonjour"
print(fn(greetings))
// Prints "bonjour"
// Setting 'index' to a new value doesn't affect 'path'
index += 1
print(greetings[keyPath: path])
// Prints "bonjour"
// Because 'fn' closes over 'index', it uses the new value
print(fn(greetings))
// Prints "안녕"
路径可以使用可选链和强制解包。以下代码在键路径中使用可选链来访问可选字符串的属性:
let firstGreeting: String? = greetings.first
print(firstGreeting?.count as Any)
// Prints "Optional(5)"
// Do the same thing using a key path.
let count = greetings[keyPath: \[String].first?.count]
print(count as Any)
// Prints "Optional(5)"
您可以混合搭配键路径的各个组成部分,以访问深层嵌套在类型中的值。以下代码使用组合了这些组成部分的键路径表达式来访问数组字典的不同值和属性。
let interestingNumbers = ["prime": [2, 3, 5, 7, 11, 13, 17],
"triangular": [1, 3, 6, 10, 15, 21, 28],
"hexagonal": [1, 6, 15, 28, 45, 66, 91]]
print(interestingNumbers[keyPath: \[String: [Int]].["prime"]] as Any)
// Prints "Optional([2, 3, 5, 7, 11, 13, 17])"
print(interestingNumbers[keyPath: \[String: [Int]].["prime"]![0]])
// Prints "2"
print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count)
// Prints "7"
print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count.bitWidth)
// Prints "64"
你可以在通常提供函数或闭包的上下文中使用键路径表达式。具体来说,你可以使用根类型为 Some Type
且路径生成 Value
类型的值的键路径表达式,而不是使用类型为 (Some Type) -> Value
的函数或闭包。
struct Task {
var description: String
var completed: Bool
}
var toDoList = [
Task(description: "Practice ping-pong.", completed: false),
Task(description: "Buy a pirate costume.", completed: true),
Task(description: "Visit Boston in the Fall.", completed: false),
]
// Both approaches below are equivalent.
let descriptions = toDoList.filter(\.completed).map(\.description)
let descriptions2 = toDoList.filter { $0.completed }.map { $0.description }
键路径表达式的任何副作用仅在表达式求值时才会求值。例如,如果你在键路径表达式的下标内调用函数,则该函数仅在表达式求值时被调用一次,而不是每次使用键路径时都会被调用。
func makeIndex() -> Int {
print("Made an index")
return 0
}
// The line below calls makeIndex().
let taskKeyPath = \[Task][makeIndex()]
// Prints "Made an index"
// Using taskKeyPath doesn't call makeIndex() again.
let someTask = toDoList[keyPath: taskKeyPath]
有关在与 Objective-C API 交互的代码中使用键路径的更多信息,请参阅在 Swift 中使用 Objective-C 运行时功能 。有关键值编码和键值观察的更多信息,请参阅键值编码编程指南和键值观察编程指南 。
键路径表达式的语法
键路径表达式 → \ 类型 ? . 键路径组件
关键路径组件 → 关键路径组件 | 关键路径组件 . 关键路径组件
键路径组件 → 标识符键路径后缀 ? | 键路径后缀
键路径后缀 → 键路径后缀键路径后缀 ?
键路径后缀 → ? | ! | self | [ 函数调用参数列表 ]
选择器表达式
选择器表达式允许你访问用于引用 Objective-C 中的方法或属性的 getter 或 setter 的选择器。其形式如下:
#selector(<#method name#>)
#selector(getter: <#property name#>)
#selector(setter: <#property name#>)
方法名称和属性名称必须是对 Objective-C 运行时中可用的方法或属性的引用。选择器表达式的值是 Selector
类型的实例。例如:
class SomeClass: NSObject {
@objc let property: String
@objc(doSomethingWithInt:)
func doSomething(_ x: Int) { }
init(property: String) {
self.property = property
}
}
let selectorForMethod = #selector(SomeClass.doSomething(_:))
let selectorForPropertyGetter = #selector(getter: SomeClass.property)
为属性的 getter 创建选择器时, 属性名称可以是对变量或常量属性的引用。相反,为属性的 setter 创建选择器时, 属性名称只能是对变量属性的引用。
方法名称可以包含括号以便分组,也可以包含 as
运算符来区分名称相同但类型签名不同的方法。例如:
extension SomeClass {
@objc(doSomethingWithString:)
func doSomething(_ x: String) { }
}
let anotherSelector = #selector(SomeClass.doSomething(_:) as (SomeClass) -> (String) -> Void)
因为选择器是在编译时而不是运行时创建的,所以编译器可以检查方法或属性是否存在以及它们是否暴露给 Objective-C 运行时。
笔记
尽管方法名称和属性名称是表达式,但它们从未被评估。
有关在与 Objective-C API 交互的 Swift 代码中使用选择器的更多信息,请参阅在 Swift 中使用 Objective-C 运行时功能 。
选择器表达式的语法
选择器表达式 → #selector ( 表达式 )
选择器表达式 → #selector ( getter: 表达式 )
选择器表达式 → #selector ( setter: 表达式 )
键路径字符串表达式
键路径字符串表达式允许您访问用于引用 Objective-C 中属性的字符串,以便在键值编码和键值观察 API 中使用。其形式如下:
#keyPath(<#property name#>)
属性名称必须是对 Objective-C 运行时中可用属性的引用。在编译时,键路径字符串表达式会被替换为字符串字面量。例如:
class SomeClass: NSObject {
@objc var someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
}
}
let c = SomeClass(someProperty: 12)
let keyPath = #keyPath(SomeClass.someProperty)
if let value = c.value(forKey: keyPath) {
print(value)
}
// Prints "12"
当您在类中使用键路径字符串表达式时,您可以通过仅写属性名称(而不写类名)来引用该类的属性。
extension SomeClass {
func getSomeKeyPath() -> String {
return #keyPath(someProperty)
}
}
print(keyPath == c.getSomeKeyPath())
// Prints "true"
由于键路径字符串是在编译时而不是运行时创建的,因此编译器可以检查该属性是否存在以及该属性是否暴露给 Objective-C 运行时。
有关在与 Objective-C API 交互的 Swift 代码中使用键路径的更多信息,请参阅在 Swift 中使用 Objective-C 运行时功能 。有关键值编码和键值观察的更多信息,请参阅键值编码编程指南和键值观察编程指南 。
笔记
尽管属性名称是一个表达式,但它从未被评估过。
键路径字符串表达式的语法
键路径字符串表达式 → #key Path ( 表达式 )
后缀表达式
后缀表达式是通过在表达式上应用后缀运算符或其他后缀语法而形成的。从语法上讲,每个基本表达式也都是后缀表达式。
有关这些运算符的行为的信息,请参阅基本运算符和高级运算符 。
有关 Swift 标准库提供的运算符的信息,请参阅运算符声明 。
后缀表达式语法
后缀表达式 → 主表达式
后缀表达式 → 后缀表达式后缀运算符
后缀表达式 → 函数调用表达式
后缀表达式 → 初始化表达式
后缀表达式 → 显式成员表达式
后缀表达式 → 后缀自我表达
后缀表达式 → 下标表达式
后缀表达式 → 强制值表达式
后缀表达式 → 可选链接表达式
函数调用表达式
函数调用表达式由函数名和括号中以逗号分隔的函数参数列表组成。函数调用表达式的形式如下:
<#function name#>(<#argument value 1#>, <#argument value 2#>)
函数名可以是任何值为函数类型的表达式。
如果函数定义中包含了参数的名称,则函数调用时必须在参数值之前包含名称,并以冒号 :
分隔。这种函数调用表达式的形式如下:
<#function name#>(<#argument name 1#>: <#argument value 1#>, <#argument name 2#>: <#argument value 2#>)
函数调用表达式可以包含尾随闭包,其形式为闭包表达式,紧跟在右括号之后。尾随闭包被理解为函数的参数,添加在最后一个带括号的参数之后。第一个闭包表达式没有标签;任何其他闭包表达式前面都带有参数标签。以下示例展示了使用和不使用尾随闭包语法的函数调用的等效版本:
// someFunction takes an integer and a closure as its arguments
someFunction(x: x, f: { $0 == 13 })
someFunction(x: x) { $0 == 13 }
// anotherFunction takes an integer and two closures as its arguments
anotherFunction(x: x, f: { $0 == 13 }, g: { print(99) })
anotherFunction(x: x) { $0 == 13 } g: { print(99) }
如果尾随闭包是函数的唯一参数,则可以省略括号。
// someMethod takes a closure as its only argument
myData.someMethod() { $0 == 13 }
myData.someMethod { $0 == 13 }
为了将尾随闭包包含在参数中,编译器将按照如下方式从左到右检查函数的参数:
尾随闭包 | 范围 | 行动 |
---|---|---|
已标记 | 已标记 | 如果标签相同,则闭包与参数匹配;否则,跳过该参数。 |
已标记 | 未标记 | 该参数被跳过。 |
未标记 | 有标签或无标签 | 如果参数在结构上类似于函数类型(如下所定义),则闭包与参数匹配;否则,跳过该参数。 |
尾随闭包会作为其匹配的参数的实参传递。扫描过程中跳过的参数不会传递实参,例如,它们可以使用默认参数。找到匹配项后,扫描将继续进行下一个尾随闭包和下一个参数的扫描。匹配过程结束时,所有尾随闭包都必须有一个匹配项。
如果参数不是输入输出参数,则该参数在结构上类似于函数类型,并且该参数是以下之一:
- 类型为函数类型的参数,如
(Bool) -> Int
- 包装表达式的类型为函数类型的自动闭包参数,例如
@autoclosure () -> ((Bool) -> Int)
- 数组元素类型为函数类型的可变参数,如
((Bool) -> Int)...
- 类型被包装在一层或多层可选中的参数,例如
Optional<(Bool) -> Int>
- 其类型结合了这些允许类型的参数,例如
(Optional<(Bool) -> Int>)...
当尾随闭包与一个结构上类似于函数类型但实际上不是函数的参数匹配时,闭包会根据需要进行包装。例如,如果参数的类型是可选类型,则闭包会自动包装为 Optional
。
为了方便从 Swift 5.3 之前的版本(该版本从右到左执行匹配)迁移代码,编译器会同时检查从左到右和从右到左的顺序。如果扫描方向产生不同的结果,则使用旧的从右到左的顺序,并且编译器会生成警告。Swift 的未来版本将始终使用从左到右的顺序。
typealias Callback = (Int) -> Int
func someFunction(firstClosure: Callback? = nil,
secondClosure: Callback? = nil) {
let first = firstClosure?(10)
let second = secondClosure?(20)
print(first ?? "-", second ?? "-")
}
someFunction() // Prints "- -"
someFunction { return $0 + 100 } // Ambiguous
someFunction { return $0 } secondClosure: { return $0 } // Prints "10 20"
在上面的例子中,标记为"Ambiguous"的函数调用在 Swift 5.3 中打印出"- 120",并引发编译器警告。Swift 的未来版本将打印"110 -"。
类、结构体或枚举类型可以通过声明几种方法之一来为函数调用语法启用语法糖,如具有特殊名称的方法中所述。
隐式转换为指针类型
在函数调用表达式中,如果参数和形参具有不同的类型,则编译器将尝试通过应用以下列表中的隐式转换之一来使它们的类型匹配:
inout SomeType
可能成为UnsafePointer<SomeType>
或UnsafeMutablePointer<SomeType>
inout Array<SomeType>
可能变为UnsafePointer<SomeType>
或UnsafeMutablePointer<SomeType>
Array<SomeType>
可能变为UnsafePointer<SomeType>
String
可能成为UnsafePointer<CChar>
以下两个函数调用是等效的:
func unsafeFunction(pointer: UnsafePointer<Int>) {
// ...
}
var myNumber = 1234
unsafeFunction(pointer: &myNumber)
withUnsafePointer(to: myNumber) { unsafeFunction(pointer: $0) }
通过这些隐式转换创建的指针仅在函数调用期间有效。为避免未定义的行为,请确保代码在函数调用结束后永远不会保留指针。
笔记
当将数组隐式转换为非安全指针时,Swift 会根据需要转换或复制数组,以确保数组的存储空间是连续的。例如,您可以将此语法用于从
NSArray
子类桥接到Array
数组,该子类没有关于其存储空间的 API 约定。如果您需要保证数组的存储空间已经是连续的,以便隐式转换无需执行此操作,请使用ContiguousArray
而不是Array
。
使用 &
而不是像 withUnsafePointer(to:)
那样显式地使用函数,可以提高对低级 C 函数的调用可读性,尤其是在函数接受多个指针参数时。但是,从其他 Swift 代码调用函数时,请避免使用 &
而应显式地使用不安全的 API。
函数调用表达式的语法
函数调用表达式 → 后缀表达式函数调用参数子句
函数调用表达式 → 后缀表达式函数调用参数子句 ? 尾随闭包
函数调用参数子句 → ( ) | ( 函数调用参数列表 )
函数调用参数列表 → 函数调用参数 | 函数调用参数 , 函数调用参数列表
函数调用参数 → 表达式 | 标识符 : 表达式
函数调用参数 → 运算符 | 标识符 : 运算符
尾随闭包 → 闭包表达式标记尾随闭包 ?
带标签的尾随闭包 → 带标签的尾随闭包带标签的尾随闭包 ?
带标签的尾随闭包 → 标识符 : 闭包表达式
初始化表达式
初始化表达式提供对类型初始化器的访问。其形式如下:
<#expression#>.init(<#initializer arguments#>)
你可以在函数调用表达式中使用初始化表达式来初始化一个类型的新实例。你也可以使用初始化表达式来委托给超类的初始化器。
class SomeSubClass: SomeSuperClass {
override init() {
// subclass initialization goes here
super.init()
}
}
和函数一样,初始化器也可以用作值。例如:
// Type annotation is required because String has multiple initializers.
let initializer: (Int) -> String = String.init
let oneTwoThree = [1, 2, 3].map(initializer).reduce("", +)
print(oneTwoThree)
// Prints "123"
如果通过名称指定类型,则可以不使用初始化表达式来访问该类型的初始化器。在所有其他情况下,都必须使用初始化表达式。
let s1 = SomeType.init(data: 3) // Valid
let s2 = SomeType(data: 1) // Also valid
let s3 = type(of: someValue).init(data: 7) // Valid
let s4 = type(of: someValue)(data: 5) // Error
初始化表达式的语法
init 表达式 → 后缀表达式 .
初始化表达式 → 后缀表达式 . ( 参数 init )
显式成员表达式
显式成员表达式允许访问命名类型、元组或模块的成员。它由项与其成员标识符之间的句点 ( .
) 组成。
<#expression#>.<#member name#>
命名类型的成员的命名是该类型声明或扩展的一部分。例如:
class SomeClass {
var someProperty = 42
}
let c = SomeClass()
let y = c.someProperty // Member access
元组的成员使用整数隐式命名,按照它们出现的顺序排列,从零开始。例如:
var t = (10, 20, 30)
t.0 = t.1
// Now t is (20, 20, 30)
模块的成员访问该模块的顶级声明。
使用 dynamicMemberLookup
属性声明的类型包括在运行时查找的成员,如属性中所述。
为了区分仅参数名称不同的方法或初始化器,请将参数名称放在括号中,每个参数名称后跟一个冒号 :
。对于没有名称的参数,请用下划线 _
表示。为了区分重载方法,请使用类型注解。例如:
class SomeClass {
func someMethod(x: Int, y: Int) {}
func someMethod(x: Int, z: Int) {}
func overloadedMethod(x: Int, y: Int) {}
func overloadedMethod(x: Int, y: Bool) {}
}
let instance = SomeClass()
let a = instance.someMethod // Ambiguous
let b = instance.someMethod(x:y:) // Unambiguous
let d = instance.overloadedMethod // Ambiguous
let d = instance.overloadedMethod(x:y:) // Still ambiguous
let d: (Int, Bool) -> Void = instance.overloadedMethod(x:y:) // Unambiguous
如果句点出现在行首,它会被理解为显式成员表达式的一部分,而不是隐式成员表达式。例如,以下清单展示了拆分成多行的链式方法调用:
let x = [10, 3, 20, 15, 4]
.sorted()
.filter { $0 > 5 }
.map { $0 * 100 }
您可以将这种多行链式语法与编译器控制语句结合使用,以控制每个方法的调用时间。例如,以下代码在 iOS 上使用了一种不同的过滤规则:
let numbers = [10, 20, 33, 43, 50]
#if os(iOS)
.filter { $0 < 40 }
#else
.filter { $0 > 25 }
#endif
在 #if
、 #endif
和其他编译指令之间,条件编译块可以包含一个隐式成员表达式,后跟零个或多个后缀,以形成后缀表达式。它还可以包含另一个条件编译块,或者这些表达式和块的组合。
您可以在任何可以编写显式成员表达式的地方使用此语法,而不仅仅是在顶级代码中。
在条件编译块中, #if
编译指令的分支必须至少包含一个表达式。其他分支可以为空。
显式成员表达式的语法
显式成员表达式 → 后缀表达式 . 十进制数字
显式成员表达式 → 后缀表达式 . 标识符泛型参数子句 ?
显式成员表达式 → 后缀表达式 . 标识符 ( 参数名称 )
显式成员表达式 → 后缀表达式条件编译块
参数名称 → 参数名称参数名称 ?
参数名称 → 标识符 :
后缀自我表达
后缀 self 表达式由一个表达式或类型的名称,紧接着 .self
组成。它有以下形式:
<#expression#>.self
<#type#>.self
第一种形式计算表达式的值。例如, x.self
计算结果为 x
。
第二种形式求值为类型的值。使用此形式可以将类型作为值访问。例如,由于 SomeClass.self
求值为 SomeClass
类型本身,因此你可以将其传递给接受类型级参数的函数或方法。
后缀自我表达语法
后缀自我表达 → self 表达式 .
下标表达式
下标表达式使用相应下标声明的 getter 和 setter 提供下标访问。其形式如下:
<#expression#>[<#index expressions#>]
要计算下标表达式的值,需要调用该表达式类型的下标 getter,并将索引表达式作为下标参数传递。要设置其值,则以相同的方式调用下标 setter。
有关下标声明的信息,请参阅协议下标声明 。
下标表达式的语法
下标表达式 → 后缀表达式 [ 函数调用参数列表 ]
强制值表达式
强制值表达式会解包一个你确定不为 nil
可选值。其形式如下:
<#expression#>!
如果表达式的值不为 nil
,则解包可选值并返回其对应的非可选类型。否则,将引发运行时错误。
强制值表达式的解包值可以被修改,可以通过修改值本身或赋值给值的某个成员来实现。例如:
var x: Int? = 0
x! += 1
// x is now 1
var someDictionary = ["a": [1, 2, 3], "b": [10, 20]]
someDictionary["a"]![0] = 100
// someDictionary is now ["a": [100, 2, 3], "b": [10, 20]]
强制值表达式的语法
强制值表达式 → 后缀表达式 !
可选链式表达式
可选链表达式提供了一种在后缀表达式中使用可选值的简化语法。其形式如下:
<#expression#>?
后缀 ?
运算符从表达式中创建可选链表达式,而不改变表达式的值。
可选链表达式必须出现在后缀表达式中,它们会导致后缀表达式以特殊方式进行求值。如果可选链表达式的值为 nil
,则后缀表达式中的所有其他操作都将被忽略,整个后缀表达式的求值结果为 nil
。如果可选链表达式的值不为 nil
,则可选链表达式的值将被解包并用于求值后缀表达式的其余部分。无论哪种情况,后缀表达式的值仍然是可选类型。
如果包含可选链表达式的后缀表达式嵌套在其他后缀表达式中,则只有最外层的表达式会返回可选类型。在下面的示例中,当 c
不为 nil
时,其值会被解包并用于求值 .property
,而 .property
的值又会用于求值 .performAction()
。整个表达式 c?.property.performAction()
的值都是可选类型。
var c: SomeClass?
var result: Bool? = c?.property.performAction()
下面的示例展示了不使用可选链的上述示例的行为。
var result: Bool?
if let unwrappedC = c {
result = unwrappedC.property.performAction()
}
可选链表达式的解包值可以被修改,可以通过修改值本身或赋值给值的某个成员来实现。如果可选链表达式的值为 nil
,则赋值运算符右侧的表达式不会被求值。例如:
func someFunctionWithSideEffects() -> Int {
return 42 // No actual side effects.
}
var someDictionary = ["a": [1, 2, 3], "b": [10, 20]]
someDictionary["not here"]?[0] = someFunctionWithSideEffects()
// someFunctionWithSideEffects isn't evaluated
// someDictionary is still ["a": [1, 2, 3], "b": [10, 20]]
someDictionary["a"]?[0] = someFunctionWithSideEffects()
// someFunctionWithSideEffects is evaluated and returns 42
// someDictionary is now ["a": [42, 2, 3], "b": [10, 20]]
可选链式表达式的语法
可选链式表达式 → 后缀表达式 ?