为有牺牲多壮志,敢教日月换新天。

Swift5.4 语言指南(二十九)高级运算符

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/10973030.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

除了“基本运算符”中描述的运算符外,Swift还提供了一些高级运算符,它们可以执行更复杂的值操作。这些包括您将在C和Objective-C中熟悉的所有按位和移位运算符。

与C中的算术运算符不同,Swift中的算术运算符默认情况下不会溢出。溢出行为被捕获并报告为错误。要选择溢出行为,请使用默认情况下会溢出的Swift第二组算术运算符,例如溢出加法运算符(&+)。所有这些溢出运算符都以“&”号开头&

当定义自己的结构,类和枚举时,为这些自定义类型提供标准Swift运算符的实现可能会很有用。使用Swift,可以轻松地为这些运算符提供量身定制的实现,并轻松确定您创建的每种类型的行为。

您不仅限于预定义的运算符。使用Swift,您可以自由定义自己的自定义中缀,前缀,后缀和赋值运算符,并具有自定义优先级和关联性值。这些运算符可以像任何预定义的运算符一样在您的代码中使用和采用,甚至可以扩展现有类型以支持您定义的自定义运算符。

按位运算符

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

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

按位NOT运算符

位NOT运算符~)反转数所有位:

../_images/bitwiseNOT_2x.png

按位NOT运算符是一个前缀运算符,它紧接在其运算的值之前出现,没有任何空格:

  1. let initialBits: UInt8 = 0b00001111
  2. let invertedBits = ~initialBits // equals 11110000

UInt8整数有8位,可以存储0之间的任何值255本示例UInt8使用二进制值初始化一个整数,该二进制值00001111的前四位设置为0,后四位设置为1等效于的十进制值15

然后使用按位NOT运算符创建一个名为的新常量invertedBits,该常量等于initialBits,但所有位都取反。零变成1,而1变成零。invertedBitsis的值11110000,它等于的无符号十进制值240

按位与运算符

位AND运算符&)结合了两个数字的位数。它返回一个新的号码,其位被设置为1仅当位是等于1两个输入数字:

../_images/bitwiseAND_2x.png

在下面的例子中,值firstSixBitslastSixBits两个具有四个中间位等于1按位AND运算符将它们组合成数字00111100,该数字等于无符号十进制值60

  1. let firstSixBits: UInt8 = 0b11111100
  2. let lastSixBits: UInt8 = 0b00111111
  3. let middleFourBits = firstSixBits & lastSixBits // equals 00111100

按位或运算符

位或运算符|)两个数的比特进行比较。操作者返回一个新的数字,其比特被设置为1如果所述比特等于1任一输入号码:

../_images/bitwiseOR_2x.png

在下面的例子中,的值someBitsmoreBits具有不同的位设置为1按位OR运算符将它们组合起来以生成数字11111110,该数字等于的无符号十进制数254

  1. let someBits: UInt8 = 0b10110010
  2. let moreBits: UInt8 = 0b01011110
  3. let combinedbits = someBits | moreBits // equals 11111110

按位XOR运算符

按位XOR运算符,或“异或运算符”( ^),比较两个数的位。运算符返回一个新数字,该数字的位设置为1输入位不同的地方,并设置为0输入位相同的地方:

../_images/bitwiseXOR_2x.png

在下面的例子中,的值firstBitsotherBits各自具有位设定为1在该其他没有一个位置。按位XOR运算符将这两个位都设置1为其输出值。和中的所有其他位都firstBitsotherBits匹配,并0在输出值中设置为

  1. let firstBits: UInt8 = 0b00010100
  2. let otherBits: UInt8 = 0b00000101
  3. let outputBits = firstBits ^ otherBits // equals 00010001

按位左移和右移运算符

按位左移位运算符<<)和逐位向右移位运算符>>)中的数向左或由特定数量的地方向右移动的所有位,根据下面定义的规则。

按位左移和右移具有将整数乘以或除以2的作用。将整数的位向左移一位将其值加倍,而将其向右移一位将其值减半。

无符号整数的移位行为

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

  1. 现有位向左或向右移动所请求的位数。
  2. 任何超出整数存储范围的位都将被丢弃。
  3. 在将原始位向左或向右移动后,将零插入到留在后面的空间中。

这种方法称为逻辑转换

下图显示的结果位置向左移动)和的结果位置向右移动)。蓝色数字将移位,灰色数字将被丢弃,而橙色零将被插入:11111111 << 111111111111111111 >> 1111111111

../_images/bitshiftUnsigned_2x.png

这是Swift代码中移位的外观:

  1. let shiftBits: UInt8 = 4 // 00000100 in binary
  2. shiftBits << 1 // 00001000
  3. shiftBits << 2 // 00010000
  4. shiftBits << 5 // 10000000
  5. shiftBits << 6 // 00000000
  6. shiftBits >> 2 // 00000001

您可以使用位移来编码和解码其他数据类型内的值:

  1. let pink: UInt32 = 0xCC6699
  2. let redComponent = (pink & 0xFF0000) >> 16 // redComponent is 0xCC, or 204
  3. let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent is 0x66, or 102
  4. let blueComponent = pink & 0x0000FF // blueComponent is 0x99, or 153

本示例使用一个UInt32常量pink来存储粉红色的级联样式表颜色值。CSS颜色值以Swift的十六进制数字表示形式#CC6699编写0xCC6699然后,按位AND运算符()和按位右移运算符(将该颜色分解为红色(CC),绿色(66)和蓝色(99)分量&>>

红色分量是通过在数字0xCC6699之间执行按位与运算而获得的0xFF0000中的零0xFF0000有效地“屏蔽”的第二个和第三个字节0xCC6699,从而导致6699忽略并留下0xCC0000结果。

然后,此数字向右(移16位十六进制数中的每对字符使用8位,因此向右移16位将转换这与相同,其十进制值为>> 160xCC00000x0000CC0xCC204

类似地,绿色分量是通过在数字0xCC6699之间执行按位与运算而获得的0x00FF00,其输出值为0x006600然后,此输出值向右移动八位,给出一个值为0x66,其十进制值为102

最后,通过在数字0xCC6699之间执行按位与运算,可以得到蓝色分量。蓝色分量0x0000FF的输出值为0x000099由于0x000099早已等于0x99,其十进制值为153,因此使用该值时不会将其向右移动,

有符号整数的移位行为

有符号整数的移位行为比无符号整数的移位行为更为复杂,因为有符号整数以二进制形式表示。(为简单起见,下面的示例基于8位带符号整数,但相同的原理适用于任何大小的带符号整数。)

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

其余位(称为值位)存储实际值。从无符号整数开始,正数的存储方式与无符号整数完全相同0这是Int8查找数字中的位的方式4

../_images/bitshiftSignedFour_2x.png

符号位是0(表示“正”),而七个值位只是数字4,用二进制表示法书写。

但是,负数的存储方式不同。通过将的绝对值减去2的幂来存储它们n,其中n是值位数。一个八位数字具有七个值位,因此这意味着2的幂7128

这是Int8查找数字中的位的方式-4

../_images/bitshiftSignedMinusFour_2x.png

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

../_images/bitshiftSignedMinusFourValue_2x.png

负数的这种编码称为二进制补码表示。表示负数似乎是一种不寻常的方式,但是它有几个优点。

首先,您可以添加-1-4,简单地通过执行一个标准二进制加法全部八个位(包括符号位),并丢弃任何不适合在八位一旦你完成:

../_images/bitshiftSignedAddition_2x.png

其次,二进制补码表示法还使您可以像正数一样向左和向右移动负数的位,并且仍然会在每次向左移时将它们加倍,或者将向右移的每一位减半。为此,将有符号整数向右移动时会使用一条额外的规则:当您将有符号整数向右移动时,应用与无符号整数相同的规则,但是用符号bit填充左侧的任何空位,而不是而不是零。

../_images/bitshiftSigned_2x.png

此操作可确保有符号整数向右移位后具有相同的符号,这称为算术移位

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

溢出运算符

如果您尝试将数字插入不能容纳该值的整数常量或变量中,则默认情况下,Swift会报告错误,而不是允许创建无效值。当您使用太大或太小的数字时,此行为可提供额外的安全性。

例如,Int16整数类型可以包含-32768之间的任何有符号整数32767尝试将Int16常量或变量设置为该范围之外的数字会导致错误:

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

当值太大或太小时提供错误处理,可以为边界值条件编码提供更大的灵活性。

但是,当您特别希望溢出条件截断可用位数时,可以选择采用这种行为,而不是触发错误。Swift提供了三个算术溢出运算符,它们选择采用溢出行为进行整数计算。这些运算符都以“&”号开头&

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

价值溢出

数字可以在正方向和负方向上溢出。

这是使用溢出加法运算符(&+允许无符号整数沿正方向溢出的情况的示例

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

变量unsignedOverflow使用aUInt8可以保持的最大值255,或11111111二进制)进行初始化然后1使用溢出加法运算符(&+将其递增这将其二进制表示形式推到aUInt8可以容纳的大小之上,从而导致其溢出超出其范围,如下图所示。UInt8溢出相加后保留在的范围内的值为00000000,或为零。

../_images/overflowAddition_2x.png

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

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

aUInt8可以保持的最小值为零或00000000二进制。如果减去100000000使用溢出减法运算符(&-),这个数字将溢出并环绕到11111111,或255十进制。

../_images/overflowUnsignedSubtraction_2x.png

有符号整数也会发生溢出。对带符号整数的所有加法和减法都是按位方式执行的,其中符号位作为要添加或减去的数字的一部分包括在内,如按位左移和右移运算符中所述

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

Int8可以保留的最小值-128,或者10000000为二进制。1用溢出运算符从该二进制数中减去可得出二进制值01111111,该二进制值将切换符号位并给出正数127,即anInt8可以保持的最大正值

../_images/overflowSignedSubtraction_2x.png

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

优先性和关联性

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

运算符关联性定义了如何将具有相同优先级的运算符组合在一起-从左侧分组还是从右侧分组。可以将其理解为“它们与他们左侧的表情相关联”或“它们与他们右侧的表情相关联”的含义。

在计算复合表达式的计算顺序时,必须考虑每个运算符的优先级和关联性,这一点很重要。例如,运算符优先级解释了为什么以下表达式等于17

  1. 2 + 3 % 4 * 5
  2. // this equals 17

如果严格从左到右阅读,则可能希望表达式的计算如下:

  • 23等于5
  • 5余数4等于1
  • 1时间5等于5

但是,实际答案17不是5较高优先级的运算符先于较低优先级的运算符进行评估。与C语言一样,在Swift中,余数运算符(%)和乘法运算符(*)的优先级高于加法运算符(+)。结果,在考虑添加之前都对它们都进行了评估。

但是,余数和乘法的优先级彼此相同要确定要使用的确切评估顺序,您还需要考虑它们的关联性。余数和乘法都与左侧的表达式相关联。可以认为这是在表达式的这些部分的左侧添加隐式括号,从它们的左侧开始:

  1. 2 + ((3 % 4) * 5)

(3 4)3,因此等效于:

  1. 2 + (3 * 5)

(3 5)15,因此等效于:

  1. 2 + 15

此计算得出的最终答案17

有关Swift标准库提供的运算符的信息,包括运算符优先级组和关联性设置的完整列表,请参阅运算符声明

笔记

Swift的运算符优先级和关联性规则比C和Objective-C中的运算符更简单且更可预测。但是,这意味着它们与基于C的语言并不完全相同。将现有代码移植到Swift时,请务必确保操作员交互仍然按照您期望的方式运行。

操作员方法

类和结构可以提供它们自己的现有运算符的实现。这称为使现有运算符超载

下例显示了如何+为自定义结构实现算术加法运算符()。算术加法运算符是二进制运算符,因为它在两个目标上进行操作,并且由于在两个目标之间出现而被称为中

该示例Vector2D为二维位置矢量定义了一个结构,然后定义了将该结构的实例加在一起operator方法的定义(x, y)Vector2D

  1. struct Vector2D {
  2. var x = 0.0, y = 0.0
  3. }
  4. extension Vector2D {
  5. static func + (left: Vector2D, right: Vector2D) -> Vector2D {
  6. return Vector2D(x: left.x + right.x, y: left.y + right.y)
  7. }
  8. }

运算符方法定义为上的类型方法Vector2D,其方法名称与要重载的运算符相匹配(+)。由于加法不是向量基本行为的一部分,因此类型方法是在的扩展中定义的,Vector2D而不是在的主结构声明中定义的Vector2D因为算术加法运算符是二进制运算符,所以此运算符方法采用类型为2的两个输入参数,Vector2D并返回类型也是的单个输出值Vector2D

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

type方法可以用作现有Vector2D实例之间的中缀运算符

  1. let vector = Vector2D(x: 3.0, y: 1.0)
  2. let anotherVector = Vector2D(x: 2.0, y: 4.0)
  3. let combinedVector = vector + anotherVector
  4. // combinedVector is a Vector2D instance with values of (5.0, 5.0)

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

../_images/vectorAddition_2x.png

前缀和后缀运算符

上面显示的示例演示了二进制中缀运算符的自定义实现。类和结构也可以提供标准一元运算符的实现一元运算符针对单个目标进行操作。他们是前缀,如果他们先于他们的目标(如-a)和后缀运营商,如果他们遵循自己的目标(如b!)。

通过在声明operator方法时关键字之前编写prefixorpostfix修饰符,可以实现前缀或后缀一元运算func符:

  1. extension Vector2D {
  2. static prefix func - (vector: Vector2D) -> Vector2D {
  3. return Vector2D(x: -vector.x, y: -vector.y)
  4. }
  5. }

上面的示例-aVector2D实例实现了一元减运算符(一元减运算符是前缀运算符,因此此方法必须使用prefix修饰符限定

对于简单的数值,一元减运算符将正数转换为负数,反之亦然。Vector2D实例的相应实现对xy属性都执行此操作

  1. let positive = Vector2D(x: 3.0, y: 4.0)
  2. let negative = -positive
  3. // negative is a Vector2D instance with values of (-3.0, -4.0)
  4. let alsoPositive = -negative
  5. // alsoPositive is a Vector2D instance with values of (3.0, 4.0)

复合赋值运算符

复合赋值运算符将赋值(=)与另一个操作结合在一起例如,加法赋值运算符(+=)将加法和赋值组合到一个操作中。您将复合赋值运算符的左输入参数类型标记为inout,因为该参数的值将直接从operator方法内部进行修改。

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

  1. extension Vector2D {
  2. static func += (left: inout Vector2D, right: Vector2D) {
  3. left = left + right
  4. }
  5. }

由于加法运算符是较早定义的,因此您无需在此处重新实现加法过程。取而代之的是,加法赋值运算符方法利用现有的加法运算符方法,并使用它来将左侧的值设置为左侧的值加上右侧的值:

  1. var original = Vector2D(x: 1.0, y: 2.0)
  2. let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
  3. original += vectorToAdd
  4. // original now has values of (4.0, 6.0)

笔记

无法重载默认的赋值运算符(=)。仅复合赋值运算符可以重载。同样,三元条件运算符()也不能重载。c

等价运算符

默认情况下,自定义类和结构没有实现等效运算符,即等于运算符(==)和不等于运算符(!=)的实现。通常==您可以实现运算符,并使用标准库的默认!=运算符实现来抵消运算符的结果==有两种实现==操作符的方法:您可以自己实现,或者对于许多类型,可以要求Swift为您综合实现。在这两种情况下,您都将对标准库的Equatable协议添加一致性

==以与实现其他中缀运算符相同的方式提供该运算符的实现:

  1. extension Vector2D: Equatable {
  2. static func == (left: Vector2D, right: Vector2D) -> Bool {
  3. return (left.x == right.x) && (left.y == right.y)
  4. }
  5. }

上面的示例实现了一个==运算符,用于检查两个Vector2D实例是否具有相等的值。在的上下文中Vector2D,将“等于”视为“两个实例具有相同的x值和y值”是有意义的,因此这是运算符实现所使用的逻辑。

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

  1. let twoThree = Vector2D(x: 2.0, y: 3.0)
  2. let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
  3. if twoThree == anotherTwoThree {
  4. print("These two vectors are equivalent.")
  5. }
  6. // Prints "These two vectors are equivalent."

在许多简单的情况下,您可以要求Swift为您提供等效运算符的综合实现,如使用综合实现采用协议中所述

定制运营商

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

新的运营商使用的是在全球范围内声明的operator关键字,并且都标有prefixinfixpostfix修饰:

  1. prefix operator +++

上面的示例定义了一个名为的新前缀运算符+++该运算符在Swift中没有现有的含义,因此在下面使用Vector2D实例的特定上下文中为其赋予了自定义的含义就本示例而言,+++被视为新的“前缀加倍”运算符。通过使用前面定义的加法赋值运算符将向量加到自身上,它将实例xy加倍Vector2D为了实现+++运营商,你添加一个名为类型的方法+++,以Vector2D如下:

  1. extension Vector2D {
  2. static prefix func +++ (vector: inout Vector2D) -> Vector2D {
  3. vector += vector
  4. return vector
  5. }
  6. }
  7. var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
  8. let afterDoubling = +++toBeDoubled
  9. // toBeDoubled now has values of (2.0, 8.0)
  10. // afterDoubling also has values of (2.0, 8.0)

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

自定义中缀运算符每个都属于一个优先级组。优先级组指定一个运算符相对于其他中缀运算符的优先级,以及该运算符的关联性。有关这些特征如何影响中缀运算符与其他中缀运算符的交互的说明,请参见优先级和关联性

未明确放置在优先级组中的自定义中缀运算符将被赋予默认优先级组,该优先级组的优先级立即高于三元条件运算符的优先级。

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

  1. infix operator +-: AdditionPrecedence
  2. extension Vector2D {
  3. static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
  4. return Vector2D(x: left.x + right.x, y: left.y - right.y)
  5. }
  6. }
  7. let firstVector = Vector2D(x: 1.0, y: 2.0)
  8. let secondVector = Vector2D(x: 3.0, y: 4.0)
  9. let plusMinusVector = firstVector +- secondVector
  10. // plusMinusVector is a Vector2D instance with values of (4.0, -2.0)

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

笔记

在定义前缀或后缀运算符时,您无需指定优先级。但是,如果将前缀和后缀运算符都应用于同一操作数,则将首先应用后缀运算符。

结果生成器

一个结果建设者是你定义一个类型,对于创建嵌套数据,例如列表或树,以自然,声明的方式增加了语法。使用结果构建器的代码可以包含普通的Swift语法(如iffor)来处理条件数据或重复数据。

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

  1. protocol Drawable {
  2. func draw() -> String
  3. }
  4. struct Line: Drawable {
  5. var elements: [Drawable]
  6. func draw() -> String {
  7. return elements.map { $0.draw() }.joined(separator: "")
  8. }
  9. }
  10. struct Text: Drawable {
  11. var content: String
  12. init(_ content: String) { self.content = content }
  13. func draw() -> String { return content }
  14. }
  15. struct Space: Drawable {
  16. func draw() -> String { return " " }
  17. }
  18. struct Stars: Drawable {
  19. var length: Int
  20. func draw() -> String { return String(repeating: "*", count: length) }
  21. }
  22. struct AllCaps: Drawable {
  23. var content: Drawable
  24. func draw() -> String { return content.draw().uppercased() }
  25. }

Drawable协议定义了对可以绘制的东西(例如线条或形状)的要求:类型必须实现一个draw()方法。Line结构表示单线工程图,并且它是大多数工程图的顶层容器。要绘制a Line,该结构调用draw()该行的每个组件,然后将结果字符串连接为单个字符串。Text结构包装字符串以使其成为图形的一部分。AllCaps结构包装并修改了另一个图形,将图形中的所有文本转换为大写。

可以通过调用其初始值设定项来绘制具有这些类型的图形:

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

这段代码有效,但是有点尴尬。后面深深嵌套的括号AllCaps很难阅读。回退逻辑用“世界”时,namenil具有使用到内联进行??操作,这将是很难与任何更复杂。如果您需要包括开关或for循环来构建图形的一部分,则无法做到这一点。结果生成器使您可以像这样重写代码,使其看起来像普通的Swift代码。

要定义结果生成器,您可以@resultBuilder在类型声明上编写属性。例如,此代码定义了一个名为的结果构建器DrawingBuilder,该构建器使您可以使用声明性语法来描述图形:

  1. @resultBuilder
  2. struct DrawingBuilder {
  3. static func buildBlock(_ components: Drawable...) -> Drawable {
  4. return Line(elements: components)
  5. }
  6. static func buildEither(first: Drawable) -> Drawable {
  7. return first
  8. }
  9. static func buildEither(second: Drawable) -> Drawable {
  10. return second
  11. }
  12. }

DrawingBuilder结构定义了实现部分结果生成器语法的三种方法。buildBlock(_:)方法增加了对在代码块中编写一系列行的支持。它将该块中的组件合并为一个LinebuildEither(first:)buildEither(second:)方法添加的支持ifelse

您可以将应用于@DrawingBuilding函数的参数,这会将传递给函数的闭包转换为结果生成器从该闭包创建的值。例如:

  1. func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
  2. return content()
  3. }
  4. func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
  5. return AllCaps(content: content())
  6. }
  7. func makeGreeting(for name: String? = nil) -> Drawable {
  8. let greeting = draw {
  9. Stars(length: 3)
  10. Text("Hello")
  11. Space()
  12. caps {
  13. if let name = name {
  14. Text(name + "!")
  15. } else {
  16. Text("World!")
  17. }
  18. }
  19. Stars(length: 2)
  20. }
  21. return greeting
  22. }
  23. let genericGreeting = makeGreeting()
  24. print(genericGreeting.draw())
  25. // Prints "***Hello WORLD!**"
  26. let personalGreeting = makeGreeting(for: "Ravi Patel")
  27. print(personalGreeting.draw())
  28. // Prints "***Hello RAVI PATEL!**"

makeGreeting(for:)函数接受一个name参数,并使用它来绘制个性化的问候语。draw(_:)caps(_:)功能都采取单一封闭件作为其参数,它标有@DrawingBuilder属性。调用这些函数时,将使用DrawingBuilder定义的特殊语法Swift将对图形的描述性描述转换为对on方法的一系列调用,DrawingBuilder以建立作为函数参数传递的值。例如,Swift将该示例中的调用caps(_:)转换为如下代码:

  1. let capsDrawing = caps {
  2. let partialDrawing: Drawable
  3. if let name = name {
  4. let text = Text(name + "!")
  5. partialDrawing = DrawingBuilder.buildEither(first: text)
  6. } else {
  7. let text = Text("World!")
  8. partialDrawing = DrawingBuilder.buildEither(second: text)
  9. }
  10. return partialDrawing
  11. }

Swift将if-else块转换为对buildEither(first:)andbuildEither(second:)方法的调用尽管您没有在自己的代码中调用这些方法,但是显示转换结果可以使您更轻松地了解使用DrawingBuilder语法时Swift如何转换代码

要添加对for使用特殊绘图语法编写循环的支持,请添加一个buildArray(_:)方法。

  1. extension DrawingBuilder {
  2. static func buildArray(_ components: [Drawable]) -> Drawable {
  3. return Line(elements: components)
  4. }
  5. }
  6. let manyStars = draw {
  7. Text("Stars:")
  8. for length in 1...3 {
  9. Space()
  10. Stars(length: length)
  11. }
  12. }

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

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

 

 
posted @ 2019-06-04 13:37  为敢技术  阅读(519)  评论(0编辑  收藏  举报