Swift5.2-枚舉(中文文檔)

引言

繼續學習Swift文檔,從上一章節:閉包,咱們學習了Swift閉包相關的內容,如閉包的定義和使用、閉包的簡寫、速記參數名稱、尾隨閉包、捕獲值、閉包的類型、轉義閉包(@escaping)和自動閉包(@autoclosure)等這些內容。如今,咱們學習Swift的枚舉相關的內容。因爲篇幅較長,這裏分篇來記錄,接下來,Fighting!html

若是你已經熟練掌握了枚舉的使用,那請移步下一章節:結構體和類express

枚舉

枚舉爲一組相關值定義了通用類型,並使您可以在代碼中以類型安全的方式使用這些值。編程

若是熟悉C,就知道C中的枚舉將相關名稱分配給一組整數值。 Swift中的枚舉更加靈活,無需爲每種枚舉提供值。 若是爲每種枚舉狀況提供了一個值(稱爲原始值),則該值能夠是字符串,字符或任何整數或浮點類型的值。swift

或者,枚舉案例能夠指定要存儲的任何類型的關聯值以及每一個不一樣的案例值,這與其餘語言中的並集或變體很類似。 您能夠將一組常見的相關案例定義爲一個枚舉的一部分,每一個案例都有一組與之關聯的不一樣類型的適當類型的值。api

Swift中的枚舉自己就是一流的類型。 它們採用了許多傳統上僅由類支持的功能(意思是能夠和類同樣提供相關功能),例如提供枚舉當前值的附加信息的計算屬性,以及提供與枚舉所表示的值相關的功能的實例方法。 枚舉還能夠定義初始值設定項以提供初始case值。 能夠擴展以擴展其功能,使其超出其最初的實現; 並能夠遵循協議以提供標準功能。安全

想知道更多的功能,請參閱PropertiesMethodsInitializationExtensions, 和 Protocols.bash

1 枚舉語法

您可使用enum關鍵字引入枚舉,並將其整個定義放在一對大括號內:閉包

enum SomeEnumeration {
    // enumeration definition goes here
}
複製代碼

這是指南針的四個要點的示例:less

enum CompassPoint {
    case north
    case south
    case east
    case west
}
複製代碼

枚舉裏定義的這些值(例如north, south, east, 和west) 是枚舉裏的cases。可使用case關鍵字新增cases。編程語言

注意
與C和Objective-C等語言不一樣,Swift枚舉案例默認狀況下未設置整數值。 在上面的CompassPoint示例中,北,南,東和西並不隱式等於0、一、2和3。相反,不一樣的枚舉狀況自己就是具備明肯定義的CompassPoint類型的值。

多個cases能夠寫在一行上,用逗號隔開:

enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
複製代碼

每一個枚舉定義都定義一個新類型。 與Swift中的其餘類型同樣,它們的名稱(例如CompassPoint和Planet)以大寫字母開頭。 給枚舉類型使用單數而不是複數的名稱,以方便閱讀:

var directionToHead = CompassPoint.west
複製代碼

當使用CompassPoint的可能值之一進行初始化時,會推斷directionToHead的類型。 將directionToHead聲明爲CompassPoint後,您可使用較短的點語法將其設置爲其餘CompassPoint值:

directionToHead = .east
複製代碼

directionToHead的類型是已知的,所以能夠在設置其值時將類型刪除。 當使用顯式類型的枚舉值時,這使得代碼易於閱讀。

2 使用switch匹配枚舉值

可使用switch匹配枚舉中不一樣的值:

directionToHead = .south
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}
// Prints "Watch out for penguins"
複製代碼

上面代碼能夠解讀爲:

判斷directionToHead的值,若是directionToHead等於.north,則打印"Lots of planets have a north"...以此類推。

控制流中所述,考慮枚舉的狀況時,switch語句必須是詳盡的。 若是省略.west的case,則此代碼不會編譯,由於它沒有考慮CompassPoint case的完整列表。 要求詳盡無遺,以確保不會意外省略枚舉case。

若是不適合爲每一個枚舉案例提供case,則能夠提供默認case以涵蓋全部未明確解決的案例:

let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("Mostly harmless")
default:
    print("Not a safe place for humans")
}
// Prints "Mostly harmless"
複製代碼

3 遍歷枚舉cases

對於某些枚舉,收集全部該枚舉的cases頗有用。 您能夠經過在枚舉名稱後輸入:CaseIterable來啓用此功能。 Swift將全部案例的集合做爲枚舉類型的allCases屬性公開。 這是一個例子:

enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"
複製代碼

在上面的示例中,您編寫Beverage.allCases來訪問包含全部Beverage枚舉案例的集合。 您能夠像使用其餘集合同樣使用allCases-該集合的元素是枚舉類型的實例,所以在這種狀況下,它們是「Beverage」值。 上面的示例計算了有多少個case,下面的示例使用for循環遍歷全部案例。

for beverage in Beverage.allCases {
    print(beverage)
}
// coffee
// tea
// juice
複製代碼

上面示例中使用的語法將枚舉標記爲遵循CaseIterable協議。 有關協議的信息,請參見Protocols

4 關聯值

上一節中的示例說明枚舉的cases自己就是已定義(和鍵入)的值。 您能夠將常量或變量設置爲Planet.earth,而後再檢查此值。 可是,有時能夠將其餘類型的值與這些案例值一塊兒存儲。 此附加信息稱爲關聯值,每次將這種狀況用做代碼中的值時,它都會有所不一樣。

您能夠定義Swift枚舉來存儲任何給定類型的關聯值,而且若是須要,每種枚舉的值類型能夠不一樣。 相似於這些的枚舉在其餘編程語言中被稱爲區分聯合,標記聯合或變體。

例如,假設庫存跟蹤系統須要經過兩種不一樣類型的條形碼來跟蹤產品。 某些產品用UPC格式的一維條形碼標記,其使用數字0到9。每一個條形碼都有一個數字系統數字,後跟五個製造商代碼數字和五個產品代碼數字。 這些後面跟一個校驗位,以驗證代碼是否已正確掃描:

其餘產品帶有QR碼格式的2D條形碼標籤,可使用任何ISO 8859-1字符,而且能夠編碼長達2,953個字符的字符串:

庫存跟蹤系統能夠方便地將UPC條形碼存儲爲四個整數的元組,並將QR碼條形碼存儲爲任意長度的字符串。

在Swift中,定義兩種類型產品條形碼的枚舉可能看起來像這樣:

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
複製代碼

能夠被解讀爲:

定義了一個叫Barcode的枚舉,帶有一個(Int, Int, Int, Int)類型的upc值和一個String類型的qrCode值。

此定義不提供任何實際的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及其整數值將替換爲新的Barcode.qrCode及其字符串值。 Barcode類型的常量和變量能夠存儲.upc或.qrCode(及其關聯值),但在任何給定時間只能存儲其中之一。

您可使用switch語句檢查不一樣的條形碼類型,相似於將枚舉值與Switch語句匹配中的示例。 可是,此次,關聯值被提取爲switch語句的一部分。 您能夠將每一個關聯的值提取爲常量(帶有let前綴)或變量(帶有var前綴),以供在switch case的主體內使用:

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
複製代碼

爲了簡化起見,若是枚舉案例的全部關聯值都提取爲常量,或者全部提取的變量都爲變量,則能夠在案例名稱前放置一個var或let批註:

switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
    print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
複製代碼

5 原始值

關聯值中的條形碼示例顯示瞭如何聲明枚舉的case存儲了不一樣類型的關聯值。 做爲關聯值的替代方法,枚舉案例能夠預先填充默認值(稱爲原始值),這些默認值都是相同的類型。

這是一個將原始ASCII值與命名枚舉cases一塊兒存儲的示例:

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
複製代碼

在這裏,被稱爲ASCIIControlCharacter的枚舉的原始值被定義爲Character類型,並被設置爲一些更常見的ASCII控制字符。 字符串和字符中描述了字符值。

原始值能夠是字符串,字符或任何整數或浮點數類型。 每一個原始值在其枚舉聲明中必須惟一。

注意
原始值與關聯值不一樣。 首次在代碼中定義枚舉時,原始值將設置爲預填充的值,例如上面的三個ASCII代碼。 特定枚舉狀況的原始值始終相同。 關聯值是在您根據枚舉的一種狀況建立新的常數或變量時設置的,而且每次均可以不一樣。

5.1 隱式設置原始值

當您使用存儲整數或字符串原始值的枚舉時,沒必要爲每種狀況顯式分配原始值。 不須要時,Swift會自動爲您分配值。

例如,當整數用於原始值時,每種狀況的隱式值都比前一種狀況大一。 若是第一種狀況未設置值,則其值爲0。

下面的枚舉是對先前Planet枚舉的改進,其中整數原始值表明從太陽到每一個行星的階數:

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
複製代碼

在上面的示例中,Planet.mercury的顯式原始值爲1,Planet.venus的隱式原始值爲2,依此類推。

若是將字符串用於原始值,則每種狀況的隱式值就是該case名稱的文本。

下面的枚舉是對早期CompassPoint枚舉的改進,使用字符串原始值來表示每一個方向的名稱:

enum CompassPoint: String {
    case north, south, east, west
}
複製代碼

在上面的示例中,CompassPoint.south的隱式原始值爲「 south」,依此類推。

您可使用其rawValue屬性訪問枚舉用例的原始值:

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
複製代碼

5.2 用原始值初始化

若是您使用原始值類型定義枚舉,則枚舉會自動接收一個採用原始值類型值的初始化程序(做爲稱爲rawValue的參數),並返回枚舉case或nil。 您可使用此初始化程序嘗試建立枚舉的新實例。

本示例從其原始值7識別天王星:

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
複製代碼

可是,並不是全部可能的Int值都能找到匹配的行星。 所以,原始值初始化程序始終返回可選的枚舉狀況。 在上面的示例中,possiblePlanet的類型爲Planet?或「可選的Planet」。

注意:
原始值初始化程序是一個失敗的初始化程序,由於並不是每一個原始值都將返回枚舉狀況。 有關更多信息,請參見失敗的初始化程序。

若是您嘗試查找位置爲11的行星,則原始值初始化程序返回的可選Planet值將爲nil:

let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Mostly harmless")
    default:
        print("Not a safe place for humans")
    }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"
複製代碼

本示例使用可選綁定嘗試訪問原始值爲11的行星。若是讓somePlanet = Planet(rawValue:11)建立,則該語句建立一個可選Planet,並將somePlanet設置爲該可選Planet的值(若是能夠檢索)。 。 在這種狀況下,沒法檢索位置爲11的行星,所以執行else分支。

遞歸枚舉

遞歸枚舉是一種枚舉,該枚舉具備該枚舉的另外一個實例做爲一個或多個枚舉案例的關聯值。 您能夠經過在其前面寫一個indirect來指示枚舉用例是遞歸的,這告訴編譯器插入必要的間接層。

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

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
複製代碼

您也能夠在枚舉開始以前編寫遞歸的代碼,以讓具備關聯值的全部枚舉案例啓用遞歸功能:

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
複製代碼

該枚舉能夠存儲三種算術表達式:素數,兩個表達式的加法以及兩個表達式的乘法。 加法和乘法案例的關聯值也是算術表達式,這些關聯值使嵌套表達式成爲可能。 例如,表達式(5 + 4)* 2在乘法的右側有一個數字,在乘法的左側有另外一個表達式。 因爲數據是嵌套的,所以用於存儲數據的枚舉也須要支持嵌套-這意味着該枚舉須要遞歸。 下面的代碼顯示了爲(5 + 4)* 2建立的ArithmeticExpression遞歸枚舉:

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
複製代碼

遞歸函數是使用具備遞歸結構的數據的直接方法。 例如,這是一個計算算術表達式的函數:

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
// Prints "18"
複製代碼

該函數經過簡單地返回相關值來表示純數字。 它經過聲明left和right表達式表示addition或multiplication,而後將它們相加或相乘來表示加法或乘法。

總結

這一章節內容主要的知識點有:

  • 枚舉語法:用enum關鍵字聲明。與C和OC不同的是,case的類型不只僅只能設置爲整數值,還能夠是其餘類型。
  • Swift裏的多個case能夠寫在一行上,用逗號隔開。
  • 能夠用switch匹配枚舉的值。
  • 在枚舉名稱後面加上:CaseIterable屬性,就能夠收集枚舉裏全部的cases,可用for-in循環遍歷全部case。
  • 能夠定義Swift枚舉來存儲任何給定類型的關聯值,而且若是須要,每種枚舉的值類型能夠不一樣,如:
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
複製代碼
  • 枚舉case能夠預先填充默認值(稱爲原始值),這些默認值都是相同的類型,如:
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
複製代碼

能夠隱式設置原始值:

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
複製代碼

則Planet.mercury的顯式原始值爲1,Planet.venus的隱式原始值爲2,以此類推。

若是將字符串用於原始值,則每種狀況的隱式值就是該case名稱的文本。

enum CompassPoint: String {
    case north, south, east, west
}
複製代碼

可使用其rawValue屬性訪問枚舉用例的原始值:

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
複製代碼

也能夠用原始值進行初始化:

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
複製代碼
  • 遞歸枚舉是一種枚舉,該枚舉具備該枚舉的另外一個實例做爲一個或多個枚舉案例的關聯值;用indirect來標識該枚舉爲遞歸枚舉。如:
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
複製代碼

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
複製代碼

好了,關於枚舉的內容已經所有總結完畢,與OC裏的枚舉差異仍是很大的,用法更靈活,只要掌握了枚舉,就能夠在項目開發中靈活運用,相信你對這個功能也很感興趣。

最後,喜歡的朋友能夠點個👍哦,你的鼓勵纔是個人動力,嘿嘿嘿~

上一章節:閉包

下一章節:結構體和類

參考文檔:Swift - Enumerations

相關文章
相關標籤/搜索