Swift4.2語言規範(十) 枚舉
一個枚舉定義了一個通用型的一組相關的值,使你在你的代碼中的一個類型安全的方式這些值來工作。
如果您熟悉C,您將知道C枚舉將相關名稱分配給一組整數值。Swift中的枚舉更加靈活,並且不必為枚舉的每個案例提供值。如果一個值(被稱為“原始”的值)被提供給每個枚舉的情況下,該值可以是一個字符串,一個字符,或任何整數的值或者浮點型。
或者,枚舉情況可以指定要與每個不同的案例值一起存儲的任何類型的關聯值,就像其他語言中的聯合或變體一樣。您可以將一組通用的相關案例定義為一個枚舉的一部分,每個枚舉都有一組與之關聯的適當類型的不同值。
Swift中的枚舉本身就是一流的類型。它們采用傳統上僅由類支持的許多功能,例如計算屬性以提供有關枚舉當前值的其他信息,以及實例方法以提供與枚舉所代表的值相關的功能。
有關這些功能的更多信息,請參閱屬性,方法,初始化,擴展和協議。
枚舉語法
您將使用enum
關鍵字引入枚舉,並將其整個定義放在一對大括號中:
1 enum SomeEnumeration { 2 // enumeration definition goes here 3 }
以下是指南針的四個要點的示例:
1 enum CompassPoint { 2 case north 3 case south 4 case east5 case west 6 }
在枚舉定義的值(例如north
,south
,east
,和west
)是其枚舉的情況下。您可以使用該case
關鍵字來引入新的枚舉案例。
註意
與C和Objective-C不同,Swift枚舉情況在創建時未分配默認整數值。在CompassPoint
上面的例子,north
,south
,east
和west
不等於隱式0
,1
,2
和3
。相反,不同的枚舉案例本身就是完全成熟的值,具有明確定義的類型CompassPoint
。
多個案例可以出現在一行中,以逗號分隔:
1 enum Planet { 2 case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune3 }
每個枚舉定義都定義了一個新類型。與Swift中的其他類型一樣,它們的名稱(例如CompassPoint
和Planet
)應以大寫字母開頭。給出枚舉類型單數而不是復數名稱,以便它們看起來不言自明:
var directionToHead = CompassPoint.west
directionToHead
當使用其中一個可能的值初始化時推斷出類型CompassPoint
。一旦directionToHead
聲明為a CompassPoint
,您可以CompassPoint
使用較短的點語法將其設置為不同的值:
directionToHead = .east
類型directionToHead
已知,因此您可以在設置其值時刪除該類型。在使用顯式類型的枚舉值時,這會產生高度可讀的代碼。
使用Switch語句匹配枚舉值
您可以將單個枚舉值與switch
語句匹配:
1 directionToHead = .south 2 switch directionToHead { 3 case .north: 4 print("Lots of planets have a north") 5 case .south: 6 print("Watch out for penguins") 7 case .east: 8 print("Where the sun rises") 9 case .west: 10 print("Where the skies are blue") 11 } 12 // Prints "Watch out for penguins"
您可以將此代碼讀作:
“依據值directionToHead
。在它等於的情況下.north
,打印。在它等於的情況下,打印。“"Lots of planets have a north"
.south
"Watch out for penguins"
…等等。
如控制流程中所述,switch
在考慮枚舉的情況時,語句必須是詳盡的。如果省略case
for .west
,則此代碼不會編譯,因為它不考慮完整的CompassPoint
案例列表。要求詳盡無遺確保枚舉案例不會被意外遺漏。
如果不適合case
為每個枚舉案例提供一個,您可以提供一個default
案例來涵蓋未明確解決的任何案例:
1 let somePlanet = Planet.earth 2 switch somePlanet { 3 case .earth: 4 print("Mostly harmless") 5 default: 6 print("Not a safe place for humans") 7 } 8 // Prints "Mostly harmless"
叠代枚舉案例
對於某些枚舉,擁有所有枚舉的案例集合很有用。您可以通過在枚舉名稱後面寫入來啟用它。Swift將所有案例的集合公開為枚舉類型的屬性。這是一個例子:: CaseIterable
allCases
1 enum Beverage: CaseIterable { 2 case coffee, tea, juice 3 } 4 let numberOfChoices = Beverage.allCases.count 5 print("\(numberOfChoices) beverages available") 6 // Prints "3 beverages available"
在上面的示例中,您編寫Beverage.allCases
以訪問包含Beverage
枚舉的所有情況的集合。您可以allCases
像任何其他集合一樣使用- 集合的元素是枚舉類型的實例,因此在這種情況下,它們是Beverage
值。上面的示例計算了有多少個案例,下面的示例使用for
循環來叠代所有案例。
1 for beverage in Beverage.allCases { 2 print(beverage) 3 } 4 // coffee 5 // tea 6 // juice
上面示例中使用的語法將枚舉標記為符合CaseIterable
協議。有關協議的信息,請參閱協議。
關聯值
上一節中的示例顯示了枚舉的情況本身是如何定義(和類型化)的值。您可以將常量或變量設置為Planet.earth
,並在以後檢查此值。但是,有時能夠在這些案例值旁邊存儲其他類型的關聯值。這使您可以存儲其他自定義信息以及案例值,並允許每次在代碼中使用該案例時此信息都會有所不同。
您可以定義Swift枚舉以存儲任何給定類型的關聯值,並且如果需要,每種枚舉情況的值類型可以不同。與這些類似的枚舉稱為區別聯合,標記聯合或其他編程語言中的變體。
例如,假設庫存跟蹤系統需要通過兩種不同類型的條形碼跟蹤產品。有些產品標有UPC格式的1D條形碼,使用數字0
來表示9
。每個條形碼都有一個“數字系統”數字,後面跟著五個“制造商代碼”數字和五個“產品代碼”數字。然後是“檢查”數字,以驗證代碼是否已正確掃描:
![技術分享圖片](https://docs.swift.org/swift-book/_images/barcode_UPC_2x.png)
其他產品使用QR碼格式的二維條碼進行標記,可以使用任何ISO 8859-1字符,並且可以編碼長達2,953個字符的字符串:
![技術分享圖片](https://docs.swift.org/swift-book/_images/barcode_QR_2x.png)
對於庫存跟蹤系統來說,能夠將UPC條形碼存儲為四個整數的元組以及QR碼條形碼作為任意長度的字符串將是方便的。
在Swift中,定義任一類型的產品條形碼的枚舉可能如下所示:
1 enum Barcode { 2 case upc(Int, Int, Int, Int) 3 case qrCode(String) 4 }
這可以理解為:
“定義枚舉類型Barcode
,它可以采取任何的值upc
與式(的相關值Int
,Int
,Int
,Int
),或的值qrCode
與類型的相關聯的值String
。”
此定義不提供任何實際值Int
或String
值 - 它只定義常量和變量在等於或時可以存儲的關聯值的類型。Barcode
Barcode.upc
Barcode.qrCode
然後可以使用以下任一類型創建新的條形碼:
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
此示例創建一個名為的新變量,productBarcode
並為其賦值Barcode.upc
一個相關的元組值。(8, 85909, 51226, 3)
可以為同一產品分配不同類型的條形碼:
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
此時,原始Barcode.upc
值及其整數值將替換為new Barcode.qrCode
及其字符串值。類型的常量和變量Barcode
可以存儲a .upc
或a .qrCode
(及其相關值),但它們只能在任何給定時間存儲其中一個。
與以前一樣,可以使用switch語句檢查不同的條形碼類型。但是,這次可以將關聯值提取為switch語句的一部分。您將每個關聯值提取為常量(帶let
前綴)或變量(帶var
前綴),以便在switch
案例正文中使用:
1 switch productBarcode { 2 case .upc(let numberSystem, let manufacturer, let product, let check): 3 print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") 4 case .qrCode(let productCode): 5 print("QR code: \(productCode).") 6 } 7 // Prints "QR code: ABCDEFGHIJKLMNOP."
如果枚舉案例的所有關聯值都被提取為常量,或者如果所有這些值都被提取為變量,則可以在案例名稱前面放置單個var
或let
註釋,以簡潔起見:
1 switch productBarcode { 2 case let .upc(numberSystem, manufacturer, product, check): 3 print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).") 4 case let .qrCode(productCode): 5 print("QR code: \(productCode).") 6 } 7 // Prints "QR code: ABCDEFGHIJKLMNOP."
默認值
“ 關聯值 ”中的條形碼示例顯示了枚舉的情況如何聲明它們存儲了不同類型的關聯值。作為關聯值的替代,枚舉情況可以預先填充默認值(稱為原始值),它們都是相同的類型。
這是一個存儲原始ASCII值和命名枚舉情況的示例:
1 enum ASCIIControlCharacter: Character { 2 case tab = "\t" 3 case lineFeed = "\n" 4 case carriageReturn = "\r" 5 }
這裏,被調用枚舉的原始值ASCIIControlCharacter
被定義為類型Character
,並被設置為一些更常見的ASCII控制字符。字符串和字符Character
中描述了值。
原始值可以是字符串,字符或任何整數或浮點數類型。每個原始值在其枚舉聲明中必須是唯一的。
註意
原始值是不一樣的關聯值。當您首次在代碼中定義枚舉時,原始值將設置為預填充值,如上面的三個ASCII代碼。特定枚舉情況的原始值始終相同。根據枚舉的情況創建新常量或變量時,將設置關聯值,每次執行此操作時可能會有所不同。
隱含地分配默認值
當您使用存儲整數或字符串原始值的枚舉時,您不必為每個案例顯式分配原始值。如果不這樣做,Swift將自動為您分配值。
例如,當整數用於原始值時,每個案例的隱含值比前一個案例多一個。如果第一種情況沒有設置值,則其值為0
。
下面的枚舉是先前Planet
枚舉的細化,整數原始值表示來自太陽的每個行星的順序:
1 enum Planet: Int { 2 case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune 3 }
在上面的示例中,Planet.mercury
具有顯式原始值1
,Planet.venus
具有隱式原始值2
,等等。
當字符串用於原始值時,每個案例的隱含值是該案例名稱的文本。
下面的枚舉是前面CompassPoint
枚舉的細化,字符串原始值表示每個方向的名稱:
1 enum CompassPoint: String { 2 case north, south, east, west 3 }
在上面的示例中,CompassPoint.south
隱含原始值為"south"
,依此類推。
您可以使用其rawValue
屬性訪問枚舉案例的原始值:
1 let earthsOrder = Planet.earth.rawValue 2 // earthsOrder is 3 3 4 let sunsetDirection = CompassPoint.west.rawValue 5 // sunsetDirection is "west"
默認值初始化
如果使用默認值類型定義枚舉,則枚舉會自動接收一個初始值設定項,該初始值設定項接受默認值類型的值(作為參數調用rawValue
)並返回枚舉大小寫或nil
。您可以使用此初始化程序嘗試創建枚舉的新實例。
這個例子從原始值中識別天王星7
:
1 let possiblePlanet = Planet(rawValue: 7) 2 // possiblePlanet is of type Planet? and equals Planet.uranus
然而,並非所有可能的Int
值都會找到匹配的行星。因此,原始值初始值設定項始終返回可選的枚舉大小寫。在上面的示例中,possiblePlanet
是類型Planet?
或“可選” Planet
。
註意
原始值初始化程序是一個可用的初始化程序,因為並非每個原始值都將返回枚舉情況。有關更多信息,請參閱Failable Initializers。
如果您嘗試查找位置為的行星,則原始值初始值設定項返回11
的可選Planet
值將為nil
:
1 let positionToFind = 11 2 if let somePlanet = Planet(rawValue: positionToFind) { 3 switch somePlanet { 4 case .earth: 5 print("Mostly harmless") 6 default: 7 print("Not a safe place for humans") 8 } 9 } else { 10 print("There isn‘t a planet at position \(positionToFind)") 11 } 12 // Prints "There isn‘t a planet at position 11"
此示例使用可選綁定嘗試訪問原始值為的行星11
。該語句創建一個可選項,並設置為該可選項的值(如果可以檢索它)。在這種情況下,無法檢索位置為的行星,因此執行分支。
if let somePlanet = Planet(rawValue: 11)PlanetsomePlanetPlanet11else
遞歸枚舉
遞歸枚舉是具有枚舉作為一個或一個以上的枚舉案件相關聯的值的另一個實例的枚舉。您通過indirect
在它之前寫入來指示枚舉情況是遞歸的,這告訴編譯器插入必要的間接層。
例如,這是一個存儲簡單算術表達式的枚舉:
1 enum ArithmeticExpression { 2 case number(Int) 3 indirect case addition(ArithmeticExpression, ArithmeticExpression) 4 indirect case multiplication(ArithmeticExpression, ArithmeticExpression) 5 }
您還可以indirect
在枚舉開始之前編寫,以便為具有關聯值的所有枚舉案例啟用間接:
1 indirect enum ArithmeticExpression { 2 case number(Int) 3 case addition(ArithmeticExpression, ArithmeticExpression) 4 case multiplication(ArithmeticExpression, ArithmeticExpression) 5 }
此枚舉可以存儲三種算術表達式:普通數字,兩個表達式的相加以及兩個表達式的相乘。將addition
與multiplication
案件有關聯的,同時也是算術表達式,這些相關的值有可能嵌套的表達式的值。例如,表達式在乘法的右側有一個數字,在乘法的左側有另一個表達式。因為數據是嵌套的,用於存儲數據的枚舉也需要支持嵌套 - 這意味著枚舉需要遞歸。下面的代碼顯示了為以下內容創建的遞歸枚舉:(5 + 4) * 2
ArithmeticExpression
(5 + 4) * 2
1 let five = ArithmeticExpression.number(5) 2 let four = ArithmeticExpression.number(4) 3 let sum = ArithmeticExpression.addition(five, four) 4 let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
遞歸函數是處理具有遞歸結構的數據的簡單方法。例如,這是一個計算算術表達式的函數:
1 func evaluate(_ expression: ArithmeticExpression) -> Int { 2 switch expression { 3 case let .number(value): 4 return value 5 case let .addition(left, right): 6 return evaluate(left) + evaluate(right) 7 case let .multiplication(left, right): 8 return evaluate(left) * evaluate(right) 9 } 10 } 11 12 print(evaluate(product)) 13 // Prints "18"
此函數只需返回相關值即可評估普通數字。它通過評估左側的表達式,評估右側的表達式,然後將它們相加或相乘來評估加法或乘法。
Swift4.2語言規範(十) 枚舉