Swift Enum 枚舉

前言

  • 枚舉是一種自定義的數據類型,在 Swift 中枚舉類型擁有至關高的自由度。在 Swift 語言中枚舉是一級類型,它擁有在其餘語言中只有類才擁有的一些特性,好比實例方法,實例構造器等。html

  • 枚舉聲明的類型是囊括可能狀態的有限集,且能夠具備附加值,並在你的代碼中以一個安全的方式使用它們。經過內嵌(nesting),方法(method),關聯值(associated values) 和模式匹配(pattern matching) 枚舉能夠分層次地定義任何有組織的數據。node

  • switch 語句相似,Swift 中的枚舉乍看之下更像是 C 語言中枚舉的進階版本,即容許你定義一種類型,用於表示普通事情中某種用例。不過深刻挖掘以後,憑藉 Swift 背後特別的設計理念,相比較 C 語言枚舉來講其在實際場景中的應用更爲普遍。特別是做爲強大的工具,Swift 中的枚舉可以清晰表達代碼的意圖。ios

  • 在一般狀況下,枚舉是很容易進行相等性判斷的。一個簡單的 enum T { case a, b } 實現默認支持相等性判斷 T.a == T.b, T.b != T.a。然而,一旦咱們爲枚舉增長了關聯值,Swift 就沒有辦法正確地爲兩個枚舉進行相等性判斷,須要咱們本身實現 == 運行符。git

  • 基於整型的枚舉,如 enum Bit: Int { case zero = 0; case one = 1 } 能夠經過 @objc 標識來將其橋接到 Objective-C 當中。然而,一旦使用整型以外的類型(如 String)或者開始使用關聯值,咱們就沒法在 Objective-C 當中使用這些枚舉了。github

  • 枚舉是值類型,而且只有在賦予變量或常量,或者被函數調用時才被賦值。objective-c

一、枚舉的建立

  • 枚舉基本結構spring

    enum 枚舉名: 字段類型 {
    
        case 字段名
        case 字段名 = 原始值
        case 字段名(關聯值類型)
    }
    enum Seasons: Int {
    
        case spring = 0
        case summer = 1
        case autumn = 2
        case winter = 3
    }
    
    var sean = Seasons.spring
    
    /// enum                枚舉關鍵字
    /// Seasons             枚舉名稱
    /// Int                 枚舉字段類型
    
    /// Seasons.spring      建立一個枚舉變量

1.1 標準定義, 基本枚舉類型

  • 枚舉的定義編程

    • Swift 的每一個枚舉項前面,都使用一個 case 關鍵字來標識,從 Swift 3.0 開始,全部枚舉的 case 都改用小寫的形式。swift

      enum Movement {
      
          case left
          case right
          case top
          case bottom
      }
    • 除了每行聲明一個枚舉項,也能夠將這些枚舉項放在一行中聲明,每項之間用逗號分隔。api

      enum CompassPoint {
      
          case north, south, east, west       // 使用逗號分隔多個成員值
      }
    • 和 C 語言不一樣的是,Swift 標準的枚舉定義方式成功定義枚舉後,成員值並不會隱式被指定爲 0、一、二、…… 這種形式。

  • 枚舉的使用

    • 使用時,咱們能夠無須明確指出 enum 的實際名稱(即 case Move.left: print("Left"))。由於類型檢查器可以自動爲此進行類型推算。這對於那些 UIKit 以及 AppKit 中錯綜複雜的枚舉是很是有用的。

      /// 若是 switch 的條件聲明在同一個函數內,這時會提示 Switch condition evaluates 
      /// to a constant,要去除這個,只須要將聲明的變量放在函數外就能夠
      let aMovement = Movement.left
      
      /// aDirection 的類型是已知的,因此在設定它的值時,能夠不寫該類型
      let aDirection: CompassPoint = .south
    • 可使用多種模式匹配結構獲取到枚舉的值,或者按照特定狀況執行操做。

      /// 不管用 default 也好,仍是明確對每個枚舉項指定行爲也好,在 Swift 中,咱們都必須對
      /// 枚舉類型下的每一個值,指定肯定的行爲,不能漏掉其中任何一個可能性。
      
      /// switch 分狀況處理
      switch aMovement {              // print left
      
          case .left:
              print("left")
      
          case .right:
              print("right")
      
          default:
              break
      }
      
      /// 枚舉的全部成員值都列出來時,能夠不用寫 default: break 分支語句,若是寫了會提示 
      /// Default will never be executed
      switch aDirection {             // print Watch out for penguins
      
          case .east:
              print("Where the sun rises")
      
          case .west:
              print("Where the skies are blue")
      
          case .south:
              print("Watch out for penguins")
      
          case .north:
              print("Lots of planets have a north")
      }
      /// 明確的 case 狀況
      if case .left = aMovement {     // print left
           print("left")
      }
      /// 條件語句特定狀況判斷
      if aMovement == .left {         // print left
           print("left")
      }

1.2 原始值定義,枚舉值

  • 枚舉的定義

    • 固然,你可能想要爲 enum 中每一個 case 分配一個值。這至關有用,好比枚舉自身實際與某事或某物掛鉤時,每每這些東西又須要使用不一樣類型來表述。在 C 語言中,你只能爲枚舉 case 分配整型值,而 Swift 則提供了更多的靈活性。

    • Swift 枚舉中支持如下四種關聯值類型,所以一般狀況下你沒法爲枚舉分配諸如 CGPoint 類型的值。

      • 整型 (Integer)
      • 浮點數 (Float Point)
      • 布爾類型 (Boolean)
      • 字符串 (String)
      /// 映射到整型
      enum Seasons: Int {
      
          case spring = 0
          case summer = 1
          case autumn = 2
          case winter = 3
      }
      /// 映射到 float double, 注意枚舉中的花式 unicode
      enum Constants: Double {
      
          case π = 3.14159
          case e = 2.71828
          case φ = 1.61803398874
          case λ = 1.30357
      }
      /// 映射到字符串
      enum House: String {
      
          case baratheon = "Ours is the Fury"
          case greyjoy   = "We Do Not Sow"
          case martell   = "Unbowed, Unbent, Unbroken"
          case stark     = "Winter is Coming"  
          case tully     = "Family, Duty, Honor"
          case tyrell    = "Growing Strong"
      }
    • 對於 StringInt 類型來講,甚至能夠忽略爲枚舉中的 case 賦值,Swift 編譯器也能正常工做。

      /// CompassPointStr 枚舉中 north = "north", ... west = "west"
      enum CompassPointStr: String {
      
          case north, south, east, west
      }
      /// Planet 枚舉中 mercury = 1, venus = 2, ... neptune = 8
      enum Planet: Int {
      
          case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
      }
    • 若是想要以底層 C 二進制編碼形式呈現某物或某事,使得更具可讀性,能夠看一下 BSD kqeue library 中的 VNode Flags 標誌位的編碼方式,如此即可以使你的 deletewrite 用例聲明一目瞭然,稍後一旦須要,只需將 raw value 傳入 C 函數中便可。

      enum VNodeFlags: UInt32 {
      
          case delete   = 0x00000001
          case write    = 0x00000002
          case extended = 0x00000004
          case attrib   = 0x00000008
          case link     = 0x00000010
          case rename   = 0x00000020
          case revoke   = 0x00000040
          case none     = 0x00000080
      }
  • 枚舉的使用

    • 若是想要讀取枚舉的值,能夠經過 rawValue 屬性來實現。

      let bestHouse = House.stark
      
      /// 讀取枚舉的值,經過 rawValue 屬性來實現
      let houseValue = bestHouse.rawValue
      
      print(houseValue)
      // print Winter is Coming
    • 若是想要經過一個已有的 raw value 來建立一個 enum case。這種狀況下,枚舉提供了一個指定構造方法來實現。假若使用 rawValue 構造器,切記它是一個可失敗構造器 (failable initializer)。換言之,構造方法返回值爲可選類型值,由於有時候傳入的值可能與任意一個 case 都不匹配。好比 Seasons(rawValue: 10)

      /// 經過構造方法來建立 enum case
      let springSeasons: Seasons? = Seasons(rawValue: 0)
      
      print(String(describing: springSeasons))
      // print Optional(Swift_Enum.Enum2.Seasons.spring)

1.2.1 使用自定義類型做爲枚舉的值

  • 若是咱們忽略關聯值,則枚舉的值就只能是整型,浮點型,字符串和布爾類型。若是想要支持別的類型,則能夠經過實現 ExpressibleByStringLiteral 協議(Swift 4 以前名稱爲 StringLiteralConvertible)來完成,這可讓咱們經過對字符串的序列化和反序列化來使枚舉支持自定義類型。

    • 做爲一個例子,假設咱們要定義一個枚舉來保存不一樣的 iOS 設備的屏幕尺寸。然而下面這段代碼不能經過編譯。由於 CGSize 並非一個常量,不能用來定義枚舉的值。

      enum Devices: CGSize {
      
          case iPhone3GS   = CGSize(width: 320, height: 480)
          case iPhone5     = CGSize(width: 320, height: 568)
          case iPhone6     = CGSize(width: 375, height: 667)
          case iPhone6Plus = CGSize(width: 414, height: 736)
      }
    • 咱們須要爲想要支持的自定義類型增長一個擴展,讓其實現 ExpressibleByStringLiteral 協議。這個協議要求咱們實現三個構造方法,這三個方法都須要使用一個 String 類型的參數,而且咱們須要將這個字符串轉換成咱們須要的類型(此處是 CGSize)。

      extension CGSize: ExpressibleByStringLiteral {
      
          public init(stringLiteral value: String) {
      
              let size = CGSizeFromString(value)
              self.init(width: size.width, height: size.height)
          }
      
          public init(extendedGraphemeClusterLiteral value: String) {
      
              let size = CGSizeFromString(value)
              self.init(width: size.width, height: size.height)
          }
      
          public init(unicodeScalarLiteral value: String) {
      
              let size = CGSizeFromString(value)
              self.init(width: size.width, height: size.height)
          }
      }
    • 如今就能夠來實現咱們須要的枚舉了,不過這裏有一個缺點:初始化的值必須寫成字符串形式,由於這就是咱們定義的枚舉須要接受的類型(記住,咱們實現了 ExpressibleByStringLiteral,所以 String 能夠轉化成 CGSize 類型)。

      enum Devices: CGSize {
      
          case iPhone3GS   = "{320, 480}"
          case iPhone5     = "{320, 568}"
          case iPhone6     = "{375, 667}"
          case iPhone6Plus = "{414, 736}"
      }
    • 終於,咱們可使用 CGSize 類型的枚舉了。須要注意的是,當要獲取真實的 CGSize 的值的時候,咱們須要訪問枚舉的是 rawValue 屬性。

      let aDevice = Devices.iPhone5
      
      let size: CGSize = aDevice.rawValue
      
      print("the phone size string is \(aDevice)")
      print("width is \(size.width), height is \(size.height)")
      
      // print the phone size string is iPhone5
      // width is 320.0, height is 568.0
  • 使用字符串序列化的形式,會讓使用自定義類型的枚舉比較困難,然而在某些特定的狀況下,這也會給咱們增長很多便利(好比使用 NSColor / UIColor 的時候)。不只如此,咱們徹底能夠對本身定義的類型使用這個方法。

1.3 嵌套定義

  • 枚舉的定義

    • 若是有特定子類型的需求,能夠對 enum 進行嵌套。這樣就容許爲實際的 enum 中包含其餘明確信息的 enum。

      /// 以 RPG 遊戲中的每一個角色爲例,每一個角色可以擁有武器,所以全部角色均可以獲取同一個
      /// 武器集合。而遊戲中的其餘實例則沒法獲取這些武器(好比食人魔,它們僅使用棍棒)
      enum Character {
      
          enum Weapon {
      
              case bow
              case sword
              case lance
              case dagger
          }
      
          enum Helmet {
      
              case wooden
              case iron
              case diamond
          }
      
          case thief
          case warrior
          case knight
      }
  • 枚舉的使用

    • 能夠經過層級結構來獲取枚舉的值

      /// 能夠經過層級結構來描述角色容許訪問的項目條
      let character = Character.thief
      let weapon = Character.Weapon.bow
      let helmet = Character.Helmet.iron
      
      print(character)             // print thief
      print(weapon)                // print bow
      print(helmet)                // print iron

1.4 包含定義

  • 枚舉的定義

    • 可以在 結構體(structs)或 類(classes)中內嵌枚舉,這也將有助於咱們將相關的信息集中在一個位置。

      /// 在結構體中定義枚舉
      struct Characters {
      
          enum CharacterType {
      
              case thief
              case warrior
              case knight
          }
      
          enum Weapon {
      
              case bow
              case sword
              case lance
              case dagger
          }
      
          let type: CharacterType
          let weapon: Weapon
      }
  • 枚舉的使用

    • 使用在結構體(structs)或 類(classes)中定義的枚舉。

      let warrior = Characters(type: .warrior, weapon: .sword)
      
      print(warrior)      
      
      // print Characters(type: Swift_Enum.Enum4.Characters.CharacterType.warrior, 
      //                weapon: Swift_Enum.Enum4.Characters.Weapon.sword)

1.5 關聯值定義

  • 枚舉的定義

    • Swift 的 enum 類型能夠存儲值, 每一個枚舉成員設定一個或多個關聯值,關聯值是將額外信息附加到 enum case 中的一種極好的方式。

    • 關聯值附加標籤的聲明

      /// 打個比方,你正在開發一款交易引擎,可能存在買和賣兩種不一樣的交易類型。除此以外每手交易
      /// 還要制定明確的股票名稱和交易數量,然而股票的價值和數量顯然從屬於交易,讓他們做爲獨立
      /// 的參數顯得模棱兩可。你可能已經想到要 struct 中內嵌一個枚舉了,不過關聯值提供了一種
      /// 更清爽的解決方案
      enum Trade {
      
          case buy(stock: String, amount: Int)
          case sell(stock: String, amount: Int)
      }
    • 關聯值不附加標籤的聲明

      enum Barcode {
      
          case UPCA(Int, Int, Int)
          case QRCode(String)
      }
    • 關聯值能夠以多種方式使用

      /// 擁有不一樣值的用例
      enum UserAction {
      
          case openURL(url: NSURL)
          case switchProcess(processId: UInt32)
          case restart(time: NSDate?, intoCommandLine: Bool)
      }
      /// 假設你在實現一個功能強大的編輯器,這個編輯器容許多重選擇,
      /// 正如 Sublime Text : https://www.youtube.com/watch?v=i2SVJa2EGIw
      enum Selection {
      
          case none
          case single(Range<Int>)
          case multiple([Range<Int>])
      }
      /// 或者假設你在封裝一個 C 語言庫,正如 Kqeue BSD/Darwin 通知系統:
      /// https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
      enum KqueueEvent {
      
          case userEvent(identifier: UInt, fflags: [UInt32], data: Int)
          case readFD(fd: UInt, data: Int)
          case writeFD(fd: UInt, data: Int)
          case vnodeFD(fd: UInt, fflags: [UInt32], data: Int)
          case errorEvent(code: UInt, message: String)
      }
      /// 又或者一個 RPG 遊戲中的全部可穿戴裝備可使用一個枚舉來進行映射,能夠爲一個裝備
      /// 增長重量和持久兩個屬性
      /// 如今能夠僅用一行代碼來增長一個"鑽石"屬性,如此一來咱們即可以增長几件新的鑲嵌鑽石
      /// 的可穿戴裝備
      enum Wearable {
      
          enum Weight: Int {
      
              case light = 1
              case mid = 4
              case heavy = 10
          }
      
          enum Armor: Int {
      
              case light = 2
              case strong = 8
              case heavy = 20
          }
      
          case Helmet(weight: Weight, armor: Armor)
          case Breastplate(weight: Weight, armor: Armor)
          case Shield(weight: Weight, armor: Armor)
      }
      
      let woodenHelmet = Wearable.Helmet(weight: .light, armor: .light)
  • 枚舉的使用

    • 若是添加了標籤,那麼,每當建立枚舉用例時,都須要將這些標籤標示出來。
    • 若是全部的枚舉成員的關聯值的提取爲常數,或者當全部被提取爲變量,爲了簡潔起見,能夠放置一個 letvar 標註在成員名稱前。

      let trade1 = Trade.buy(stock: "APPL", amount: 500)
      let trade2 = Trade.sell(stock: "TSLA", amount: 100)
      
      if case let Trade.buy(stock, amount) = trade1 {
      
          print("buy \(amount) of \(stock)")            // print buy 500 of APPL
      }
      
      if case let Trade.sell(stock, amount) = trade2 {
      
          print("sell \(amount) of \(stock)")           // print sell 100 of TSLA
      }
      let productBarcode1: Barcode = .UPCA(8, 85909_51226, 3)
      let productBarcode2: Barcode = Barcode.QRCode("ABCDEFGHIJKLMNOP")
      
      switch productBarcode1 {     // print UPC-A with value of 8, 8590951226, 3.
      
          case .UPCA(let numberSystem, let identifier, let check):
              print("UPC-A with value of \(numberSystem), \(identifier), \(check).")
      
          case .QRCode(let productCode):
              print("QR code with value of \(productCode).")
      }
      
      switch productBarcode2 {     // print QR code with value of ABCDEFGHIJKLMNOP.
      
          /// 放置一個 let 或 var 標註在成員名稱前
          case let .UPCA(numberSystem, identifier, check):
              print("UPC-A with value of \(numberSystem), \(identifier), \(check).")
      
          case let .QRCode(productCode):
              print("QR code with value of \(productCode).")  
      }
    • 在 Swift 中,帶有關聯值的 enum 不提供 == 運算符的操做,正確使用方法 使用 switch 去判斷類型。

      let code3: Barcode = .QRCode("123")
      let code4: Barcode = .QRCode("456")
      
      /// 若是像下面這樣使用時,系統會提示:
      /// Binary operator '==' cannot be applied to two 'Enum.Barcode' operands
      if code3 == code4 {
      
      }
      
      /// 使用 switch 去判斷類型
      switch (code3, code4) {          // print code3 == code4: false
      
          case (.QRCode(let a), .QRCode(let b)) where a == b:
              print("code3 == code4: true")
      
          default:
              print("code3 == code4: false")
      }

1.5.1 枚舉的遞歸/間接類型

  • 間接類型是 Swift 2.0 新增的一個類型。它們容許將枚舉中一個 case 的關聯值再次定義爲枚舉。

  • 舉個例子,假設咱們想定義一個文件系統,用來表示文件以及包含文件的目錄。若是將文件和目錄定義爲枚舉的 case,則目錄 case 的關聯值應該再包含一個文件的數組做爲它的關聯值。由於這是一個遞歸的操做,編譯器須要對此進行一個特殊的準備。Swift 文檔中是這麼寫的:枚舉和 case 能夠被標記爲間接的(indrect),這意味它們的關聯值是被間接保存的,這容許咱們定義遞歸的數據結構。

  • 因此,若是咱們要定義 FileNode 的枚舉,它應該會是這樣的

    enum FileNode {
    
        case file(name: String)
        indirect case folder(name: String, files: [FileNode])
    }
  • 此處的 indrect 關鍵字告訴編譯器間接地處理這個枚舉的 case。

  • 也能夠對整個枚舉類型使用這個關鍵字。這是一個很強大的特性,可讓咱們用很是簡潔的方式來定義一個有着複雜關聯的數據結構。

  • 做爲例子,咱們來定義一個二叉樹:

    indirect enum Tree<Element: Comparable> {
    
        case empty
        case node(Tree<Element>, Element, Tree<Element>)
    }

1.5.2 對枚舉的關聯值進行比較

  • 在一般狀況下,枚舉是很容易進行相等性判斷的。一個簡單的 enum T { case a, b } 實現默認支持相等性判斷 T.a == T.b, T.b != T.a

  • 然而,一旦咱們爲枚舉增長了關聯值,Swift 就沒有辦法正確地爲兩個枚舉進行相等性判斷,須要咱們本身實現 == 運行符。

    enum Trade1 {
    
        case buy(stock: String, amount: Int)
        case sell(stock: String, amount: Int)
    }
    
    func ==(lhs: Trade1, rhs: Trade1) -> Bool {
    
        switch (lhs, rhs) {
    
            case let (.buy(stock1, amount1), .buy(stock2, amount2))
                where stock1 == stock2 && amount1 == amount2:
                return true
    
            case let (.sell(stock1, amount1), .sell(stock2, amount2))
                where stock1 == stock2 && amount1 == amount2:
                return true
    
            default:
                return false
        }
    }
  • 正如咱們所見,咱們經過 switch 語句對兩個枚舉的 case 進行判斷,而且只有當它們的 case 是匹配的時候(好比 buybuy)纔對它們的真實關聯值進行判斷。

    let buy1 = Trade1.buy(stock: "buy1", amount: 10)
    let buy2 = Trade1.buy(stock: "buy1", amount: 11)
    
    print(buy1 == buy2)         // print false

1.5.3 枚舉關聯的泛型

二、枚舉的屬性

  • 一個枚舉一般包含多個枚舉成員,枚舉成員能夠包括計算型屬性、類型別名,甚至其它枚舉、結構體和類。枚舉聲明中,每個事件塊都由一個 case 關鍵字開始。多個成員的值能夠出如今一行上,用逗號分隔。

  • 儘管增長一個存儲屬性到枚舉中不被容許,但你依然可以建立計算屬性。固然,計算屬性的內容都是創建在枚舉值下或者枚舉關聯值獲得的。

2.1 定義枚舉的計算屬性

  • 枚舉屬性的定義

    enum Device {
    
        case iPad, iPhone
    
        var year: Int {             // 定義計算屬性
    
            switch self {
    
                case .iPhone:
                    return 2007
    
                case .iPad:
                    return 2010
            }
        }
    }
  • 枚舉屬性的使用

    let device = Device.iPhone.year
    
    print(device)                   // print 2007

三、枚舉的方法

  • 枚舉中的方法爲每個 enum case 而 「生」。因此假若想要在特定狀況執行特定代碼的話,須要分支處理或採用 switch 語句來明確正確的代碼路徑。

  • 枚舉的方法

    • 靜態方法:可以爲枚舉建立一些靜態方法(static methods),換言之經過一個非枚舉類型來建立一個枚舉。
    • 可變方法:方法能夠聲明爲 mutating,這樣就容許改變隱藏參數 selfcase 值了。

    • 方法和靜態方法的添加容許咱們爲 enum 附加功能,這意味着無須依靠額外函數就能實現。

3.1 定義枚舉的方法

  • 枚舉方法的定義

    • 普通方法,分支處理或採用 switch 語句來明確正確的代碼路徑。

      enum Wearable {
      
          enum Weight: Int {
              case light = 1
          }
      
          enum Armor: Int {
              case light = 2
          }
      
          case helmet(weight: Weight, armor: Armor)
      
          func attributes() -> (weight: Int, armor: Int) {        // 定義方法
      
              switch self {
      
                  case .helmet(let w, let a):
                      return (weight: w.rawValue * 2, armor: a.rawValue * 4)
              }
          }
      }
      enum Device1 {
      
          case iPad, iPhone, AppleTV, AppleWatch
      
          func introduced() -> String {                           // 定義方法
      
              switch self {
      
                  case .iPad:
                      return "\(self) was introduced 2010"
                  case .iPhone:
                      return "\(self) was introduced 2007"
                  case .AppleTV:
                      return "\(self) was introduced 2006"
                  case .AppleWatch:
                      return "\(self) was introduced 2014"
              }
          }
      }
    • 靜態方法, static 修飾,經過一個非枚舉類型來建立一個枚舉。

      enum Device2 {
      
          case AppleWatch
      
          static func fromSlang(term: String) -> Device2? {       // 定義靜態方法
      
              if term == "iWatch" {
                  return .AppleWatch
              }
      
              return nil
          }
      }
    • 可變方法,mutating 修飾,改變隱藏參數 selfcase 值了。

      enum TriStateSwitch {
      
          case off, low, high
      
          mutating func next() {                                  // 定義可變方法
      
              switch self {
      
                  case .off:
                      self = .low
                  case .low:
                      self = .high
                  case .high:
                      self = .off
              }
          }
      }
  • 枚舉方法的使用

    • 普通方法

      let woodenHelmetProps = Wearable.helmet(weight: .light, armor: .light).attributes()
      print(woodenHelmetProps)          // print (weight: 2, armor: 8)
      
      let device1 = Device1.iPhone.introduced()
      print(device1)                    // print iPhone was introduced 2007
    • 靜態方法

      let device2 = Device2.fromSlang(term: "iWatch")
      
      print(String(describing: device2))  
      // print Optional(Swift_Enum.Enum7.Device2.AppleWatch)
    • 可變方法

      var ovenLight = TriStateSwitch.low
      
      ovenLight.next()                // ovenLight 如今等於 .high
      print(ovenLight)                // print high
      
      ovenLight.next()                // ovenLight 如今等於 .off
      print(ovenLight)                // print off

3.2 自定義構造方法

  • 咱們也可使用自定義構造方法來替換靜態方法。

  • 枚舉與結構體和類的構造方法最大的不一樣在於,枚舉的構造方法須要將隱式的 self 屬性設置爲正確的 case

    • 可失敗構造方法

      enum Device3 {
      
          case AppleWatch
      
          init?(term: String) {           // 可失敗(failable)的構造方法
      
              if term == "iWatch" {
                  self = .AppleWatch
                  return
              }
      
              return nil
          }
      }
    • 普通構造方法

      enum NumberCategory {
      
          case small
          case medium
          case big
          case huge
      
          init(number n: Int) {           // 普通的構造方法
      
              if n < 10000 {
                  self = .small
              } else if n < 1000000 {
                  self = .medium
              } else if n < 100000000 {
                  self = .big
              } else {
                  self = .huge
              }
          }
      }
    • 使用

      let device = Device3(term: "iWatch")
      print(String(describing: device))              
      // print Optional(Swift_Enum.Enum7.Device3.AppleWatch)
      
      let aNumber = NumberCategory(number: 100)
      print(aNumber)                                 
      // print small

四、枚舉的擴展

  • 枚舉也能夠進行擴展。最明顯的用例就是將枚舉的 casemethod 分離,這樣閱讀你的代碼可以簡單快速地消化掉 enum 內容。

4.1 定義枚舉的擴展

  • 枚舉

    enum Entities {
    
        case soldier(x: Int, y: Int)
        case tank(x: Int, y: Int)
        case player(x: Int, y: Int)
    }
  • 枚舉的擴展

    • 枚舉擴展的定義

      /// 爲 enum 擴展方法
      extension Entities {
      
          mutating func move(dist: CGVector) {}
          mutating func attack() {}
      }
      /// 你一樣能夠經過寫一個擴展來遵循一個特定的協議
      extension Entities: CustomStringConvertible {
      
          var description: String {
      
              switch self {
      
                  case let .soldier(x, y):
                      return "\(x), \(y)"
      
                  case let .tank(x, y):
                      return "\(x), \(y)"
      
                  case let .player(x, y):
                      return "\(x), \(y)"
              }
          }
      }
    • 枚舉擴展的使用

      var entities = Entities.tank(x: 2, y: 5)
      
      print(entities.attack())            // print ()
      print(entities.description)         // print 2, 5

五、枚舉的協議

  • Swift 協議定義一個接口或類型以供其餘數據結構來遵循,enum 固然也不例外。

  • 除了附加方法的能力以外,Swift 也容許你在枚舉中使用協議(Protocols)和協議擴展(Protocol Extension)。

5.1 定義枚舉的協議

  • 枚舉

    enum Account {
    
        case empty
        case funds(remaining: Int)
    
        enum Error1: Error {
            case overdraft(amount: Int)
        }
    
        var remainingFunds: Int {
    
            switch self {
    
                case .empty: 
                    return 0
                case .funds(let remaining):
                    return remaining
            }
        }
    }
    • 你也許會簡單地拿 struct 實現這個協議,可是考慮應用的上下文,enum 是一個更明智的處理方法。不過你沒法添加一個存儲屬性到 enum 中,就像 var remainingFuns: Int。那麼你會如何構造呢?答案是你可使用關聯值完美解決
  • 枚舉的協議

    • 枚舉協議的定義

      • 咱們先從 Swift 標準庫中的一個例子開始。CustomStringConvertible 是一個以打印爲目的的自定義格式化輸出的類型。該協議只有一個要求,即一個只讀(getter)類型的字符串(String 類型)。咱們能夠很容易爲 enum 實現這個協議。

        protocol CustomStringConvertible {
            var description: String { get }
        }
      • 自定義協議

        /// 一些協議的實現可能須要根據內部狀態來相應處理要求。例如定義一個管理銀行帳號的協議
        protocol AccountCompatible {
        
            var remainingFunds: Int { get }
        
            mutating func addFunds(amount: Int) throws
            mutating func removeFunds(amount: Int) throws
        }
    • 枚舉協議的實現

      • 實現系統定義的協議

        enum Trade: CustomStringConvertible {
        
            case buy, sell
        
            var description: String {                               // 實現協議
        
                switch self {
        
                    case .buy:
                        return "We're buying something"
                    case .sell:
                        return "We're selling something"
                }
            }
        }
      • 實現自定義的協議,爲了保持代碼清爽,咱們能夠在 enum 的擴展(protocl extension)中定義必須的協議函數。

        extension Account: AccountCompatible {
        
            mutating func addFunds(amount: Int) throws {            // 實現協議
        
                var newAmount = amount
        
                if case let .funds(remaining) = self {
                    newAmount += remaining
                }
        
                if newAmount < 0 {
                    throw Error1.overdraft(amount: -newAmount)
                } else if newAmount == 0 {
                    self = .empty
                } else {
                    self = .funds(remaining: newAmount)
                }
            }
        
            mutating func removeFunds(amount: Int) throws {         // 實現協議
        
                try self.addFunds(amount: amount * -1)
            }
        }
    • 枚舉協議的使用

      • 使用

        let action = Trade.buy
        
        print("this action is \(action)")            
        // print this action is We're buying something
        var account = Account.funds(remaining: 20)
        
        print("add: ", try? account.addFunds(amount: 10))            
        // print add:  Optional(())
        
        print("remove 1: ", try? account.removeFunds(amount: 15))            
        // print remove 1:  Optional(())
        
        print("remove 2: ", try? account.removeFunds(amount: 55))            
        // print remove 2:  nil

六、枚舉的泛型

  • 枚舉也支持泛型參數定義。你可使用它們以適應枚舉中的關聯值。

  • 就拿直接來自 Swift 標準庫中的簡單例子來講,即 Optional 類型。你主要可能經過如下幾種方式使用它:

    • 可選鏈(optional chaining(?))
    • if let 可選綁定
    • guard let 語句
    • switch 語句
  • 可是從語法角度來講你也能夠這麼使用 Optional

    let aValue  = Optional<Int>.some(5)
    let noValue = Optional<Int>.none
    
    print(aValue)                   // print Optional(5)
    
    if noValue == Optional.none {   // print No value
        print("No value")           
    }
  • 這是 Optional 最直接的用例,並未使用任何語法糖,可是不能否認 Swift 中語法糖的加入使得你的工做更簡單。若是你觀察上面的實例代碼,你恐怕已經猜到 Optional 內部實現是這樣的。

    // Simplified implementation of Swift's Optional
    enum MyOptional<T> {
    
        case some(T)
        case none
    }
  • 這裏有啥特別呢?注意枚舉的關聯值採用泛型參數 T 做爲自身類型,這樣可選類型構造任何你想要的返回值。枚舉能夠擁有多個泛型參數。就拿熟知的 Either 類爲例,它並不是是 Swift 標準庫中的一部分,而是實現於衆多開源庫以及其餘函數式編程語言,好比 HaskellF#。設計想法是這樣的: 相比較僅僅返回一個值或沒有值(née Optional),你更指望返回一個成功值或者一些反饋信息(好比錯誤值)。

    // The well-known either type is, of course, an enum that allows you to 
    // return either value one (say, a successful value) or value two (say 
    // an error) from a function
    enum Either<T1, T2> {
    
        case left(T1)
        case right(T2)
    }
  • Swift 中全部在 class 和 struct 中奏效的類型約束,在 enum 中一樣適用。

    // Totally nonsensical example. A bag that is either full (has an array with  
    // contents) or empty.
    // Totally nonsensical example. A bag that is either full (has an array with
    // contents) or empty.
    enum Bag<T: Sequence> where T.Iterator.Element == Equatable {
    
        case empty
        case full(contents: T)
    }

七、枚舉的迭代

  • 一個特別常常被問到的問題就是如何對枚舉中的 case 進行迭代。惋惜的是,枚舉並無遵照 SequenceType 協議,所以沒有一個官方的作法來對其進行迭代。

  • 取決於枚舉的類型,對其進行迭代可能也簡單,也有可能很困難。

  • StackOverflow 上有一個很好的討論貼。貼子裏面討論到的不一樣狀況太多了,若是隻在這裏摘取一些會有片面性,而若是將所有狀況都列出來,則會太多。

八、對 Objective-C 的支持

  • 基於整型的枚舉,如 enum Bit: Int { case zero = 0; case one = 1 } 能夠經過 @objc 標識來將其橋接到 Objective-C 當中。然而,一旦使用整型以外的類型(如 String)或者使用關聯值,咱們就沒法在 Objective-C 當中使用這些枚舉了。

    • Swift 中定義的整型枚舉

      @objc enum Seasons: Int {                       // 使用 @objc 標識
      
          case spring = 0
          case summer = 1
          case autumn = 2
          case winter = 3
      }
    • 在 Objective-C 中使用時,Swift 中定義的整型枚舉被隱式的轉換成了以下形式

      typedef SWIFT_ENUM(NSInteger, Seasons) {        // Objective-C 類型的枚舉
      
          SeasonsSpring = 0,
          SeasonsSummer = 1,
          SeasonsAutumn = 2,
          SeasonsWinter = 3,
      };
    • 在 Objective-C 中使用

      Seasons se = SeasonsSummer;
      
      switch (se) {                                   // print Summer
      
          case SeasonsSpring:
              NSLog(@"Spring");
              break;
      
          case SeasonsSummer:
              NSLog(@"Summer");
              break;
      
          case SeasonsAutumn:
              NSLog(@"Autumn");
              break;
      
          case SeasonsWinter:
              NSLog(@"Winter");
              break;
      
          default:
              break;
      }
  • 有一個名爲 _ObjectiveCBridgeable 的隱藏協議,可讓規範咱們以定義合適的方法,如此一來,Swift 即可以正確地將枚舉轉成 Objective-C 類型。然而,從理論上來說,這個協議仍是容許咱們將枚舉(包括其實枚舉值)正確地橋接到 Objective-C 當中。

  • 可是,咱們並不必定非要使用上面提到的這個方法。爲枚舉添加兩個方法,使用 @objc 定義一個替代類型,如此一來咱們即可以自由地將枚舉進行轉換了,而且這種方式不須要遵照私有協議。

    • 這個方法有一個的缺點,咱們須要將枚舉映射爲 Objective-C 中的 NSObject 基礎類型(咱們也能夠直接使用 NSDictionary),可是,當咱們碰到一些確實須要在 Objective-C 當中獲取有關聯值的枚舉時,這是一個可使用的方法。

    • Swift 中定義的枚舉

      enum Trade {
      
          case buy(stock: String, amount: Int)
          case sell(stock: String, amount: Int)
      }
    • 定義 Objective-C 中可使用的 Trade 的替代類型,這個類型也能夠定義在 Objective-C 的代碼中

      /// 在 Swift 代碼中定義
      
      @objc class OTrade: NSObject {
      
          var type: Int
      
          var stock: String
          var amount: Int
      
          init(type: Int, stock: String, amount: Int) {
      
              self.type = type
              self.stock = stock
              self.amount = amount
          }
      }
      /// 在 Objective-C 代碼中定義
      
      @property (nonatomic, assign) NSInteger type;
      
      @property (nonatomic, strong) NSString *stock;
      @property (nonatomic, assign) NSInteger amount;
      
      - (instancetype)initWithType:(NSInteger)type 
                             stock:(NSString *)stock 
                            amount:(NSInteger)amount;
      
      - (instancetype)initWithType:(NSInteger)type 
                             stock:(NSString *)stock 
                            amount:(NSInteger)amount {
      
          self = [super init];
          if (self) {
              _type = type;
              _stock = stock;
              _amount = amount;
          }
          return self;
      }
    • Swift 中定義的枚舉須要實現的兩個方法

      extension Trade  {
      
          func toObjc() -> OTrade {
      
              switch self {
      
                  case let .buy(stock, amount):
                      return OTrade(type: 0, stock: stock, amount: amount)
      
                  case let .sell(stock, amount):
                      return OTrade(type: 1, stock: stock, amount: amount)
              }
          }
      
          static func fromObjc(source: OTrade) -> Trade? {
      
              switch (source.type) {
      
                  case 0:
                      return Trade.buy(stock: source.stock, amount: source.amount)
      
                  case 1:
                      return Trade.sell(stock: source.stock, amount: source.amount)
      
                  default:
                      return nil
              }
          }
      }
    • 在 Objective-C 中使用

      OTrade *trade = [[OTrade alloc] initWithType:0 stock:@"A" amount:100];
      
      NSLog(@"%@", trade.stock);              // print A
      NSLog(@"%ld", (long)trade.amount);      // print 100

九、枚舉的底層

  • Erica Sadun 寫過一篇關於枚舉底層的博客,涉及到枚舉底層的方方面面。在生產代碼中毫不應該使用到這些東西,可是學習一下仍是至關有趣的。

  • 在這裏,只提到那篇博客中一條,若是想了解更多,請移步到原文:枚舉一般都是一個字節長度。[...] 若是你真的很傻很天真,你固然能夠定義一個有成百上千個 case 的枚舉,在這種狀況下,取決於最少所須要的比特數,枚舉可能佔據兩個字節或者更多。

十、Swift 標準庫中的枚舉

  • FloatingPointClassification 這個枚舉定義了一系列 IEEE 754 可能的類別,好比 NegativeInfinity, PositiveZeroSignalingNaN

    /// The IEEE 754 floating-point classes.
    public enum FloatingPointClassification {
    
        case signalingNaN
    
        case quietNaN
    
        case negativeInfinity
    
        case negativeNormal
    
        case negativeSubnormal
    
        case negativeZero
    
        case positiveZero
    
        case positiveSubnormal
    
        case positiveNormal
    
        case positiveInfinity
    }
  • Mirror.AncestorRepresentationMirror.DisplayStyle 這兩個枚舉被用在 Swift 反射 API 的上下文當中。

    /// Representation of ancestor classes.
    public enum AncestorRepresentation {
    
        case generated
    
        case customized(() -> Mirror)
    
        case suppressed
    }
    /// A suggestion of how a `Mirror`'s `subject` is to be interpreted.
    public enum DisplayStyle {
    
        case `struct`
    
        case `class`
    
        case `enum`
    
        case tuple
    
        case optional
    
        case collection
    
        case dictionary
    
        case set
    }
  • Optional 這個就不用多說了

    /// A type that represents either a wrapped value or `nil`, the absence of a value.
    public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    
        case none
    
        case some(Wrapped)
    
        public init(_ some: Wrapped)
    
        public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
    
        public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
    
        public init(nilLiteral: ())
    
        public var unsafelyUnwrapped: Wrapped { get }
    }

十一、枚舉的實踐用例

  • 在不少場合,使用枚舉要賽過使用結構體和類。通常來說,若是問題能夠被分解爲有限的不一樣類別,則使用枚舉應該就是正確的選擇。

  • 即便只有兩種 case,這也是一個使用枚舉的完美場景,正如 OptionalEither 類型所展現的。

11.1 錯誤處理

  • 說到枚舉的實踐使用,固然少不了在 Swift 2.0 當中新推出的錯誤處理。標記爲可拋出的函數能夠拋出任何遵照了 Error 空協議(Swift 4 以前名稱爲 ErrorType)的類型。正如 Swift 官方文檔中所寫的:Swift 的枚舉特別適用於構建一組相關的錯誤狀態,能夠經過關聯值來爲其增長額外的附加信息。

  • 做爲一個示例,咱們來看下流行的 JSON 解析框架 Argo。當 JSON 解析失敗的時候,它有多是如下兩種主要緣由:
    • JSON 數據缺乏某些最終模型所須要的鍵(好比你的模型有一個 username 的屬性,可是 JSON 中缺乏了)
    • 存在類型不匹配,好比說 username 須要的是 String 類型,而 JSON 中包含的是 NSNull6

    • 除此以外,Argo 還爲不包含在上述兩個類別中的錯誤提供了自定義錯誤。它們的 Error 枚舉是相似這樣的,全部的 case 都有一個關聯值用來包含關於錯誤的附加信息。

      enum DecodeError: Error {
      
          case TypeMismatch(expected: String, actual: String)
          case MissingKey(String)
          case Custom(String)
      }
  • 一個更加通用的用於完整 HTTP / REST API 錯誤處理的 Error 應該是相似這樣的

    enum APIError: Error {
    
        // Can't connect to the server (maybe offline?)
        case ConnectionError(error: NSError)
    
        // The server responded with a non 200 status code
        case ServerError(statusCode: Int, error: NSError)
    
        // We got no data (0 bytes) back from the server
        case NoDataError
    
        // The server response can't be converted from JSON to a Dictionary
        case JSONSerializationError(error: Error)
    
        // The Argo decoding Failed
        case JSONMappingError(converstionError: DecodeError)
    }
    • 這個 Error 實現了完整的 REST 程序棧解析有可能出現的錯誤,包含了全部在解析結構體與類時會出現的錯誤。
    • 若是你看得夠仔細,會發如今 JSONMappingError 中,咱們將 Argo 中的 DecodeError 封裝到了咱們的 APIError 類型當中,由於咱們會用 Argo 來做實際的 JSON 解析。
  • 更多關於 Error 以及此種枚舉類型的示例能夠參看官方文檔

11.2 觀察者模式

  • 在 Swift 當中,有許多方法來構建觀察模式。若是使用 @objc 兼容標記,則咱們可使用 NSNotificationCenter 或者 KVO。即便不用這個標記,didSet 語法也能夠很容易地實現簡單的觀察模式。在這裏可使用枚舉,它可使被觀察者的變化更加清晰明瞭。

  • 設想咱們要對一個集合進行觀察。若是咱們稍微思考一下就會發現這隻有幾種可能的狀況:一個或多個項被插入,一個或多個項被刪除,一個或多個項被更新。這聽起來就是枚舉能夠完成的工做。

    enum Change {
    
        case Insertion(items: [Item])
        case Deletion(items: [Item])
        case Update(items: [Item])
    }
    • 以後,觀察對象就可使用一個很簡潔的方式來獲取已經發生的事情的詳細信息。這也能夠經過爲其增長 oldValuenewValue 的簡單方法來擴展它的功能。

11.3 狀態碼

  • 若是咱們正在使用一個外部系統,而這個系統使用了狀態碼(或者錯誤碼)來傳遞錯誤信息,相似 HTTP 狀態碼,這種狀況下枚舉就是一種很明顯而且很好的方式來對信息進行封裝。

    enum HttpError: String {
    
        case Code400 = "Bad Request"
        case Code401 = "Unauthorized"
        case Code402 = "Payment Required"
        case Code403 = "Forbidden"
        case Code404 = "Not Found"
    }

11.4 結果類型映射

  • 枚舉也常常被用於將 JSON 解析後的結果映射成 Swift 的原生類型。相似地,若是咱們解析了其它的東西,也可使用這種方式將解析結果轉化咱們 Swift 的類型。

    enum JSON {
    
        case JSONString(Swift.String)
        case JSONNumber(Double)
        case JSONObject([String : JSONValue])
        case JSONArray([JSONValue])
        case JSONBool(Bool)
        case JSONNull
    }

11.5 UIKit 標識

  • 枚舉能夠用來將字符串類型的重用標識或者 storyboard 標識映射爲類型系統能夠進行檢查的類型。

  • 假設咱們有一個擁有不少原型 CellUITableView

    enum CellType: String {
    
        case ButtonValueCell = "ButtonValueCell"
        case UnitEditCell    = "UnitEditCell"
        case LabelCell       = "LabelCell"
        case ResultLabelCell = "ResultLabelCell"
    }

11.6 單位

  • 單位以及單位轉換是另外一個使用枚舉的絕佳場合。能夠將單位及其對應的轉換率映射起來,而後添加方法來對單位進行自動的轉換。另外一個示例是貨幣的轉換。以及數學符號(好比角度與弧度)也能夠從中受益。

    enum Liquid: Float {
    
        case ml = 1.0
        case l = 1000.0
    
        func convert(amount amount: Float, to: Liquid) -> Float {
    
            if self.rawValue < to.rawValue {
                return (self.rawValue / to.rawValue) * amount
            } else {
                return (self.rawValue * to.rawValue) * amount
            }
        }
    }
    
    // Convert liters to milliliters
    print(Liquid.l.convert(amount: 5, to: Liquid.ml))

11.7 遊戲

  • 遊戲也是枚舉中的另外一個至關好的用例,屏幕上的大多數實體都屬於一個特定種族的類型(敵人,障礙,紋理,...)。相對於本地的 iOS 或者 Mac 應用,遊戲更像是一個白板。即開發遊戲咱們可使用全新的對象以及全新的關聯創造一個全新的世界,而 iOS 或者 macOS 須要使用預約義的 UIButtonsUITableViewsUITableViewCells 或者 NSStackView

  • 不只如此,因爲枚舉能夠遵照協議,咱們能夠利用協議擴展和基於協議的編程爲不一樣爲遊戲定義的枚舉增長功能。

    enum FlyingBeast { case Dragon, Hippogriff, Gargoyle }
    enum Horde { case Ork, Troll }
    enum Player { case Mage, Warrior, Barbarian }
    enum NPC { case Vendor, Blacksmith }
    enum Element { case Tree, Fence, Stone }
    
    protocol Hurtable {}
    protocol Killable {}
    protocol Flying {}
    protocol Attacking {}
    protocol Obstacle {}
    
    extension FlyingBeast: Hurtable, Killable, Flying, Attacking {}
    extension Horde: Hurtable, Killable, Attacking {}
    extension Player: Hurtable, Obstacle {}
    extension NPC: Hurtable {}
    extension Element: Obstacle {}

11.8 字符串類型化

  • 在一個稍微大一點的 Xcode 項目中,咱們很快就會有一大堆經過字符串來訪問的資源。在前面的小節中,咱們已經提太重用標識和 storyboard 的標識,可是除了這兩樣,還存在不少資源:圖像,Segues,Nibs,字體以及其它資源。一般狀況下,這些資源均可以分紅不一樣的集合。若是是這樣的話,一個類型化的字符串會是一個讓編譯器幫咱們進行類型檢查的好方法。

    enum DetailViewImages: String {
    
        case Background    = "bg1.png"
        case Sidebar       = "sbg.png"
        case ActionButton1 = "btn1_1.png"
        case ActionButton2 = "btn2_1.png"
    }
  • 對於 iOS 開發者,R.swift 這個第三方庫能夠爲以上提到的狀況自動生成結構體。可是有些時候你可能須要有更多的控制(或者你多是一個 Mac 開發者)。

11.9 API 端點

  • Rest API 是枚舉的絕佳用例。它們都是分組的,它們都是有限的 API 集合,而且它們也可能會有附加的查詢或者命名的參數,而這可使用關聯值來實現。

  • 這裏有個 Instagram API 的簡化版。

    enum Instagram {
    
        enum Media {
    
            case Popular
            case Shortcode(id: String)
            case Search(lat: Float, min_timestamp: Int, 
                        lng: Float, max_timestamp: Int, 
                   distance: Int)
        }
    
        enum Users {
    
            case User(id: String)
             case Feed
            case Recent(id: String)
        }
    }
  • Ash Furrow 的 Moya 框架就是基本這個思想,使用枚舉對 rest 端點進行映射。

11.10 鏈表

  • Airspeed Velocity 有一篇極好的文章說明了如何使用枚舉來實現一個鏈表。那篇文章中的大多數代碼都超出了枚舉的知識,並涉及到了大量其它有趣的主題,可是,鏈表最基本的定義是相似這樣的(對其進行了一些簡化)。

    enum List {
    
        case End
        indirect case Node(Int, next: List)
    }
    • 每個節點(Node)case 都指向了下一個 case,經過使用枚舉而非其它類型,咱們能夠避免使用一個可選的 next 類型以用來表示鏈表的結束。
  • Airspeed Velocity 還寫過一篇超讚的博客,關於如何使用 Swift 的間接枚舉類型來實現紅黑樹,因此若是你已經閱讀過關於鏈表的博客,你可能想繼續閱讀這篇關於紅黑樹的博客。

11.11 設置字典

  • 這是 Erica Sadun 提出的很是很是機智的解決方案。簡單來說,就是任何咱們須要用一個屬性的字典來對一個項進行設置的時候,都應該使用一系列有關聯值的枚舉來替代。使用這方法,類型檢查系統能夠確保配置的值都是正確的類型。關於更多的細節,以及合適的例子,能夠閱讀下她的文章

十二、枚舉的侷限

12.1 提取關聯值

  • David Owens 寫過一篇文章,他以爲當前的關聯值提取方式是很笨重的:爲了從一個枚舉中獲取關聯值,咱們必須使用模式匹配。然而,關聯值就是關聯在特定枚舉 case 的高效元組。而元組是可使用更簡單的方式來獲取它內部值,即 .keyword 或者 .0

    // Enum(枚舉)
    enum Ex {
    
        case Mode(ab: Int, cd: Int) 
    }
    
    if case Ex.Mode(let ab, let cd) = Ex.Mode(ab: 4, cd: 5) {
        print(ab)
    }
    
    // vs tuples(元組)
    let tp = (ab: 4, cd: 5)
    print(tp.ab)
  • 若是你也一樣以爲咱們應該使用相同的方法來對枚舉進行解構(deconstruct),這裏有個 rdar: rdar://22704262

12.2 相等性

  • 擁有關聯值的枚舉沒有遵照 equatable 協議。這是一個遺憾,由於它爲不少事情增長了沒必要要的複雜和麻煩。深層的緣由多是由於關聯值的底層是使用了元組,而元組並無遵照 equatable 協議。然而,對於限定的 case 子集,若是這些關聯值的類型都遵照了 equatable 類型,編譯器應該默認爲其生成 equatable 擴展。

    // Int 和 String 是可判等的, 因此 Mode 應該也是可判等的
    enum Ex { 
    
        case Mode(ab: Int, cd: String) 
    }
    
    // Swift 應該可以自動生成這個函數
    func ==(lhs: Ex.Mode, rhs: Ex.Mode) -> Bool {
    
        switch (lhs, rhs) {
    
            case (.Mode(let a, let b), .Mode(let c, let d)):
                return a == c && b == d
    
            default:
                return false
        }
    }

12.3 元組

  • 最大的問題就是對元組的支持。他們目前還處於無文檔狀態而且在不少場合都沒法使用。在枚舉當中,咱們沒法使用元組做爲枚舉的值。

    enum Devices: (intro: Int, name: String) {
    
        case iPhone     = (intro: 2007, name: "iPhone")
        case AppleTV    = (intro: 2006, name: "Apple TV")
        case AppleWatch = (intro: 2014, name: "Apple Watch")
    }
    • 這彷佛看起來並非一個最好的示例,可是咱們一旦開始使用枚舉,就會常常陷入到須要用到相似上面這個示例的情形中。

12.4 迭代枚舉的全部 case

  • 這個咱們已經在前面討論過了。目前尚未一個很好的方法來得到枚舉中的全部 case 的集合以使咱們能夠對其進行迭代。

12.5 默認關聯值

  • 另外一個會碰到的事是枚舉的關聯值老是類型,可是咱們卻沒法爲這些類型指定默認值。

    enum Characters {
    
        case Mage(health: Int = 70, magic: Int = 100, strength: Int = 30)
        case Warrior(health: Int = 100, magic: Int = 0, strength: Int = 100)
        case Neophyte(health: Int = 50, magic: Int = 20, strength: Int = 80)
    }
  • 咱們依然可使用不一樣的值建立新的 case,可是角色的默認設置依然會被映射。

相關文章
相關標籤/搜索