3.5 语句

原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/statements/

3.5 语句

把表达式组织起来并控制执行流程。

在 Swift 中,语句有三类:简单语句、编译器控制语句和控制流语句。简单语句最常见,由一个表达式或一个声明构成。编译器控制语句让程序能够改变编译器行为的某些方面,包括条件编译块和行控制语句。

控制流语句用来控制程序的执行流程。Swift 中有好几种控制流语句,包括循环语句、分支语句和控制转移语句。循环语句让一段代码可以被重复执行;分支语句让某段代码只在满足特定条件时才执行;控制转移语句则提供了改变代码执行顺序的方式。此外,Swift 还提供了 do 语句来引入作用域并捕获处理错误,以及 defer 语句用于在当前作用域退出之前执行清理动作。

分号(;)可以出现在任何语句之后,用于在同一行中出现多条语句时把它们分隔开。

Grammar of a statement:

statement → expression ;?
statement → declaration ;?
statement → loop-statement ;?
statement → branch-statement ;?
statement → labeled-statement ;?
statement → control-transfer-statement ;?
statement → defer-statement ;?
statement → do-statement ;?
statement → compiler-control-statement
statements → statement statements?

循环语句

循环语句让一段代码根据循环中指定的条件被重复执行。Swift 有三种循环语句:for-in 语句、while 语句和 repeat-while 语句。

循环语句中的控制流可以由 break 语句和 continue 语句改变,详见下文的 break 语句和 continue 语句。

Grammar of a loop statement:

loop-statement → for-in-statement
loop-statement → while-statement
loop-statement → repeat-while-statement

for-in 语句

for-in 语句让一段代码对遵循 Sequence 协议的集合(或任何类型)中的每个元素执行一次。

for-in 语句的形式如下:

1
2
3
for <#item#> in <#collection#> {
   <#statements#>
}

程序会对集合表达式调用 makeIterator() 方法,得到一个迭代器类型的值——也就是遵循 IteratorProtocol 协议的类型。程序通过在迭代器上调用 next() 方法开始执行循环:如果返回的值不是 nil,就把它赋给元素模式,执行语句,然后回到循环开头继续执行;否则程序不执行赋值、也不执行语句,for-in 语句的执行到此结束。

Grammar of a for-in statement:

for-in-statement → for case? pattern in expression where-clause? code-block

while 语句

只要条件保持为真,while 语句就让一段代码被重复执行。

while 语句的形式如下:

1
2
3
while <#condition#> {
   <#statements#>
}

while 语句的执行过程如下:

  1. 求值条件。

    如果为 true,继续执行第 2 步;如果为 false,while 语句的执行就此结束。

  2. 程序执行语句,然后回到第 1 步。

由于条件的值在语句执行之前求值,while 语句中的语句可能执行零次或多次。

条件的值必须是 Bool 类型或桥接到 Bool 的类型。条件也可以是一个可选绑定声明,详见可选绑定。

Grammar of a while statement:

while-statement → while condition-list code-block

condition-list → condition | condition , condition-list
condition → expression | availability-condition | case-condition | optional-binding-condition

case-condition → case pattern initializer
optional-binding-condition → let pattern initializer? | var pattern initializer?

repeat-while 语句

只要条件保持为真,repeat-while 语句就让一段代码执行一次或多次。

repeat-while 语句的形式如下:

1
2
3
repeat {
   <#statements#>
} while <#condition#>

repeat-while 语句的执行过程如下:

  1. 程序执行语句,然后继续执行第 2 步。

  2. 求值条件。

    如果为 true,回到第 1 步;如果为 false,repeat-while 语句的执行就此结束。

由于条件的值在语句执行之后求值,repeat-while 语句中的语句至少会执行一次。

条件的值必须是 Bool 类型或桥接到 Bool 的类型。

Grammar of a repeat-while statement:

repeat-while-statement → repeat code-block while expression

分支语句

分支语句让程序根据一个或多个条件的取值执行代码的某些部分:分支语句中指定条件的值控制程序如何分支,从而决定执行哪块代码。Swift 有三种分支语句:if 语句、guard 语句和 switch 语句。

if 语句或 switch 语句中的控制流可以由 break 语句改变,详见下文 break 语句。

Grammar of a branch statement:

branch-statement → if-statement
branch-statement → guard-statement
branch-statement → switch-statement

if 语句

if 语句用于根据一个或多个条件的求值结果执行代码。

if 语句有两种基本形式,两种形式都要求写出开、闭花括号。

第一种形式只在一个条件为真时执行代码,其形式如下:

1
2
3
if <#condition#> {
   <#statements#>
}

if 语句的第二种形式提供一个额外的else 子句(由 else 关键字引入),用于在条件为真时执行一部分代码、在同一个条件为假时执行另一部分代码。只有一个 else 子句时,if 语句的形式如下:

1
2
3
4
5
if <#condition#> {
   <#statements to execute if condition is true#>
} else {
   <#statements to execute if condition is false#>
}

if 语句的 else 子句可以包含另一个 if 语句,从而测试多个条件。以这种方式串联起来的 if 语句形式如下:

1
2
3
4
5
6
7
if <#condition 1#> {
   <#statements to execute if condition 1 is true#>
} else if <#condition 2#> {
   <#statements to execute if condition 2 is true#>
} else {
   <#statements to execute if both conditions are false#>
}

if 语句中任何条件的值都必须是 Bool 类型或桥接到 Bool 的类型。条件也可以是一个可选绑定声明,详见可选绑定。

Grammar of an if statement:

if-statement → if condition-list code-block else-clause?
else-clause → else code-block | else if-statement

guard 语句

如果一个或多个条件不满足,guard 语句用于把程序控制权转移出当前作用域。

guard 语句的形式如下:

1
2
3
guard <#condition#> else {
   <#statements#>
}

guard 语句中任何条件的值都必须是 Bool 类型或桥接到 Bool 的类型。条件也可以是一个可选绑定声明,详见可选绑定。

在 guard 语句条件中通过可选绑定声明赋值的任何常量或变量,都可以在 guard 语句所在作用域的其余部分中使用。

guard 语句的 else 子句是必需的,而且必须调用返回类型为 Never 的函数,或者用下列语句之一把程序控制权转移出 guard 语句所在的作用域:

  • return
  • break
  • continue
  • throw

控制转移语句在下文控制转移语句中讨论。关于返回类型为 Never 的函数的更多内容,参见永不返回的函数。

Grammar of a guard statement:

guard-statement → guard condition-list else code-block

switch 语句

switch 语句根据控制表达式的值执行代码的某些块。

switch 语句的形式如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
switch <#control expression#> {
case <#pattern 1#>:
    <#statements#>
case <#pattern 2#> where <#condition#>:
    <#statements#>
case <#pattern 3#> where <#condition#>,
    <#pattern 4#> where <#condition#>:
    <#statements#>
default:
    <#statements#>
}

先对 switch 语句的控制表达式求值,再与各个 case 中指定的模式比较。如果找到匹配,程序就执行该 case 作用域内列出的语句。每个 case 的作用域不能为空,因此每个 case 标签的冒号(:)之后必须至少包含一条语句;如果你不打算在匹配到的 case 中执行任何代码,就写一条 break 语句。

代码可以用来分支的表达式取值非常灵活:除了整数、字符这类标量类型的值之外,代码还可以根据任意类型的值分支,包括浮点数、字符串、元组、自定义类的实例和可选值。控制表达式的值甚至可以与某个枚举成员匹配,并检查它是否落在指定的取值区间内。关于在 switch 语句中使用这些不同类型值的示例,参见控制流中的 switch。

switch 的每个 case 可以在每个模式之后包含一个可选的 where 子句。where 子句由 where 关键字后跟一个表达式引入,用于在 case 中的模式被认为与控制表达式匹配之前提供附加条件。如果存在 where 子句,那么只有当控制表达式的值与该 case 的某个模式匹配、并且 where 子句的表达式求值为 true 时,相应 case 中的语句才会执行。例如,在下面的例子里,只有当控制表达式是一个包含两个相同值的元组(例如 (1, 1))时,才会匹配该 case。

1
case let (x, y) where x == y:

如上面的例子所示,case 中的模式也可以用 let 关键字绑定常量(也可以用 var 关键字绑定变量)。这些常量(或变量)随后可以在对应的 where 子句中、以及该 case 作用域内其余代码中被引用。如果该 case 包含多个匹配控制表达式的模式,那么所有模式必须包含相同的常量或变量绑定,而且每个被绑定的变量或常量在该 case 的所有模式中必须具有相同的类型。

switch 语句还可以包含由 default 关键字引入的 default case。只有在没有其他 case 匹配控制表达式时,才会执行 default case 中的代码。一个 switch 语句只能包含一个 default case,而且它必须出现在 switch 语句的末尾。

尽管模式匹配操作的实际执行顺序(特别是各个 case 中模式的求值顺序)并未规定,但 switch 语句中的模式匹配表现得就像按源代码顺序求值——也就是按它们在源代码中出现的顺序。因此,如果多个 case 包含求值结果相同的模式、从而都能匹配控制表达式的值,程序只会执行源代码顺序中第一个匹配 case 中的代码。

switch 语句必须穷尽

在 Swift 中,控制表达式类型的每一个可能取值都必须与至少一个 case 的某个模式匹配。当这确实不可行时(例如控制表达式的类型是 Int),你可以添加一个 default case 来满足这一要求。

对未来的枚举成员做 switch

非冻结枚举是一种特殊的枚举,它将来可能新增枚举成员——甚至在你编译并发布应用之后。对非冻结枚举做 switch 需要额外考虑:当库的作者把某个枚举标记为非冻结时,他们就保留了新增枚举成员的权利,而与该枚举交互的任何代码必须能够处理这些未来的成员,而无需重新编译。以库演进模式编译的代码、Swift 标准库中的代码、Apple 框架的 Swift 覆盖层,以及 C 和 Objective-C 代码,都可以声明非冻结枚举。关于冻结与非冻结枚举的内容,参见 frozen。

对非冻结枚举值做 switch 时,你总需要包含一个 default case,即使该枚举的每个成员都已经有对应的 switch case 也是如此。你可以给 default case 加上 @unknown 特性,表示该 default case 只应匹配将来新增的枚举成员。如果这个 default case 匹配到了编译期已知的枚举成员,Swift 会产生一条警告——这条面向未来的警告告诉你:库作者给该枚举新增了一个成员,而它没有对应的 switch case。

下面的例子对 Swift 标准库 Mirror.AncestorRepresentation 枚举现有三个成员做 switch。如果将来新增了成员,编译器会生成警告,提示你需要更新该 switch 语句以考虑新成员。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let representation: Mirror.AncestorRepresentation = .generated
switch representation {
case .customized:
    print("Use the nearest ancestor’s implementation.")
case .generated:
    print("Generate a default mirror for all ancestor classes.")
case .suppressed:
    print("Suppress the representation of all ancestor classes.")
@unknown default:
    print("Use a representation that was unknown when this code was compiled.")
}
// 输出 "Generate a default mirror for all ancestor classes."。

执行不会隐式贯穿 case

匹配到的 case 中的代码执行完毕之后,程序就从 switch 语句中退出,不会继续执行、也不会"贯穿"到下一个 case 或 default case。话虽如此,如果你希望执行从一个 case 继续到下一个 case,就要在你希望继续执行的那个 case 中显式写出 fallthrough 语句——它只由 fallthrough 关键字构成。关于 fallthrough 语句的更多内容,参见下文贯穿语句。

Grammar of a switch statement:

switch-statement → switch expression { switch-cases? }
switch-cases → switch-case switch-cases?
switch-case → case-label statements
switch-case → default-label statements
switch-case → conditional-switch-case

case-label → attributes? case case-item-list :
case-item-list → pattern where-clause? | pattern where-clause? , case-item-list
default-label → attributes? default :

where-clause → where where-expression
where-expression → expression

conditional-switch-case → switch-if-directive-clause switch-elseif-directive-clauses? switch-else-directive-clause? endif-directive
switch-if-directive-clause → if-directive compilation-condition switch-cases?
switch-elseif-directive-clauses → elseif-directive-clause switch-elseif-directive-clauses?
switch-elseif-directive-clause → elseif-directive compilation-condition switch-cases?
switch-else-directive-clause → else-directive switch-cases?

带标签的语句

你可以在循环语句、if 语句、switch 语句或 do 语句前面加上语句标签,它由标签名紧跟一个冒号(:)构成。把语句标签与 break、continue 语句一起使用,可以明确表示你想如何改变循环语句或 switch 语句中的控制流,详见下文 break 语句和 continue 语句。

带标签语句的作用域是语句标签之后的整个语句。你可以嵌套带标签语句,但每个语句标签的名字必须唯一。

更多内容以及使用语句标签的示例,参见控制流中的带标签的语句。

Grammar of a labeled statement:

labeled-statement → statement-label loop-statement
labeled-statement → statement-label if-statement
labeled-statement → statement-label switch-statement
labeled-statement → statement-label do-statement

statement-label → label-name :
label-name → identifier

控制转移语句

控制转移语句通过把程序控制权从一段代码无条件地转移到另一段代码,改变程序中代码的执行顺序。Swift 有五种控制转移语句:break 语句、continue 语句、fallthrough 语句、return 语句和 throw 语句。

Grammar of a control transfer statement:

control-transfer-statement → break-statement
control-transfer-statement → continue-statement
control-transfer-statement → fallthrough-statement
control-transfer-statement → return-statement
control-transfer-statement → throw-statement

break 语句

break 语句结束循环、if 语句或 switch 语句的程序执行。break 语句可以只由 break 关键字构成,也可以由 break 关键字后跟语句标签名构成,如下所示。

1
2
break
break <#label name#>

当 break 语句后跟语句标签名时,它结束该标签所指的循环、if 语句或 switch 语句的程序执行。

当 break 语句后面没有语句标签名时,它结束它所在的 switch 语句或最内层循环语句的程序执行。你不能用不带标签的 break 语句跳出 if 语句。

两种情况下,程序控制权随后都会被转移到外层循环或 switch 语句之后的第一行代码(如果有的话)。

关于使用 break 语句的示例,参见控制流中的 break和带标签的语句。

Grammar of a break statement:

break-statement → break label-name?

continue 语句

continue 语句结束循环语句当前这一次迭代的程序执行,但并不停止该循环语句的执行。continue 语句可以只由 continue 关键字构成,也可以由 continue 关键字后跟语句标签名构成,如下所示。

1
2
continue
continue <#label name#>

当 continue 语句后跟语句标签名时,它结束该标签所指循环语句当前这一次迭代的程序执行。

当 continue 语句后面没有语句标签名时,它结束它所在的最内层循环语句当前这一次迭代的程序执行。

两种情况下,程序控制权随后都会被转移到外层循环语句的条件处。

在 for 语句中,continue 语句执行之后仍会对递增表达式求值,因为递增表达式是在循环体执行之后才求值的。

关于使用 continue 语句的示例,参见控制流中的 continue和带标签的语句。

Grammar of a continue statement:

continue-statement → continue label-name?

贯穿语句

fallthrough 语句由 fallthrough 关键字构成,只出现在 switch 语句的 case 块中。fallthrough 语句让程序执行从 switch 语句中的一个 case 继续到下一个 case,即使该 case 标签的模式与 switch 语句控制表达式的值不匹配,程序执行也会继续到下一个 case。

fallthrough 语句可以出现在 switch 语句中的任何位置,而不只是 case 块的最后一条语句,但它不能用在最后一个 case 块中,也不能把控制权转移到一个模式包含值绑定模式的 case 块中。

关于在 switch 语句中使用 fallthrough 语句的示例,参见控制流中的控制转移语句。

Grammar of a fallthrough statement:

fallthrough-statement → fallthrough

return 语句

return 语句出现在函数或方法定义的语句体中,使程序执行返回到调用该函数或方法的地方,并从该函数或方法调用之后的位置继续执行。

return 语句可以只由 return 关键字构成,也可以由 return 关键字后跟一个表达式构成,如下所示。

1
2
return
return <#expression#>

当 return 语句后跟表达式时,该表达式的值被返回给调用该函数或方法的地方。如果表达式的值与函数或方法声明中所声明的返回类型不一致,表达式的值会先被转换成返回类型,再返回给调用方。

注意:如可失败构造器所述, return 语句有一种特殊形式(return nil), 可以用在可失败构造器中表示初始化失败。

当 return 语句后面没有表达式时,它只能用于从不返回值的函数或方法中返回(也就是说,该函数或方法的返回类型是 Void 或 ())。

Grammar of a return statement:

return-statement → return expression?

throw 语句

throw 语句出现在抛出错误的函数或方法体中,或者出现在类型标记了 throws 关键字的闭包表达式体中。

throw 语句使程序结束当前作用域的执行,并开始向包含它的作用域传播错误。抛出的错误会一直传播,直到被某个 do 语句的 catch 子句处理。

throw 语句由 throw 关键字后跟一个表达式构成,如下所示。

1
throw <#expression#>

表达式的值必须具有遵循 Error 协议的类型。如果包含该 throw 语句的 do 语句或函数声明了它所抛出的错误类型,那么表达式的值必须是该类型的实例。

关于使用 throw 语句的示例,参见错误处理中的用抛出错误的函数传播错误。

Grammar of a throw statement:

throw-statement → throw expression

defer 语句

defer 语句用于在把程序控制权转移出该 defer 语句所在的作用域之前执行代码。

defer 语句的形式如下:

1
2
3
defer {
    <#statements#>
}

无论程序控制权如何转移,defer 语句中的语句都会执行。这意味着,例如你可以用 defer 语句执行手动资源管理(如关闭文件描述符),以及执行即使抛出错误也必须要做的操作。

defer 语句中的语句在包含该 defer 语句的作用域末尾执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func f(x: Int) {
  defer { print("First defer") }

  if x < 10 {
    defer { print("Second defer") }
    print("End of if")
  }

  print("End of function")
}
f(x: 5)
// 输出 "End of if"。
// 输出 "Second defer"。
// 输出 "End of function"。
// 输出 "First defer"。

在上面的代码中,if 语句中的 defer 先于函数 f 中声明的 defer 执行,因为 if 语句的作用域先于函数作用域结束。

如果同一个作用域中出现多个 defer 语句,它们的执行顺序与出现顺序相反:先执行某个作用域中最后一条 defer 语句,意味着该 defer 语句中的语句可以引用将由其他 defer 语句清理的资源。

1
2
3
4
5
6
7
8
9
func f() {
    defer { print("First defer") }
    defer { print("Second defer") }
    print("End of function")
}
f()
// 输出 "End of function"。
// 输出 "Second defer"。
// 输出 "First defer"。

defer 语句中的语句不能把程序控制权转移出 defer 语句之外。

Grammar of a defer statement:

defer-statement → defer code-block

do 语句

do 语句用来引入新的作用域,并且可以选择性地包含一个或多个 catch 子句,其中包含与已定义的错误状况相匹配的模式。在 do 语句作用域中声明的变量和常量只能在该作用域内访问。

Swift 中的 do 语句类似于 C 语言中用于界定代码块的花括号({}),而且不会在运行时带来性能开销。

do 语句的形式如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
do {
    try <#expression#>
    <#statements#>
} catch <#pattern 1#> {
    <#statements#>
} catch <#pattern 2#> where <#condition#> {
    <#statements#>
} catch <#pattern 3#>, <#pattern 4#> where <#condition#> {
    <#statements#>
} catch {
    <#statements#>
}

do 语句可以选择性地指定它抛出的错误类型,形式如下:

1
2
3
4
5
6
7
do throws(<#type#>) {
    try <#expression#>
} catch <#pattern> {
    <#statements#>
} catch {
    <#statements#>
}

如果 do 语句包含 throws 子句,那么 do 块只能抛出指定类型的错误。该类型必须是遵循 Error 协议的具象类型、遵循 Error 协议的不透明类型,或者装箱协议类型 any Error。如果 do 语句没有指定它所抛出的错误类型,Swift 按下列方式推断错误类型:

  • 如果 do 代码块中的每一条 throws 语句和 try 表达式都嵌套在穷尽的错误处理机制内,Swift 就推断该 do 语句不抛出错误。

  • 如果 do 代码块在穷尽的错误处理之外包含只抛出单一类型错误的代码(而非抛出 Never),Swift 就推断该 do 语句抛出该具体错误类型。

  • 如果 do 代码块在穷尽的错误处理之外包含抛出多于单一类型错误的代码,Swift 就推断该 do 语句抛出 any Error。

关于处理具有显式类型的错误的更多内容,参见指定错误类型。

如果 do 代码块中的任何语句抛出错误,程序控制权就会被转移到模式与该错误匹配的第一个 catch 子句。如果没有任何子句匹配,错误就传播到外围作用域;如果错误在顶层仍未被处理,程序执行会以运行时错误停止。

与 switch 语句一样,编译器会尝试推断 catch 子句是否穷尽。如果能够做出这种判断,就认为该错误已被处理;否则错误可以传播出所在作用域,这意味着它必须由外层的 catch 子句处理,或者包含它的函数必须用 throws 声明。

包含多个模式的 catch 子句,只要其中任何一个模式匹配该错误,就算匹配。如果某个 catch 子句包含多个模式,那么所有模式必须包含相同的常量或变量绑定,而且每个被绑定的变量或常量在该 catch 子句的所有模式中必须具有相同的类型。

为了确保错误一定被处理,请使用一个能匹配所有错误的模式的 catch 子句,例如通配符模式(_)。如果 catch 子句没有指定模式,它就会匹配任何错误,并把错误绑定到名为 error 的局部常量。关于 catch 子句中可用模式的更多内容,参见模式。

关于在 do 语句中使用多个 catch 子句的示例,参见处理错误。

Grammar of a do statement:

do-statement → do throws-clause? code-block catch-clauses?
catch-clauses → catch-clause catch-clauses?
catch-clause → catch catch-pattern-list? code-block
catch-pattern-list → catch-pattern | catch-pattern , catch-pattern-list
catch-pattern → pattern where-clause?

编译器控制语句

编译器控制语句让程序能够改变编译器行为的某些方面。Swift 有三种编译器控制语句:条件编译块、行控制语句和编译期诊断语句。

Grammar of a compiler control statement:

compiler-control-statement → conditional-compilation-block
compiler-control-statement → line-control-statement
compiler-control-statement → diagnostic-statement

条件编译块

条件编译块让代码可以根据一个或多个编译条件的取值被有条件地编译。

每个条件编译块都以 #if 编译指令开始,以 #endif 编译指令结束。简单的条件编译块形式如下:

1
2
3
#if <#compilation condition#>
    <#statements#>
#endif

与 if 语句的条件不同,编译条件在编译期求值。因此,只有当编译条件在编译期求值为 true 时,语句才会被编译和执行。

编译条件可以包含 true 和 false 布尔字面量、与 -D 命令行标志一起使用的标识符,或者下表列出的任何平台条件。

平台条件合法实参
os()macOS、iOS、watchOS、tvOS、visionOS、Linux、Windows
arch()arm、arm64、i386、wasm32、x86_64、
swift()>= 或 < 后跟版本号
compiler()>= 或 < 后跟版本号
canImport()模块名
targetEnvironment()simulator、macCatalyst

swift() 和 compiler() 平台条件的版本号由主版本号、可选的次版本号、可选的补丁版本号等构成,版本号各部分之间用点(.)分隔,比较运算符与版本号之间不能有空白。compiler() 的版本是编译器版本,与传给编译器的 Swift 版本设置无关;swift() 的版本是当前正在编译的语言版本。例如,如果你用 Swift 5 编译器以 Swift 4.2 模式编译代码,那么编译器版本是 5,语言版本是 4.2。在这种设置下,下面的代码会打印三条消息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#if compiler(>=5)
print("Compiled with the Swift 5 compiler or later")
#endif
#if swift(>=4.2)
print("Compiled in Swift 4.2 mode or later")
#endif
#if compiler(>=5) && swift(<5)
print("Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5")
#endif
// 输出 "Compiled with the Swift 5 compiler or later"。
// 输出 "Compiled in Swift 4.2 mode or later"。
// 输出 "Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5"。

canImport() 平台条件的实参是某个可能在所有平台上都不存在的模块名,模块名中可以包含点(.)。这个条件测试是否可以导入该模块,但并不会真正导入它:如果模块存在,该平台条件返回 true,否则返回 false。

targetEnvironment() 平台条件在为指定环境编译代码时返回 true,否则返回 false。

注意:arch(arm) 平台条件对 ARM 64 设备不会返回 true; 为 32 位 iOS 模拟器编译代码时, arch(i386) 平台条件返回 true。

你可以用逻辑运算符 &&、||、! 组合和取反编译条件,并用圆括号分组。这些运算符的结合性和优先级与用于组合普通布尔表达式的逻辑运算符相同。

与 if 语句类似,你可以添加多个条件分支来测试不同的编译条件:用 #elseif 子句添加任意数量的附加分支,还可以用 #else 子句添加最后一个附加分支。包含多个分支的条件编译块形式如下:

1
2
3
4
5
6
7
#if <#compilation condition 1#>
    <#statements to compile if compilation condition 1 is true#>
#elseif <#compilation condition 2#>
    <#statements to compile if compilation condition 2 is true#>
#else
    <#statements to compile if both compilation conditions are false#>
#endif

注意:条件编译块体中的每条语句都会被解析, 即使它不会被编译。 不过有一个例外: 如果编译条件包含 swift() 或 compiler() 平台条件, 那么只有在语言或编译器版本与平台条件中指定的版本相匹配时, 这些语句才会被解析。 这一例外确保较旧的编译器不会尝试解析 较新 Swift 版本中引入的语法。

关于如何把显式成员表达式包在条件编译块中,参见显式成员表达式。

Grammar of a conditional compilation block:

conditional-compilation-block → if-directive-clause elseif-directive-clauses? else-directive-clause? endif-directive

if-directive-clause → if-directive compilation-condition statements?
elseif-directive-clauses → elseif-directive-clause elseif-directive-clauses?
elseif-directive-clause → elseif-directive compilation-condition statements?
else-directive-clause → else-directive statements?
if-directive → #if
elseif-directive → #elseif
else-directive → #else
endif-directive → #endif

compilation-condition → platform-condition
compilation-condition → identifier
compilation-condition → boolean-literal
compilation-condition → ( compilation-condition )
compilation-condition → ! compilation-condition
compilation-condition → compilation-condition && compilation-condition
compilation-condition → compilation-condition || compilation-condition

platform-condition → os ( operating-system )
platform-condition → arch ( architecture )
platform-condition → swift ( >= swift-version ) | swift ( < swift-version )
platform-condition → compiler ( >= swift-version ) | compiler ( < swift-version )
platform-condition → canImport ( import-path )
platform-condition → targetEnvironment ( environment )

operating-system → macOS | iOS | watchOS | tvOS | visionOS | Linux | Windows
architecture → arm | arm64 | i386 | wasm32 | x86_64
swift-version → decimal-digits swift-version-continuation?
swift-version-continuation → . decimal-digits swift-version-continuation?
environment → simulator | macCatalyst

行控制语句

行控制语句用于指定可以与正在编译的源代码的行号和文件名不同的行号和文件名。用行控制语句可以改变 Swift 用于诊断和调试的源代码位置。

行控制语句的形式如下:

1
2
#sourceLocation(file: <#file path#>, line: <#line number#>)
#sourceLocation()

行控制语句的第一种形式会改变 #line、#file、#fileID 和 #filePath 字面表达式的值,从该行控制语句之后的代码行开始生效。行号改变 #line 的值,可以是任何大于零的整数字面量;文件路径改变 #file、#fileID 和 #filePath 的值,是一个字符串字面量:指定的字符串成为 #filePath 的值,而该字符串的最后一段路径部分被用作 #fileID 的值。关于 #file、#fileID 和 #filePath 的内容,参见字面表达式。

行控制语句的第二种形式 #sourceLocation() 把源代码位置重置回默认的行号和文件路径。

Grammar of a line control statement:

line-control-statement → #sourceLocation ( file: file-path , line: line-number )
line-control-statement → #sourceLocation ( )
line-number → A decimal integer greater than zero
file-path → static-string-literal

编译期诊断语句

在 Swift 5.9 之前,#warning 和 #error 语句会在编译期间发出诊断信息;这一行为现在由 Swift 标准库中的 warning(_:) 和 error(_:) 宏提供。

可用性条件

可用性条件用作 if、while 和 guard 语句的条件,根据指定的平台实参查询 API 在运行时的可用性。

可用性条件的形式如下:

1
2
3
4
5
if #available(<#platform name#> <#version#>, <#...#>, *) {
    <#statements to execute if the APIs are available#>
} else {
    <#fallback statements to execute if the APIs are unavailable#>
}

你用可用性条件来根据想使用的 API 在运行时是否可用,执行某个代码块。编译器在验证该代码块中的 API 是否可用时,会利用可用性条件提供的信息。

可用性条件接受一个由平台名和版本组成的逗号分隔列表:平台名使用 iOS、macOS、watchOS、tvOS 和 visionOS,并写出相应的版本号。* 实参是必需的,它表示在其他任何平台上,由该可用性条件守护的代码块体在你的目标所指定的最低部署目标上执行。

与布尔条件不同,你不能用 &&、|| 之类的逻辑运算符组合可用性条件;也不能用 ! 取反可用性条件,而应改用不可用性条件,其形式如下:

1
2
3
4
5
if #unavailable(<#platform name#> <#version#>, <#...#>) {
    <#fallback statements to execute if the APIs are unavailable#>
} else {
    <#statements to execute if the APIs are available#>
}

#unavailable 形式是取反该条件的语法糖:在不可用性条件中,* 实参是隐式的,不能写出来,其含义与可用性条件中的 * 实参相同。

Grammar of an availability condition:

availability-condition → #available ( availability-arguments )
availability-condition → #unavailable ( availability-arguments )
availability-arguments → availability-argument | availability-argument , availability-arguments
availability-argument → platform-name platform-version
availability-argument → *

platform-name → iOS | iOSApplicationExtension
platform-name → macOS | macOSApplicationExtension
platform-name → macCatalyst | macCatalystApplicationExtension
platform-name → watchOS | watchOSApplicationExtension
platform-name → tvOS | tvOSApplicationExtension
platform-name → visionOS | visionOSApplicationExtension
platform-version → decimal-digits
platform-version → decimal-digits . decimal-digits
platform-version → decimal-digits . decimal-digits . decimal-digits