Swift 程式語言 — Enumeration

讓我們來講講如何使用 Swift 強大的 Enumeration 的類型。

Jeremy Xue
11 min readFeb 17, 2019

前言:

在部分程式語言中,有所謂的 Enum,也就是 Enumeration。我們能夠藉由 Enum 來自訂一些有規範的類型,讓我們在處理一些有規範類型的值的時候能夠更安全、方便,那接下來就讓我來使用探討 Enumeration 吧!

⎮ 枚舉 Enumeration

枚舉為了一組相關值定義了一個通用類型,使你能夠在程式碼中以類型安全的方式處理這些值。

如果你熟悉 C 語言,你將知道 C 語言中的枚舉將相關名稱分配給一組整數值。但枚舉在 Swift 更為靈活,並且不需要為每個案例( case )提供值。如果為每個枚舉的情況提供一個值( 原始值 ),則該值可以是字串、字符或任何整數或浮點數類型的值。

而且,枚舉的情況可以指定任意類型的值來與不同的情況值關聯儲存,這更像是其他語言中的 union 或 variant 的效果。你可以定義一組相關情況的合集作為枚舉的一部分,每一個情況都可以有不同類型的值的合集與其關聯。

⎮ 枚舉語法

你可以使用 enum 關鍵字來定義一個枚舉,然後將其所有的定義內容放在一個大括號中:

enum SomeEnumeration {
// enumeration definition goes here
}

這邊我們定義一個指南針的方位的範例:

枚舉中定義的值(像是 northsoutheastwest )是枚舉情況。你可以使用 case 關鍵字來引入新的情況。

與 C 和 Objective-C 等語言不同,Swift 的枚舉情況默認下沒有設置整數值。上述的 CompassPoint 範例中,north、south、east 和 west 不代表 0,1,2,3。相反的,不同的枚舉情況本身就是值,具有明確的定義 CompassPoint 類型。

多個情況值可以出現在同一行中,使用逗號隔開:

每個枚舉定義都定義了一個新的類型,如同其他 Swift 中的類型一樣,他們的名稱以大寫字母開頭,給枚舉類型起一個單數的而不是複數的名字,從而使得它們能夠顧名思義:

當我們使用 CompassPoint 的一個值初始化 directionToHead 時,其類型會被推斷出來。將 directionToHead 宣告為 CompassPoint 後,可以使用較短的點語法將其設置為不同的 CompassPoint 值:

因為 directionToHead 的類型是已知的,所以在設定它的值時你可以不用再寫一次其類型。這樣做可以使你在操作確定類型的枚舉時讓程式碼非常安全、易讀。

⎮使用 Switch 來匹配枚舉

你可以用 switch 語法來匹配單獨枚舉值:

這邊我們使用 switch 語法判斷 directionToHead 的值,如果是相對應的情況則印出該方位的訊息。所以,以我們這個範例來說,他會印出 .south 情況下的內容,小心企鵝?! ⚠️🐧(官方範例…)

而你可以發現我們這個 switchcase 並沒有 default 的情況。因為當我們已經覆蓋所有情況時,我們就不需要加上 default 的情況,來處理沒寫到的情況。

但假設我們今天沒有涵蓋他所有情況時,我們還是必須要加上 default 來處理沒涵蓋到的情況:

⎮遍歷枚舉情況

對於某些枚舉來說,如果能有一個集合包含了枚舉的所有情況就好了。你可以通過在枚舉名字後面寫 CaseIterable 來允許枚舉被遍歷。 Swift 將所有情況的集合為枚舉類型的 allCases 屬性:

當然我們能用一個迴圈來印出所有的情況:

而我們就會看見它印出 Beverage 中各個情況:

⎮關聯值 ( Associated Values )

有時將其它類型的關聯值與這些情況一起存儲是很有用的。這樣你就可以將額外的自定義信息和情況儲存再一起,並且允許你的程式碼中使用每次調用這個情況時都能使用它。

舉例,假設庫存追蹤系統需要兩種不同類型的條碼追蹤產品,一些產品貼的是用數字 0 到 9 的 UPC-A 格式一維條形碼。每個條碼數字都含有 “ 一個數字系統位 ”,接著是 “ 五個製造商代碼數字 ” 和 “ 五個產品代碼數字 ”。而最後則是一個 “ 檢測位 ” 來驗證代碼已經被正確掃描:

而其他產品使用 QRCode 的二維條碼進行標記,可以使用 ISO 8859–1 字符,並且可以編碼長達 2953 字符的字串。

這樣可以讓庫存追蹤系統很方便的以一個由 4 個整數組成的元組來儲存 UPC-A 條碼,然而 QRCode 則可以被存儲為一個任意長度的字串中。

在 Swift 中,為不同類型產品條碼定義枚舉大概是這樣:

這邊我們定義了一個名為 Barcode 的枚舉類型,他可以為 upc 的值,其關聯值的類型為(Int,Int,Int,Int),或者他也能為 qrCode 的值,其關聯值的類型為 String

在定義時不需要提供任何實際的 Int 或 String 值,它只定義 Barcode 在常數或變數等於 Barcode.upc 或是 Barcode.qrCode 時可以儲存關聯值的類型。

讓我們讓創建一個 upc 類型的條碼類型:

這邊我們創建一個名為 productBarcode 的變數,並為賦一個 Barcode.upc 的值,並且有一個元組的關聯值 (8,85909,51226,3)

當然我們也能賦予它不同的條碼類型:

這時 productBarcode 的值將會是 Barcode.qrCode ,其關聯值為 ABCDEFGHIJKLMNOP 的字串。

你可以使用 switch 語法來檢查不同的條碼類型,類似於用 switch 語句匹配枚舉值得範例。但是,我們可以將情況中的關聯值作為 switch 與句的一部份提取。你可以將每個關聯值提取為常數(使用 let 前綴)或是變數(使用 var 前綴)以在 switch case 中使用:

如果一個枚舉情況的所有的關聯值都被提取為常數或是變數,你可以使用一個單獨的 letvar 在該情況前標註即可:

⎮原始值 ( Raw Values )

關聯值中的 Barcode 範例演示了枚舉情況如何宣告其儲存不同類型的關聯值。而作為關聯值的另一種選擇,枚舉情況可以用相同類型的默認值預(原始值)。

這是一個存儲原始 ASCII 值和命名枚舉情況的範例:

這邊,名為 ASCIIControlCharacter 的枚舉的原始值被定義為 Character 類型,並且被設置一些更常見的 ASCII 控制字符。

原始值可以為字串、字符或是任何整數及浮點數類型。每個原始值在其枚舉宣告中必須是唯一的。

原始值與關聯值並不相同。原始值是當你第一次定義枚舉的時候,用來預先填充的值,如上面的三個 ASCII 碼範例,特定枚舉情況中的原始值是始終相同的。而關聯值在基於枚舉情況的其中之一創建新的常數或變數時設定,這麼做的時候其關聯值可以是不同的。

⎮ 隱式指定的原始值

當你在使用整數或字串原始值的枚舉時,你不必顯式地給每一種情況都分配一個原始值。當你沒有分配時,Swift 將會自動為你分配值。

例如,當 Int 作為原始值類型時,每種情況的隱含值比前一種情況多一。 如果第一種情況沒有設置值,則其值為 0。

下面的枚舉是前面的 Planet 枚舉的簡化版,用整數原始值來代表從太陽到每一個行星的順序:

可以看到我們 mercury 的原始值為 1,那麼 venus 的隱含值為 2,earth 為 3 ,以此類推…。

而當使用 String 作為原始值類型時,每個情況的隱含值為該情況的名稱,這邊我們用前面的 CompassPoint 作為範例:

所以,以這個範例來說,north 的隱含值為 north,south 的隱含值為 south,以此類推…。

而我們也能透過每個情況的 rawValue 來訪問每個情況的原始值:

⎮ 從原始值初始化

如果使用原始值定義枚舉,那麼沒舉會有一個接受原始值的初始化器,該初使化器會接受原始值類型的值( 作為名為 rawValue 的參數 )並返回該枚舉情況或是 nil

這邊你會發現這個是初始化器是返回一個 Planet? 而不是 Planet ,因為不是所有的原始值都有所對應的枚舉情況,所以經由初始化後的值可能會是 Planet? 的情況或是 nil

枚舉中的原始值初始化器是一個可失敗初始化器,因為不是所有原始值都會返回一個枚舉情況。

想當然的,如果這個初始化是有可能回傳可選類型的值的話,那麼我們當然可以使用可選綁定的方式來安全的處理它:

⎮ 遞歸枚舉

遞歸枚舉是一種將枚舉的另一個實例作為一個或是多個枚舉情況的關聯值的枚舉。你透過在其之前編寫 indirect 來表示枚舉情況是遞歸的,這告訴編輯器插入必要的間接層。

例如,這是一個儲存簡單算數表達式的枚舉:

你同樣可以在枚舉之前寫 indirect 來讓整個枚舉情況在需要時可以遞歸:

此枚舉可以儲存三種算數表達式,單個數字,兩個表達式的加法和兩個表達式的乘法。而加法和乘法的情況具有相關的值,這些值也算是算術表達式,這些相關值使其可能變為嵌套表達式。

例如,表達式 ( 5 + 4 ) * 2 在乘法的右側具有數字,而在其左側有另一個表達式。因為數據是嵌套的,所以用於儲存數據的枚舉也需要支持嵌套,這意味著枚舉需要被遞歸。

下面的程式碼顯示為 ( 5 + 4 ) * 2 創建的 ArithmeticExpression 的遞歸枚舉:

遞歸函數是處理具有遞歸結構的數據最直接的方式。例如,下面為一個計算算術表達式的函數:

這個函數透過直接返回關聯值來判斷普通數字。它透過衡量表達式左側和右側判斷是加法還是乘法,然後對它們相加或者相乘。

因此我們會透過其 evaluate 函數來進行遞迴,並且獲得其中的值來進行運算,最後會得到我們 ( 5 + 4 ) * 2 = 18 的結果。

後記:

枚舉在 Swift 中是一個非常好用且強大的類型,透過自定義的類型讓你在處理許多有規範的資料結構時非常好處理,透過枚舉讓你在使用特殊類型的值的時候更加安全快速,加上它還有許多關聯值或是原始值的操作方式,讓我們在使用枚舉類型時可以進行更多複雜的操作,那我們這次枚舉介紹就到這邊,希望大家學會如何使用它了 💪🏻。

--

--

Jeremy Xue
Jeremy Xue

Written by Jeremy Xue

Hi, I’m Jeremy. [好想工作室 — iOS Developer]

No responses yet