Swift學習(4)- 進階運算子(程式碼完善版)

Swift學習(4)- 進階運算子(程式碼完善版)

August 12, 2021·Jingyao Zhang
Jingyao Zhang

image

除了先前介紹過的基本運算子,Swift還提供了多種可對數值進行複雜運算的進階運算子。這些運算子包含了在CObjective-C中大家熟悉的位元運算子與位移運算子。

自訂結構、類別與列舉時,若能為它們提供標準Swift運算子的實作,將會非常實用。在Swift中為這些運算子自訂實作相當簡單,運算子也會針對不同型別使用對應的實作。

開發者不必受限於預先定義的運算子。在Swift中可以自由定義中置、前置、後置與賦值運算子,並可自訂其優先順序與結合性。這些運算子在程式碼中可如同預設運算子般使用,開發者甚至能擴充既有型別以支援自訂運算子。


位元運算子

位元運算子可操作資料結構中每個獨立的位元。它們通常用於底層開發,例如圖形程式設計與裝置驅動程式的撰寫。處理外部資源的原始資料時,像是對自訂通訊協定傳輸的資料進行編碼與解碼,位元運算子也非常有用。

Swift支援C語言中的所有位元運算子,以下將一一介紹。

Bitwise NOT Operator(按位元反運算子)

按位元反運算子~會將一個數值的所有位元進行反轉。

Bitwise NOT OP

按位元反運算子是前置運算子,直接放在運算元前方,且運算子與運算元之間不能有空格。

let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits  // 等於0b11110000
print("對值\(initialBits)按位元反後的結果是:\(invertedBits)")
---
output: 對值15按位元反後的結果是:240

UInt8型別的整數有8個位元,可儲存0~255之間的任意整數。這個範例初始化了一個UInt8型別的整數,並賦值為二進位的00001111,前4位為0,後4位為1。這個值等同於十進位的15。

接著使用按位元反運算子建立一個名為invertedBits的常數,這個常數的值就是initialBits所有位元反轉後的結果。也就是所有0變成1,所有1變成0。invertedBits的二進位值為11110000,等同於無號十進位數240。

Bitwise AND Operator(按位元與運算子)

按位元與運算子&會將兩個數值的位元進行合併。只有當兩個數值對應位元都為1時,結果的對應位元才為1。

Bitwise AND OP

在下列範例中,firstSixBitslastSixBits中間4個位元的值都為1。使用按位元與運算子後,得到二進位數值00111100,等同於無號十進位數60。

let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits  // 等於0b00111100
print("值\(firstSixBits)和值\(lastSixBits)按位元與後的結果為:\(middleFourBits)")
---
output: 252和值63按位元與後的結果為:60

Bitwise OR Operator(按位元或運算子)

按位元或運算子|可將兩個數值的位元進行比較。只要兩個數值對應位元中有任一為1,結果的對應位元就為1。

Bitwise OR OP

在下列範例中,someBitsmoreBits有不同的位元被設為1。使用按位元或運算子後,得到二進位數值11111110,等同於無號十進位254。

let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedBits = someBits | moreBits
print("值\(someBits)和值\(moreBits)按位元或後的結果為:\(combinedBits)")
---
output: 178和值94按位元或後的結果為:254

Bitwise XOR Operator(按位元異或運算子)

按位元異或運算子(又稱「互斥或」)^,可將兩個數值的位元進行比較。當兩個數值對應位元不相同時,結果的對應位元為1,若相同則為0。

Bitwise XOR OP

在下列範例中,firstBitsotherBits各有一個自己為1而對方為0的位元。按位元異或運算子會將這兩個位元設為1。其餘位元相同則設為0。

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits
print("值\(firstBits)和值\(otherBits)按位元異或後的結果為:\(outputBits)")
---
output: 20和值5按位元異或後的結果為:17

Bitwise Left and Right Shift Operators(按位元左移、右移運算子)

按位元左移運算子<<與按位元右移運算子>>可將一個數值的所有位元依指定位數進行左移或右移,需遵循下列規則。

對一個數值進行按位元左移或右移,相當於將該數值乘以2或除以2。將整數左移一位等同於乘以2,右移一位等同於除以2。

無號整數的位移運算

對無號整數進行位移的規則如下:

1. 已存在的位元依指定位數進行左移或右移

2. 任何因移動而超出整數儲存範圍的位元都會被捨棄

3. 用0填補移位後產生的空白位元

這種方式稱為邏輯移位

Bit shift unsigned

下列程式碼示範Swift中的位移運算。

let shiftBits: UInt8 = 0b00000100
print("\(shiftBits)向左移1位:\(shiftBits << 1)")  // 0b00001000
print("\(shiftBits)向左移2位:\(shiftBits << 2)")  // 0b00010000
print("\(shiftBits)向左移5位:\(shiftBits << 5)")  // 0b10000000
print("\(shiftBits)向左移6位:\(shiftBits << 6)")  // 0b00000000
print("\(shiftBits)向右移2位:\(shiftBits >> 2)")  // 0b00000001
---
output: 4向左移1位:8
4向左移2位:16
4向左移5位:128
4向左移6位:0
4向右移2位:1

可利用位移運算對其他資料型別進行編碼與解碼。

let pink: UInt32 = 0xCC6699; print(pink)
let redComponent  = (pink & 0xFF0000) >> 16  // redComponent為0xCC,即204
let greenComponent = (pink & 0x00FF00) >> 8  // greenComponent為0x66,即102
let blueComponent = (pink & 0x0000FF) // blueComponent為0x99,即153
print("pink中紅色成分為:\(redComponent)")
print("pink中綠色成分為:\(greenComponent)")
print("pink中藍色成分為:\(blueComponent)")
---
output: 13395609
pink中紅色成分為204
pink中綠色成分為102
pink中藍色成分為153

上述範例中,使用名為pinkUInt32常數儲存Cascading Style Sheets (CSS)中粉紅色的顏色值。該CSS顏色值#CC6699,在Swift中以十六進位0xCC6699表示。接著利用按位元與運算子&與按位元右移運算子>>將此顏色分解為紅(CC)、綠(66)、藍(99)三個部分。

紅色部分(redComponent)是將0xCC66990xFF0000進行按位元與運算後得到的。0xFF0000中的0會「遮蔽」0xCC6699的第二、第三個位元組,只留下0xCC0000。再將此數值右移16位後變成0x0000CC,即十進位204。

有號整數的位移運算

相較於無號整數,有號整數的位移運算較為複雜,這種複雜性來自於有號整數的二進位表示方式。(為簡化說明,下列範例皆以8位元有號整數為例,但原理適用於任何位數的有號整數。)

有號整數使用第一個位元(通常稱為符號位)來表示數值的正負。符號位為0代表正數,為1代表負數。

其餘位元(稱為數值位)儲存實際的值。有號正整數與無號整數的儲存方式相同,皆從0開始。以下是值為4的Int8型整數的二進位表示:

Bit shift signed four

符號位為0(代表「正數」),另外7位元則代表十進位4的二進位表示。

負數的儲存方式略有不同,會儲存2的n次方減去實際值的絕對值,這裡的n是數值位的位數。8位元的數有7個數值位,所以是2的7次方,即128。以下是值為-4的Int8型整數的二進位表示:

Bit shift signed minus four

這次符號位為1,代表負數,另外7位元則代表數值124(即2^7 - |-4| = 124)的二進位表示。

負數的表示方式通常稱為二進位補數。這種方式乍看之下有點奇怪,但有幾個優點。

首先,若要將-1與-4相加,只需對這兩個數的全部8個位元執行標準二進位加法(包含符號位),並將超出8位元的結果捨棄。

其次,使用二進位補數可讓負數的按位元左移與右移運算得到與正數相同的效果。也就是每左移一位就將自身數值乘以2,每右移一位就將自身數值除以2。為達此目的,有號整數的右移有一額外規則:對有號整數進行按位元右移時,遵循無號整數相同規則,但移位產生的空白位元以符號位填補,而非0。

此行為可確保有號整數的符號位不會因右移而改變,這通常稱為算術移位


溢位運算子

當將超過整數型別容量的值賦予常數或變數時,Swift預設會報錯,而非產生無效數值。這個行為讓開發者在運算過大或過小的數值時更有安全性。

例如,Int16型整數可容納的有號整數範圍為-32768~32767。若將超過此範圍的值賦予Int16型變數或常數,系統會報錯。

var potentialOverFlow = Int16.max
//potentialOverFlow += 1  // 若取消這行註解,執行後會報錯:Swift runtime failure: arithmetic overflow

在賦值時對過大或過小的情況提供錯誤處理,可讓開發者在處理邊界值時更有彈性。

然而,開發者也可選擇讓系統在數值溢位時採取截斷處理,而非報錯。Swift提供三個溢位運算子來支援整數溢位運算。這些運算子皆以&開頭:

  • 溢位加法&+
  • 溢位減法&-
  • 溢位乘法&*

數值溢位

數值可能發生上溢或下溢。

下列範例示範對無號整數使用溢位加法進行上溢時會發生什麼情況。

var unsignedOverFlow = UInt8.max  // unsignedOverFlow等於UInt8可容納的最大整數255
unsignedOverFlow = unsignedOverFlow &+ 1  // 此時unsignedOverFlow等於0
print("UInt8上溢後的值:\(unsignedOverFlow)")
---
output: UInt8上溢後的值0

unsignedOverFlow初始化為UInt8可容納的最大整數(255,二進位為11111111),再用溢位加法運算子&+加1。使其二進位表示超出UInt8可容納的位數,導致數值溢位。溢位後,仍留在UInt8範圍內的值為00000000,即十進位0。

Over flow addition

對無號整數進行下溢運算時也會有類似情況。以下為使用溢位減法運算子&-的範例。

unsignedOverFlow = UInt8.min  // unsignedOverFlow等於UInt8可容納的最小整數0
unsignedOverFlow = unsignedOverFlow &- 1  // 此時unsignedOverFlow等於255
print("UInt8下溢後的值:\(unsignedOverFlow)")
---
output: UInt8下溢後的值255

UInt8型整數可容納的最小值為0,二進位為00000000。使用溢位減法運算子減1時,數值會下溢並被截斷為11111111,即十進位255。

Over flow unsigned subtraction

溢位也會發生在有號整數上。針對有號整數的所有溢位加法或減法運算皆以按位元運算方式執行,符號位也會參與計算,如按位元左移、右移運算子所述。

var signedOverFlow = Int8.min  // signedOverFlow等於Int8可容納的最小整數-128
signedOverFlow = signedOverFlow &- 1  // 此時signedOverFlow等於127
print("Int8下溢後的值:\(signedOverFlow)")
---
output: Int8下溢後的值127

Int8型整數可容納的最小值為-128,二進位為10000000。使用溢位減法運算子減1時,符號位會翻轉,得到二進位01111111,即十進位127,這也是Int8型整數可容納的最大值。

Over flow signed subtraction

對於無號與有號整數型別,當發生上溢時,會從可容納的最大值變成最小值;發生下溢時,則從最小值變成最大值。


優先順序與結合性

運算子的優先順序決定某些運算子會優先於其他運算子執行,也就是優先順序高的運算子會先被計算。

結合性定義了相同優先順序的運算子如何結合,也就是與左邊結合為一組還是與右邊結合為一組。

在考慮複合運算式的計算順序時,運算子的優先順序與結合性非常重要。例如,運算子優先順序解釋了為什麼下列運算式的結果會是17。

var result = 2 + 3 % 4 * 5  // result的值是17
print("2 + 3 % 4 * 5的值是:\(result)")
---
output: 2 + 3 % 4 * 5的值是:17

若直接從左到右運算,可能會認為計算過程如下:

  • 2 + 3 = 5
  • 5 % 4 = 1
  • 1 * 5 = 5

但正確答案是17而非5。優先順序高的運算子要先於優先順序低的運算子計算。與C語言類似,在Swift中,乘法運算子*與取餘運算子%的優先順序高於加法運算子+。因此它們會先計算。

而乘法與取餘運算子的優先順序相同,這時為了得到正確的運算順序,還需考慮結合性。乘法與取餘運算子皆為左結合。可將這視為從左邊開始,對這兩部分運算式隱式加上括號。即 2 + 3 % 4 * 5 ==> 2 + ((3 % 4) * 5) ==> 2 + (3 * 5) ==> 2 + 15 ==> 17。因此結果為17。


運算子函式

類別與結構可為現有運算子自訂實作,這通常稱為運算子的多載。

下列範例展示如何讓自訂結構支援加法運算子+。算術加法運算子是二元運算子,因為它對兩個值進行運算,也可稱為中置運算子,因為它出現在兩個值之間。

範例中定義了一個名為Vector2D的結構用來表示二維座標向量(x, y),接著定義了一個可將兩個Vector2D結構實例相加的運算子函式。

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型別的值。

在此實作中,輸入參數分別命名為leftright,代表+運算子左邊與右邊的兩個Vector2D實例,這個實例的xy分別等於兩個參數的xy之和。

此型別方法可在任意兩個Vector2D實例之間作為中置運算子使用。

let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector  // combinedVector為新的Vector2D實例,值為(5.0, 5.0)
print("兩個Vector2D型別向量相加後的值為:\(combinedVector)")
---
output: 兩個Vector2D型別向量相加後的值為Vector2D(x: 5.0, y: 5.0)

前置與後置運算子

上一個範例示範了二元中置運算子的自訂實作,類別與結構也能提供標準一元運算子的實作。一元運算子只運算一個值。當運算子出現在值之前時,為前置(如-a);出現在值之後時,為後置(如b!)。

要實作前置或後置運算子,需在宣告運算子函式時於func關鍵字前加上prefixpostfix修飾詞。

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

上述程式碼為Vector2D型別實作了一元運算子(-a)。由於此運算子為前置運算子,因此函式需加上prefix修飾詞。

對於單純數值,一元負號運算子可改變其正負號。對Vector2D而言,此運算會將其xy屬性的正負號都改變。

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive  // negative為(-3.0, -4.0)的Vector2D實例
let alsoPositive = -negative  // alsoPositive為(3.0, 4.0)的Vector2D實例
print("-positive的值為:\(negative)")
print("-negative的值為:\(alsoPositive)")
---
output: -positive的值為Vector2D(x: -3.0, y: -4.0)
-negative的值為Vector2D(x: 3.0, y: 4.0)

複合賦值運算子

複合賦值運算子將賦值運算子=與其他運算子結合。例如,加法與賦值結合成加法賦值運算子+=。實作時,需將運算子的左參數設為inout型別,因為此參數的值會在運算子函式內直接被修改。

下列範例對Vector2D實例實作加法賦值運算子函式。

extension Vector2D {
        static func += (left: inout Vector2D, right: Vector2D) {
                left = left + right  // 因為Vector2D的加法運算已定義,可直接利用現有加法運算子函式
        }
}

var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd  // original的值現在是(4.0, 6.0)
print("Vector2D型別加法賦值運算後的值為:\(original)")
---
output: Vector2D型別加法賦值運算後的值為Vector2D(x: 4.0, y: 6.0)

注意

不能對預設的賦值運算子=進行多載。僅複合賦值運算子可被多載。同樣也無法對三元條件運算子(a ? b : c)進行多載。

等價運算子

通常自訂類別與結構不會預設實作等價運算子,等價運算子通常指相等運算子==與不等運算子!=

若要使用等價運算子對自訂型別進行判等,需為「相等」運算子自訂實作,實作方式與其他中置運算子相同,並需遵循標準函式庫的Equatable協定。

extension Vector2D: Equatable {  // 遵循Equatable協定即可包含對「相等」運算子(==)與「不等」運算子(!=)的實作
        static func == (left: Vector2D, right: Vector2D) -> Bool {
                return (left.x == right.x) && (left.y == right.y)
        }
}

上述程式碼實作了「相等」運算子(==)來判斷兩個Vector2D實例是否相等。對Vector2D而言,「相等」代表「兩個實例的x與y都相等」,這也是程式碼中用來判等的邏輯。若已實作「相等」運算子,通常不需再實作「不等」運算子(!=)。標準函式庫對「不等」運算子提供預設實作,會將「相等」運算子的結果取反後回傳。

let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
        print("這兩個向量相等!")
}
let threeTwo = Vector2D(x: 3.0, y: 2.0)
if twoThree != threeTwo {  // 已實作「相等」運算子,故不需再實作「不等」運算子
        print("這兩個向量不相等!")
}
---
output: 這兩個向量相等!
這兩個向量不相等!

多數簡單情況下,可讓Swift自動合成等價運算子的實作(只需遵循Equatable協定即可)。


自訂運算子

除了實作標準運算子,Swift中也可宣告與實作自訂運算子。

新的運算子需使用operator關鍵字在全域範疇內定義,並指定prefix(前置)、infix(中置)或postfix(後置)修飾詞。

prefix operator +++  // 定義一個新的名為`+++`的前置運算子

上述程式碼定義了一個新的名為+++的前置運算子。此運算子在Swift中並無既定意義,因此可針對Vector2D實例的特定情境給予自訂意義。對此範例而言,+++被實作為「前置自增」運算子。它利用前面定義的複合加法運算子讓向量與自身相加,使Vector2D實例的xy屬性值都加倍。可如下為Vector2D新增+++型別方法來實作。

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與afterDoubling現在的值皆為(2.0, 8.0)
print("toBeDoubled的值為:\(toBeDoubled)")
print("afterDoubling的值為:\(afterDoubling)")
---
output: toBeDoubled的值為Vector2D(x: 2.0, y: 8.0)
afterDoubling的值為Vector2D(x: 2.0, y: 8.0)

自訂中置運算子的優先順序

每個自訂中置運算子都屬於某個優先順序群組。優先順序群組指定此運算子相對於其他中置運算子的優先順序與結合性。

未明確指定優先順序群組的自訂中置運算子會被歸入預設優先順序群組,其優先順序高於三元運算子。

以下範例定義了一個新的自訂中置運算子+-,此運算子屬於AdditionPrecedence優先群組。

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為Vector2D實例,值為(4.0, -2.0)
print("plusMinusVector的值為:\(plusMinusVector)")
---
output: plusMinusVector的值為Vector2D(x: 4.0, y: -2.0)

此運算子將兩個向量的x值相加,並從第一個向量的y值減去第二個向量的y。因其本質屬於「加法型」運算子,故將其歸於與+-等預設中置「加法型」運算子相同的優先順序群組(AdditionPrecedence)。

注意

定義前置與後置運算子時,無需指定優先順序。然而,若同時對一個值使用前置與後置運算子,則後置運算子會先運算。


結果建構器

結果建構器是一種自訂型別,支援以自然的宣告式語法建立類似清單或樹狀等巢狀資料。使用結果建構器時,程式碼可包含一般Swift語法,例如用於條件判斷的if,或處理重複資料的for

下列程式碼定義了一些型別用於繪製星號線段與文字線段。

protocol Drawable {
        func draw() -> String
}
struct Line: Drawable {
        var elements: [Drawable]
        func draw() -> String {
                return elements.map { $0.draw() }.joined(separator: "")  // 繪製Line時,會呼叫每個元素的draw(),再將所有結果字串串接成單一字串
        }
}
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結構包裝另一個可繪製元素,並將其中所有文字轉為大寫。

可組合這些型別的建構器來建立可繪製元素。

let name: String? = "Jensen Jon"
let manualDrawing = Line(elements: [Stars(length: 3), Text("Hello"), Space(), AllCaps(content: Text((name ?? "World") + "!")), Stars(length: 2)])
print(manualDrawing.draw())  // 輸出“***Hello JENSEN JON!**”
---
output: ***Hello JENSEN JON!**

程式碼沒問題,但不夠優雅。AllCaps後方的括號巢狀太深,可讀性不佳。namenil時使用「World」的備用邏輯必須依賴??運算子,邏輯複雜時更難閱讀。若還需switchfor迴圈來組成繪製的一部分,會更難撰寫。使用結果建構器可將這類程式碼重構得更像一般Swift程式。

在型別定義上加上@resultBuilder屬性即可定義結果建構器。例如下列程式碼定義了允許以宣告式語法描述繪製的結果建構器DrawingBuilder

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

DrawingBuilder結構定義了三個方法來實作部分結果建構器語法。

buildBlock(_:)方法支援在方法區塊中撰寫多行程式碼。它會將區塊中的多個元素組合成LinebuildEither(first:)buildEither(second:)方法則支援if-else語法。

可在函式參數上套用@DrawingBuilder屬性,會將傳入函式的閉包轉換為用結果建構器建立的值。

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())  // 輸出“***Hello WORLD!**”

let personalGreeting = makeGreeting(for: "Jensen Jon")
print(personalGreeting.draw())  // 輸出“***Hello JENSEN JON!**”
---
output: ***Hello WORLD!**
***Hello JENSEN JON!**

makeGreeting(for:)函式會將傳入的name參數用於繪製個人化問候語。

draw(_:)caps(_:)函式皆傳入套用@DrawingBuilder屬性的單一閉包參數。呼叫這些函式時,需使用DrawingBuilder定義的特殊語法。Swift會將繪製的宣告式描述轉換為一連串DrawingBuilder方法呼叫,組成最終傳入函式的參數值。例如,Swift會將範例中的caps(_:)呼叫轉換如下:

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(:_)方法。

extension DrawingBuilder {
        static func buildArray(_ components: [Drawable]) -> Drawable {
                return Line(elements: components)
        }
}
func makeStars() -> Drawable {
        let manyStars = draw {
                Text("Stars:")
                for length in 1...3 {
                        Space()
                        Stars(length: length)  // for迴圈中會自動呼叫buildArray(_:)方法
                }
        }
        return manyStars
}
let generateStars = makeStars()
print(generateStars.draw())
---
output: Stars: * ** ***

上述程式碼中,利用for迴圈建立一個繪製陣列,buildArray(_:)方法會將該陣列組成Line

最後更新於