1. 程式人生 > >Swift 列舉

Swift 列舉

感謝原作者 連結:http://www.cocoachina.com/newbie/basic/2014/0611/8771.html

列舉定義了一個通用型別的一組相關的值,使你可以在你的程式碼中以一個安全的方式來使用這些值。

如果你熟悉 C 語言,你就會知道,在 C 語言中列舉指定相關名稱為一組整型值。Swift 中的列舉更加靈活,不必給每一個列舉成員(enumeration member)提供一個值。如果一個值(被認為是“原始”值)被提供給每個列舉成員,則該值可以是一個字串,一個字元,或是一個整型值或浮點值。 此外,列舉成員可以指定任何型別的關聯值儲存到列舉成員值中,就像其他語言中的聯合體(unions)和變體(variants)。你可以定義一組通用的相關成員作為列舉的一部分,每一組都有不同的一組與它相關的適當型別的數值。 在 Swift 中,列舉型別是一等(first-class)型別。它們採用了很多傳統上只被類所支援的特徵,例如計算型屬性(computed properties),用於提供關於列舉當前值的附加資訊,例項方法(instance methods),用於提供和列舉所代表的值相關聯的功能。列舉也可以定義構造器(initializers)來提供一個初始成員值;可以在原始的實現基礎上擴充套件它們的功能;可以遵守協議(protocols)來提供標準的功能。 欲瞭解更多相關功能,請參考屬性,方法,構造過程,擴充套件,和協議。 列舉語法(Enumeration Syntax)
使用enum關鍵詞並且把它們的整個定義放在一對大括號內:
  1. enum SomeEumeration { 
  2.     // enumeration definition goes here
以下是指南針四個方向的一個例子: 
  1. enum CompassPoint { 
  2.     case North 
  3.     case South 
  4.     case East 
  5.     case West 
一個列舉中被定義的值(例如 North,South,East和West)是列舉的成員值(或者成員)。case關鍵詞表明新的一行成員值將被定義。 注意: 不像 C 和 Objective-C 一樣,Swift 的列舉成員在被建立時不會被賦予一個預設的整數值。在上面的CompassPoints例子中,North,South,East和West不是隱式得等於0,1,2和3。相反的,這些不同的列舉成員在CompassPoint的一種顯示定義中擁有各自不同的值。 多個成員值可以出現在同一行上,用逗號隔開:
  1. enum
     Planet { 
  2.     case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Nepturn 
每個列舉定義了一個全新的型別。像 Swift 中其他型別一樣,它們的名字(例如CompassPoint和Planet)必須以一個大寫字母開頭。給列舉型別起一個單數名字而不是複數名字,以便於讀起來更加容易理解:
  1. var directionToHead = CompassPoint.West 
directionToHead的型別被推斷當它被CompassPoint的一個可能值初始化。一旦directionToHead被宣告為一個CompassPoint,你可以使用更短的點(.)語法將其設定為另一個CompassPoint的值:
  1. directionToHead = .East 
directionToHead的型別已知時,當設定它的值時,你可以不再寫型別名。使用顯示型別的列舉值可以讓程式碼具有更好的可讀性。 匹配列舉值和Switch語句(Matching Enumeration Values with a Switch Statement) 你可以匹配單個列舉值和switch語句: 
  1. directionToHead = .South 
  2. switch directionToHead { 
  3. case .North: 
  4.     println("Lots of planets have a north"
  5. case .South: 
  6.     println("Watch out for penguins"
  7. case .East: 
  8.     println("Where the sun rises"
  9. case .West: 
  10.     println("Where the skies are blue"
  11. // prints "Watch out for penguins”
你可以如此理解這段程式碼: “考慮directionToHead的值。當它等於.North,列印“Lots of planets have a north”。當它等於.South,列印“Watch out for penguins”。” 等等依次類推。 正如在控制流中介紹,當考慮一個列舉的成員們時,一個switch語句必須全面。如果忽略了.West這種情況,上面那段程式碼將無法通過編譯,因為它沒有考慮到CompassPoint的全部成員。全面性的要求確保了列舉成員不會被意外遺漏。 當不需要匹配每個列舉成員的時候,你可以提供一個預設default分支來涵蓋所有未明確被提出的任何成員:
  1. let somePlanet = Planet.Earth 
  2. switch somePlanet { 
  3. case .Earth: 
  4.     println("Mostly harmless"
  5. default
  6.     println("Not a safe place for humans"
  7. // prints "Mostly harmless”
關聯值(Associated Values) 上一小節的例子演示了一個列舉的成員是如何被定義(分類)的。你可以為Planet.Earth設定一個常量或則變數,並且在之後檢視這個值。然而,有時候會很有用如果能夠把其他型別的關聯值和成員值一起儲存起來。這能讓你隨著成員值儲存額外的自定義資訊,並且當每次你在程式碼中利用該成員時允許這個資訊產生變化。 你可以定義 Swift 的列舉儲存任何型別的關聯值,如果需要的話,每個成員的資料型別可以是各不相同的。列舉的這種特性跟其他語言中的可辨識聯合(discriminated unions),標籤聯合(tagged unions),或者變體(variants)相似。 例如,假設一個庫存跟蹤系統需要利用兩種不同型別的條形碼來跟蹤商品。有些商品上標有 UPC-A 格式的一維碼,它使用數字0到9.每一個條形碼都有一個代表“數字系統”的數字,該數字後接10個代表“識別符號”的數字。最後一個數字是“檢查”位,用來驗證程式碼是否被正確掃描: 其他商品上標有 QR 碼格式的二維碼,它可以使用任何 ISO8859-1 字元,並且可以編碼一個最多擁有2,953字元的字串: 對於庫存跟蹤系統來說,能夠把 UPC-A 碼作為三個整型值的元組,和把 QR 碼作為一個任何長度的字串儲存起來是方便的。 在 Swift 中,用來定義兩種商品條碼的列舉是這樣子的:
  1. enum Barcode { 
  2.     case UPCA(Int, Int, Int) 
  3.     case QRCode(String) 
以上程式碼可以這麼理解: “定義一個名為Barcode的列舉型別,它可以是UPCA的一個關聯值(Int,Int,Int),或者QRCode的一個字串型別(String)關聯值。” 這個定義不提供任何Int或String的實際值,它只是定義了,當Barcode常量和變數等於Barcode.UPCA或Barcode.QRCode時,關聯值的型別。 然後可以使用任何一種條碼型別建立新的條碼,如:
  1. var productBarcode = Barcode.UPCA(8, 85909_51226, 3) 
以上例子建立了一個名為productBarcode的新變數,並且賦給它一個Barcode.UPCA的關聯元組值(8, 8590951226, 3)。提供的“識別符號”值在整數字中有一個下劃線,使其便於閱讀條形碼。 同一個商品可以被分配給一個不同型別的條形碼,如:
  1. productBarcode = .QRCode("ABCDEFGHIJKLMNOP"
這時,原始的Barcode.UPCA和其整數值被新的Barcode.QRCode和其字串值所替代。條形碼的常量和變數可以儲存一個.UPCA或者一個.QRCode(連同它的關聯值),但是在任何指定時間只能儲存其中之一。 像以前那樣,不同的條形碼型別可以使用一個switch語句來檢查,然而這次關聯值可以被提取作為switch語句的一部分。你可以在switch的case分支程式碼中提取每個關聯值作為一個常量(用let字首)或者作為一個變數(用var字首)來使用:
  1. switch productBarcode { 
  2. case .UPCA(let numberSystem, let identifier, let check): 
  3.     println("UPC-A with value of \(numberSystem), \(identifier), \(check)."
  4. case .QRCode(let productCode): 
  5.     println("QR code with value of \(productCode)."
  6. // prints "QR code with value of ABCDEFGHIJKLMNOP.”
如果一個列舉成員的所有關聯值被提取為常量,或者它們全部被提取為變數,為了簡潔,你可以只放置一個var或者let標註在成員名稱前:
  1. switch productBarcode { 
  2. case let .UPCA(numberSystem, identifier, check): 
  3.     println("UPC-A with value of \(numberSystem), \(identifier), \(check)."
  4. case let .QRCode(productCode): 
  5.     println("QR code with value of \(productCode)."
  6. // prints "QR code with value of ABCDEFGHIJKLMNOP."
原始值(Raw Values) 在關聯值小節的條形碼例子中演示了一個列舉的成員如何宣告它們儲存不同型別的關聯值。作為關聯值的替代,列舉成員可以被預設值(稱為原始值)預先填充,其中這些原始值具有相同的型別。 這裡是一個列舉成員儲存原始 ASCII 值的例子:
  1. enum ASCIIControlCharacter: Character { 
  2.     case Tab = "\t"
  3.     case LineFeed = "\n"
  4.     case CarriageReturn = "\r"
在這裡,稱為ASCIIControlCharacter的列舉的原始值型別被定義為字元型Character,並被設定了一些比較常見的 ASCII 控制字元。字元值的描述請詳見字串和字元章節。 注意,原始值和關聯值是不相同的。當你開始在你的程式碼中定義列舉的時候原始值是被預先填充的值,像上述三個 ASCII 碼。對於一個特定的列舉成員,它的原始值始終是相同的。關聯值是當你在建立一個基於列舉成員的新常量或變數時才會被設定,並且每次當你這麼做得時候,它的值可以是不同的。 原始值可以是字串,字元,或者任何整型值或浮點型值。每個原始值在它的列舉宣告中必須是唯一的。當整型值被用於原始值,如果其他列舉成員沒有值時,它們會自動遞增。 下面的列舉是對之前Planet這個列舉的一個細化,利用原始整型值來表示每個 planet 在太陽系中的順序:
  1. enum Planet: Int { 
  2.     case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune 
自動遞增意味著Planet.Venus的原始值是2,依次類推。 使用列舉成員的toRaw方法可以訪問該列舉成員的原始值:
  1. let earthsOrder = Planet.Earth.toRaw() 
  2. // earthsOrder is 3
使用列舉的fromRaw方法來試圖找到具有特定原始值的列舉成員。這個例子通過原始值7識別Uranus:
  1. let possiblePlanet = Planet.fromRaw(7) 
  2. // possiblePlanet is of type Planet? and equals Planet.Uranus
然而,並非所有可能的Int值都可以找到一個匹配的行星。正因為如此,fromRaw方法可以返回一個可選的列舉成員。在上面的例子中,possiblePlanet是Planet?型別,或“可選的Planet”。 如果你試圖尋找一個位置為9的行星,通過fromRaw返回的可選Planet值將是nil:
  1. let positionToFind = 9 
  2. if let somePlanet = Planet.fromRaw(positionToFind) { 
  3.     switch somePlanet { 
  4.     case .Earth: 
  5.         println("Mostly harmless"
  6.     default
  7.         println("Not a safe place for humans"
  8.     } 
  9. else { 
  10.     println("There isn't a planet at position \(positionToFind)"
  11. // prints "There isn't a planet at position 9
這個範例使用可選繫結(optional binding),通過原始值9試圖訪問一個行星。if let somePlanet = Planet.fromRaw(9)語句獲得一個可選Planet,如果可選Planet可以被獲得,把somePlanet設定成該可選Planet的內容。在這個範例中,無法檢索到位置為9的行星,所以else分支被執行。