Skip to content

除了在 Basic Operators 中描述的操作符之外,Swift 还提供了一些高级操作符,可以执行更复杂的价值操作。这些操作符包括你从 C 和 Objective-C 中熟悉的全部位运算和位移操作符。

与 C 中的算术操作符不同,Swift 中的算术操作符默认不会发生溢出。溢出行为会被捕获并报告为错误。要启用溢出行为,请使用 Swift 的第二组默认会发生溢出的算术操作符,例如溢出加法操作符( &+ )。所有这些溢出操作符都以一个与号( & )开头。

当你定义自己的结构体、类和枚举时,为这些自定义类型提供标准 Swift 操作符的自定义实现可能会很有用。Swift 使得为每个你创建的类型提供这些操作符的定制实现变得非常容易,并且可以确定它们在每个类型中的具体行为。

你并不局限于预定义的操作符。Swift 给你自由定义自己的前缀、后缀、中缀和赋值操作符的自由,你可以自定义它们的优先级和结合性。这些操作符可以在你的代码中像预定义的操作符一样使用,并且你甚至可以扩展现有的类型以支持你定义的自定义操作符。

位运算符

位运算符使你可以操作数据结构中的个体原始数据位。它们通常用于低级编程,例如图形编程和设备驱动程序的创建。位运算符在处理来自外部源的原始数据时也非常有用,例如在通过自定义协议进行通信时对数据进行编码和解码。

Swift 支持 C 中的所有位运算符,如下所述。

位运算非操作符

位运算非操作符( ~ )会反转数字中的所有位:

Bitwise NOT

位运算非操作符是一个前缀操作符,紧挨着它操作的值出现,没有任何空格:

swift
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits  // equals 11110000

UInt8 整数有八位,并且可以存储从 0255 的任何值。此示例将一个 UInt8 整数初始化为二进制值 00001111 ,其前四位设置为 0 ,后四位设置为 1 。这相当于十进制值 15

然后使用位运算非操作符来创建一个新的常量 invertedBits ,它等于 initialBits ,但所有位都被反转。零变为一,一变为零。 invertedBits 的值为 11110000 ,这是一个无符号十进制值 240

位运算与操作符

位运算与操作符( & )将两个数字的位结合。它返回一个新的数字,该数字的位仅在两个输入数字中都等于 1 时才设置为 1

Bitwise AND

在下面的例子中, firstSixBitslastSixBits 的中间四位都等于 1 。位运算与操作符将它们结合起来生成数字 00111100 ,这是一个无符号十进制值 60

swift
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8  = 0b00111111
let middleFourBits = firstSixBits & lastSixBits  // equals 00111100

位或运算符

位或运算符( | )会比较两个数字的位。运算符返回一个新的数字,其位在任一输入数字的相应位为 1 时被设置为 1

Bitwise OR

在下面的例子中, someBitsmoreBits 的值在不同的位上设置为 1 。位或运算符将它们结合起来,生成数字 11111110 ,其无符号十进制值为 254

swift
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits  // equals 11111110

位异或运算符

位运算符,或“异或运算符”( ^ ),比较两个数字的位。该运算符返回一个新的数字,其位在输入位不同处被设置为 1 ,在输入位相同处被设置为 0

Bitwise XOR

在下面的例子中, firstBitsotherBits 的值在另一个值没有设置位的地方各自设置了一个位。位运算符将这两个位在输出值中设置为 1firstBitsotherBits 的其他位匹配,并在输出值中设置为 0

swift
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits  // equals 00010001

位运算符的左移和右移操作

位运算符的左移操作( << )和位运算符的右移操作( >> )将数字中的所有位向左或向右移动一定数量的位置,根据下面定义的规则。

位移操作对整数具有乘以或除以二的效果。将整数的位向左移动一位会使其值翻倍,而向右移动一位则会使值减半。

无符号整数的位移行为

无符号整数的位移行为如下:

  1. 存在的位将按照请求的数量向左或向右移动。
  2. 任何超出整数存储范围的位都会被丢弃。
  3. 在原始位移动到左或右后留下的空位中插入零。

这种方法称为逻辑位移。

下图显示了 11111111 << 1 (即 11111111 向左移动 1 位)和 11111111 >> 1 (即 11111111 向右移动 1 位)的结果,绿色数字表示位移,灰色数字表示丢弃,粉红色零表示插入:

Bitshift Unsigned

这是位移在 Swift 代码中的样子:

swift
let shiftBits: UInt8 = 4   // 00000100 in binary
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 is 0xCC, or 204
let greenComponent = (pink & 0x00FF00) >> 8   // greenComponent is 0x66, or 102
let blueComponent = pink & 0x0000FF           // blueComponent is 0x99, or 153

该示例使用了一个名为 pinkUInt32 常量来存储粉色的 CSS 颜色值。CSS 颜色值 #CC6699 在 Swift 的十六进制数表示中写为 0xCC6699 。然后通过位与运算符( & )和位右移运算符( >> )将该颜色分解为其红色( CC )、绿色( 66 )和蓝色( 99 )分量。

红色分量是通过在数字 0xCC66990xFF0000 之间执行位与操作获得的。 0xFF0000 中的零实际上“屏蔽”了 0xCC6699 的第二和第三字节,导致 6699 被忽略,留下 0xCC0000 作为结果。

然后将该数字向右移动 16 位( >> 16 )。每个十六进制数中的每对字符使用 8 位,因此向右移动 16 位将 0xCC0000 转换为 0x0000CC 。这与 0xCC 相同,其十进制值为 204

同样,绿色分量是通过在数字 0xCC66990x00FF00 之间执行位与操作获得的,这给出了输出值 0x006600 。然后将该输出值向右移动 8 位,得到值 0x66 ,其十进制值为 102

最后,蓝色分量通过在数字 0xCC66990x0000FF 之间执行位与操作获得,这给出了输出值 0x000099 。因为 0x000099 已经等于 0x99 ,其十进制值为 153 ,所以使用该值而无需将其右移。

签名整数的位移行为

签名整数的行为比无符号整数更复杂,因为签名整数在二进制中的表示方式不同。(以下示例基于 8 位签名整数以简化说明,但相同的原理适用于任何大小的签名整数。)

签名整数使用其第一个位(称为符号位)来表示整数是正数还是负数。符号位为 0 表示正数,符号位为 1 表示负数。

剩余的位(称为数值位)存储实际值。正数的存储方式与无符号整数完全相同,从 0 开始向上计数。这是 Int8 中表示数字 4 的位长:

Bitshift Signed Four

符号位是 0 (表示“正数”),而七位数值位就是数字 4 ,以二进制表示。

然而,负数的存储方式不同。它们通过从 2n 次方中减去它们的绝对值来存储,其中 n 是数值位的数量。一个八位数有七位数值位,这意味着 27 次方,即 128

这是 Int8 中表示数字 -4 的位长:

Bitshift Signed Minus Four

这次,符号位是 1 (表示“负”),而七位数值位的二进制值是 124 (即 128 - 4 ):

Bitshift Signed Minus Four Value

这种表示负数的编码被称为补码表示法。虽然这种方式表示负数可能看起来有些不寻常,但它有几个优点。

首先,你可以通过对所有八位(包括符号位)进行标准的二进制加法来将 -1 加到 -4 上,并在完成后丢弃超出八位的内容:

Bitshift Signed Addition

其次,补码表示法还允许你像正数一样将负数的位向左或向右移动,并且每次向左移动一位都会将它们翻倍,每次向右移动一位都会将它们减半。为了实现这一点,当对带符号整数进行右移时会使用一个额外的规则:当对带符号整数进行右移时,应用与无符号整数相同的规则,但填充左侧的空位时使用符号位而不是零。

Bitshift Signed

此操作确保右移后的带符号整数保持相同的符号,并且被称为算术移位。

由于正负数存储的方式特殊,将它们向右移动会使得它们更接近零。在此移动过程中保持符号位不变意味着负整数在值接近零时仍然保持为负数。

溢出运算符

如果你尝试将一个数字插入一个无法容纳该值的整数常量或变量中,默认情况下,Swift 会报告错误而不是允许创建一个无效的值。这种行为在处理过大或过小的数字时提供了额外的安全性。

例如, Int16 整数类型可以容纳介于 -3276832767 之间的任何带符号整数。尝试将一个 Int16 常量或变量设置为这个范围之外的数字会导致错误:

swift
var potentialOverflow = Int16.max
// potentialOverflow equals 32767, which is the maximum value an Int16 can hold
potentialOverflow += 1
// this causes an error

当值过大或过小时提供错误处理可以让你在处理边界条件时具有更大的灵活性。

但是,当您希望在特定情况下,当可用位数不足时发生溢出条件并截断数字时,可以选择启用此行为,而不是触发错误。Swift 为整数计算提供了三个溢出操作符,这些操作符可以启用溢出行为。这些操作符都以 ampersand ( & ) 开头:

  • 溢出加法 ( &+ )
  • 溢出减法 ( &- )
  • 溢出乘法 ( &* )

Value Overflow

数字可以正向溢出,也可以负向溢出。

这里是一个当无符号整数在正方向上溢出时的例子,使用了溢出加法运算符 (&+):

swift
var unsignedOverflow = UInt8.max
// unsignedOverflow equals 255, which is the maximum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &+ 1
// unsignedOverflow is now equal to 0

The variable unsignedOverflow is initialized with the maximum value a UInt8 can hold (255, or 11111111 in binary). It’s then incremented by 1 using the overflow addition operator (&+). This pushes its binary representation just over the size that a UInt8 can hold, causing it to overflow beyond its bounds, as shown in the diagram below. The value that remains within the bounds of the UInt8 after the overflow addition is 00000000, or zero.

Unsigned Overflow Addition

类似的,当无符号整数在负方向上溢出时也会发生类似的情况。以下使用溢出减法运算符( &- )的示例:

swift
var unsignedOverflow = UInt8.min
// unsignedOverflow equals 0, which is the minimum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &- 1
// unsignedOverflow is now equal to 255

UInt8 可以存储的最小值是零,或者在二进制中是 00000000 。如果使用溢出减法运算符( &- )从 00000000 中减去 1 ,数字将溢出并回绕到 11111111 ,或者在十进制中是 255

Unsigned Overflow Subtraction

对于带符号整数,也会发生溢出。带符号整数的所有加法和减法都是按位进行的,包括符号位作为参与加减的数字的一部分,如位移左和右位移运算符所述。

swift
var signedOverflow = Int8.min
// signedOverflow equals -128, which is the minimum value an Int8 can hold
signedOverflow = signedOverflow &- 1
// signedOverflow is now equal to 127

Int8 可以存储的最小值是 -128 ,或者在二进制中是 10000000 。使用溢出运算符从这个二进制数中减去 1 给出一个二进制值 01111111 ,这会切换符号位并给出正值 127 ,这是 Int8 可以存储的最大正值。

Signed Overflow Minus

对于有符号和无符号整数,正向溢出会从有效的最大整数值循环回到最小值,负向溢出会从最小值循环回到最大值。

优先级和结合性

运算符优先级赋予某些运算符比其他运算符更高的优先级;这些运算符会优先被应用。

运算符结合性定义了具有相同优先级的运算符如何分组在一起——要么从左向右分组,要么从右向左分组。可以将其理解为“它们与左侧的表达式结合”,或者“它们与右侧的表达式结合”。

在计算复合表达式的顺序时,考虑每个操作符的优先级和结合性是很重要的。例如,操作符优先级解释了以下表达式等于 17 的原因。

swift
2 + 3 % 4 * 5
// this equals 17

如果你严格从左到右阅读,你可能会认为表达式将被计算如下:

  1. 23 等于 5
  2. 5 除以 4 的余数等于 1
  3. 15 等于 5

然而,实际的答案是 17 ,而不是 5 。优先级较高的运算符会先于优先级较低的运算符进行计算。在 Swift 中,如 C 语言一样,取余运算符( % )和乘法运算符( * )的优先级高于加法运算符( + )。

然而,取余和乘法的优先级与其他运算符相同。要确定具体的计算顺序,还需要考虑它们的结合性。取余和乘法都向左结合。可以将这些部分的表达式想象成加上了从左开始的隐式括号:

swift
2 + ((3 % 4) * 5)

(3 % 4)3 ,所以这相当于:

swift
2 + (3 * 5)

(3 * 5)15 ,所以这相当于:

swift
2 + 15

这个计算得到最终的答案为 17

对于 Swift 标准库提供的运算符的介绍,包括运算符优先级组和结合性设置的完整列表,请参阅运算符声明

注意

Swift 的运算符优先级和结合性规则比 C 和 Objective-C 中的更简单且更可预测。然而,这意味着它们与 C 语言基础的运算符并不完全相同。在将现有代码移植到 Swift 时,请务必小心,确保运算符的交互仍然以您预期的方式行为。

操作方法

类和结构体可以提供现有运算符的自定义实现。这被称为重载现有运算符

以下示例展示了如何为自定义结构体实现算术加法运算符( + )。算术加法运算符是一个二元运算符,因为它作用于两个目标,而且它是一个中缀运算符,因为它出现在这两个目标之间。

The example 定义了一个 Vector2D 结构用于二维位置向量 (x, y) ,随后定义了一个操作方法来将 Vector2D 结构的实例相加:

swift
struct Vector2D {
    var x = 0.0, y = 0.0
}

extension Vector2D {
    static func + (left: Vector2D, right: Vector2D) -> Vector2D {
       return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}

操作方法被定义为在 Vector2D 上的一种类型方法,方法名称与要重载的操作符匹配( + )。由于加法不是向量的基本行为的一部分,因此该类型方法在 Vector2D 的扩展中定义,而不是在 Vector2D 的主要结构声明中。由于算术加法操作符是一个二元操作符,因此该操作方法接受两个类型为 Vector2D 的输入参数,并返回一个类型也为 Vector2D 的单一输出值。

在此实现中,输入参数命名为 leftright ,以表示将位于 + 运算符左侧和右侧的 Vector2D 实例。该方法返回一个新的 Vector2D 实例,其 xy 属性初始化为两个被添加在一起的 Vector2D 实例的 xy 属性之和。

类型方法可以在现有的 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 is a Vector2D instance with values of (5.0, 5.0)

本例将向量 (3.0, 1.0)(2.0, 4.0) 相加以生成向量 (5.0, 5.0) ,如下面所示。

Vector Addition

前缀和后缀运算符

上面的例子演示了一个自定义的二元中缀运算符的实现。类和结构也可以提供标准一元运算符的实现。一元运算符作用于单一的目标。如果它们在目标之前(例如 -a ),则为前缀运算符;如果它们在目标之后(例如 b! ),则为后缀运算符。

您可以通过在声明运算符方法时在 prefixpostfix 修改符之前写入 func 关键字来实现前缀或后缀一元运算符:

swift
extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}

上面的例子为 Vector2D 实例实现了负运算符( -a )。负运算符是一个前缀运算符,因此此方法需要使用 prefix 修改符进行限定。

对于简单的数值,负运算符将正数转换为其负数等价值,反之亦然。对于 Vector2D 实例的相应实现将在 xy 属性上执行此操作:

swift
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative is a Vector2D instance with values of (-3.0, -4.0)
let alsoPositive = -negative
// alsoPositive is a Vector2D instance with values of (3.0, 4.0)

复合赋值运算符

复合赋值运算符将赋值( = )与另一个操作相结合。例如,加法赋值运算符( += )将加法和赋值结合成一个操作。你将复合赋值运算符的左输入参数类型标记为 inout ,因为参数的值将在运算符方法内部直接被修改。

下面的示例为 Vector2D 实例实现了一个加法赋值运算符方法:

swift
extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}

由于之前已经定义了加法运算符,这里不需要重新实现加法过程。相反,加法赋值运算符方法利用现有的加法运算符方法,并使用它将左值设置为左值加上右值:

swift
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original now has values of (4.0, 6.0)

注意

无法重载默认赋值运算符( = )。只有复合赋值运算符可以被重载。同样,三元条件运算符( a ? b : c )也无法被重载。

等价运算符

默认情况下,自定义类和结构体没有实现等价运算符,即等于运算符( == )和不等于运算符( != )。通常您会实现 == 运算符,并使用 Swift 标准库中 != 运算符的默认实现,该运算符会返回 == 运算符结果的否定。实现 == 运算符有两种方式:您可以自己实现它,或者对于许多类型,您可以请求 Swift 为您合成一个实现。无论哪种方式,您都需要遵循 Swift 标准库中的 Equatable 协议。

您可以像实现其他中缀运算符一样提供 == 运算符的实现:

swift
extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
       return (left.x == right.x) && (left.y == right.y)
    }
}

上述示例实现了一个 == 操作符,用于检查两个 Vector2D 实例的值是否等价。在 Vector2D 的上下文中,“相等”意味着“两个实例具有相同的 x 值和 y 值”,因此操作符的实现就是基于这种逻辑。

您现在可以使用此运算符检查两个 Vector2D 实例是否等价:

swift
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
    print("These two vectors are equivalent.")
}
// Prints "These two vectors are equivalent."

在许多简单的情况下,你可以请求 Swift 为你提供等价运算符的合成实现,如《使用合成实现采用协议》中所述。

自定义运算符

除了 Swift 提供的标准运算符外,您还可以声明和实现自己的自定义运算符。有关可用于定义自定义运算符的字符列表,请参见运算符

新的运算符使用 operator 关键字在全局级别声明,并使用 prefixinfixpostfix 标记:

swift
prefix operator +++

上面的示例定义了一个新的前缀运算符 +++ 。此运算符在 Swift 中没有现成的含义,因此在处理 Vector2D 实例的具体上下文中,它被赋予了自定义含义。为了这个示例的目的, +++ 被视为一个新的“前缀加倍”运算符。它通过使用前面定义的加法赋值运算符将向量与其自身相加,从而将 xy 值加倍。要实现 Vector2D 运算符,您需要向 +++ 添加一个类型方法 +++ ,如下所示:

swift
extension Vector2D {
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}

var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)

自定义中缀运算符的优先级

自定义中缀运算符属于一个优先级组。优先级组指定了运算符相对于其他中缀运算符的优先级,以及运算符的结合性。有关这些特性如何影响中缀运算符与其他中缀运算符的交互,请参阅优先级和结合性

如果没有显式地将自定义中缀运算符放入优先级组,则该运算符将被赋予一个默认优先级组,其优先级高于三元条件运算符的优先级。

以下示例定义了一个新的自定义后缀运算符 +- ,它属于优先级组 AdditionPrecedence

swift
infix operator +-: AdditionPrecedence
extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)

此运算符将两个向量的 x 值相加,并从第一个向量中减去第二个向量的 y 值。由于它本质上是一个“加法”运算符,因此被赋予了与加法中缀运算符 +- 相同的优先级组。有关 Swift 标准库提供的运算符的详细信息,包括完整的运算符优先级组和结合性设置列表,请参阅运算符声明。有关优先级组的更多信息以及如何定义您自己的运算符和优先级组的语法,请参阅运算符声明

注意

定义前缀或后缀运算符时不需要指定优先级。然而,如果你同时对同一个操作数应用前缀和后缀运算符,后缀运算符会先被应用。

结果构建器

结果构建器是你定义的一种类型,它添加了创建嵌套数据(如列表或树)的语法,以一种自然且声明的方式。使用结果构建器的代码可以包含普通 Swift 语法,如 iffor ,以处理条件或重复的数据片段。

以下代码定义了几种使用星号和文本在单行上绘制的类型。

swift
protocol Drawable {
    func draw() -> String
}
struct Line: Drawable {
    var elements: [Drawable]
    func draw() -> String {
        return elements.map { $0.draw() }.joined(separator: "")
    }
}
struct Text: Drawable {
    var content: String
    init(_ content: String) { self.content = content }
    func draw() -> String { return content }
}
struct Space: Drawable {
    func draw() -> String { return " " }
}
struct Stars: Drawable {
    var length: Int
    func draw() -> String { return String(repeating: "*", count: length) }
}
struct AllCaps: Drawable {
    var content: Drawable
    func draw() -> String { return content.draw().uppercased() }
}

Drawable 协议定义了可以绘制的内容,比如一条线或一个形状:该类型必须实现一个 draw() 方法。 Line 结构表示单线绘制,并且它是大多数绘制内容的顶级容器。为了绘制一个 Line ,该结构会对线的每个组件调用 draw() ,然后将得到的字符串连接成一个字符串。 Text 结构包裹一个字符串使其成为绘制的一部分。 AllCaps 结构包裹并修改另一个绘制内容,将绘制中的任何文本转换为大写。

通过调用它们的初始化器,可以使用这些类型绘制图形:

swift
let name: String? = "Ravi Patel"
let manualDrawing = Line(elements: [
     Stars(length: 3),
     Text("Hello"),
     Space(),
     AllCaps(content: Text((name ?? "World") + "!")),
     Stars(length: 2),
])
print(manualDrawing.draw())
// Prints "***Hello RAVI PATEL!**"

这段代码可以工作,但有点笨拙。 AllCaps 后面的深深嵌套的括号很难读。当 name 等于 nil 时,使用“World”作为后备逻辑需要使用 ?? 运算符在内联完成,如果需要更复杂的内容,这会很困难。如果你需要包含开关或 for 循环来构建图形的一部分,就无法做到这一点。结果构建器可以让你重写这样的代码,使其看起来像正常的 Swift 代码。

要定义一个结果构建器,您需要在类型声明上编写 @resultBuilder 属性。例如,这段代码定义了一个名为 DrawingBuilder 的结果构建器,它允许您使用声明性语法来描述一个绘图。

swift
@resultBuilder
struct DrawingBuilder {
    static func buildBlock(_ components: Drawable...) -> Drawable {
        return Line(elements: components)
    }
    static func buildEither(first: Drawable) -> Drawable {
        return first
    }
    static func buildEither(second: Drawable) -> Drawable {
        return second
    }
}

The DrawingBuilder 结构定义了三个方法来实现结果构建器语法的一部分。The buildBlock(_:) 方法添加了在代码块中写一系列行的支持。它将该块中的组件组合成一个 Line 。The buildEither(first:)buildEither(second:) 方法添加了对 if - else 的支持。

你可以将 @DrawingBuilder 属性应用到函数的参数上,这会将传递给函数的闭包转换为结果构建器从该闭包创建的值。例如:

swift
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return content()
}
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return AllCaps(content: content())
}

func makeGreeting(for name: String? = nil) -> Drawable {
    let greeting = draw {
        Stars(length: 3)
        Text("Hello")
        Space()
        caps {
            if let name = name {
                Text(name + "!")
            } else {
                Text("World!")
            }
        }
        Stars(length: 2)
    }
    return greeting
}
let genericGreeting = makeGreeting()
print(genericGreeting.draw())
// Prints "***Hello WORLD!**"

let personalGreeting = makeGreeting(for: "Ravi Patel")
print(personalGreeting.draw())
// Prints "***Hello RAVI PATEL!**"

makeGreeting(for:) 函数接受一个 name 参数并使用它来绘制个性化的问候语。 draw(_:)caps(_:) 函数都接受一个单一的闭包作为其参数,该闭包用 @DrawingBuilder 属性标记。当你调用这些函数时,使用 DrawingBuilder 定义的特殊语法。Swift 将这种描述绘图的声明性描述转换为对 DrawingBuilder 方法的一系列调用,以构建传递给函数参数的值。例如,Swift 将该示例中对 caps(_:) 的调用转换为如下代码:

swift
let capsDrawing = caps {
    let partialDrawing: Drawable
    if let name = name {
        let text = Text(name + "!")
        partialDrawing = DrawingBuilder.buildEither(first: text)
    } else {
        let text = Text("World!")
        partialDrawing = DrawingBuilder.buildEither(second: text)
    }
    return partialDrawing
}

Swift 将 if - else 块转换为对 buildEither(first:)buildEither(second:) 方法的调用。虽然你在自己的代码中不会调用这些方法,但显示转换的结果可以让人们更容易看到当你使用 DrawingBuilder 语法时 Swift 如何转换你的代码。

为了在特殊绘制语法中支持编写 for 循环,添加一个 buildArray(_:) 方法。

swift
extension DrawingBuilder {
    static func buildArray(_ components: [Drawable]) -> Drawable {
        return Line(elements: components)
    }
}
let manyStars = draw {
    Text("Stars:")
    for length in 1...3 {
        Space()
        Stars(length: length)
    }
}

在上面的代码中, for 循环创建了一个绘图数组, buildArray(_:) 方法将该数组转换为一个 Line

对于 Swift 如何将构建器语法转换为对构建器类型方法的调用的完整列表,请参见 resultBuilder