Swift - 枚举

首先感觉Swift的枚举确实相对OC来说功能和语法都扩展很多。首先说一下枚举的定义。

枚举声明的类型是囊括可能状态的有限集,且可以具有附加值。通过内嵌(nesting),方法(method),关联值(associated values)和模式匹配(pattern matching),枚举可以分层次地定义任何有组织的数据。

Swift中可以制定枚举的类型如下,Swift可以制定IntegerFloat PointString、三种关联值类型,有的资料说也布尔类型(Boolean),但本人在测试以后好像不支持,下去也会再查资料验证是否支持Boolean类型

  • 整型(Integer)
  • 浮点数(Float Point)
  • 字符串(String)
关联Integer值类型,
enum Direction: Int {
    case Left = 0
    case Right = 1
    case Top = 2
    case Bottom = 3
}

关联String值类型,
enum Workday: String {
    case Mon = "Monday"
    case Tue = "Tuesday"
    case Wed = "Wednesday"
    case Thu = "Thursday"
    case Fri = "Friday"
}

关联Float Point值类型,
enum DouNum: Double {
    case π = 3.14159
    case e = 2.71828
    case φ = 1.61803398874
    case λ = 1.30357
}

当关联IntegerString类型的时候可以忽略为枚举中的case赋值,Swift枚举也能正常工作,如下

enum Direction: Int {
    case Left = 1
    case Right
    case Top
    case Bottom
}

enum Direction: String {
    case Sun
    case Mon
    case Tue
    case Wed
    case Thu
    case Fri
    case Sat
}

可以通过rawValue 属性来读取枚举的值,如上面的Workday枚举取出WorkDays枚举中Sun的值
case Mon = "Monday"

 let EnumValue = WorkDays.Mon.rawValue
 print(EnumValue)
 //打印的值为- Monday

不过某种情形下,你可能想要通过一个已有的 raw value
来创建一个 enum case。这种情况下,枚举提供了一个指定构造法:

enum Direction: Int {
    case Left = 0 
    case Right = 1 
    case Top = 2 
    case Bottom = 3
}

// 创建一个movement.Right用例,其raw value值为1,let rightMovement = Movement(rawValue: 1)

倘若使用rawValue构造器,切记它是一个可失败构造器( failable initializer )。换言之,构造方法返回值为 可选类型值 ,因为有时候传入的值可能与任意一个 cas都不匹配。比如 Movement(rawValue:42)

  • 嵌套枚举(Nesting Enums)
    如果你有特定子类型的需求,可以对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

说实话上面的例子我个人是无法理解的,首先枚举同级别的项目应该是统一类型的,比如特性(Character)下面的几个类别根本不是一个类型如头盔(Weapon)、武器(Helmet)、小偷(Thief)、武士(Warrior)、爵士(Knight)后三个是人物前两个是是人物的特性

个人感觉下面的例子比较适合用嵌套枚举,如枚举的第一层是人物角色,而人物的角色下面又包含其特有的技能,比如Role包含Student,而Student又有Write技能

enum Role {
    enum Student {
        case Sing
        case Dance
        case Write
    }
    enum Player {
        case Sing
        case Dance
        case Run
        case Jump
    }
    enum programmer {
        case Sing
        case Dance
        case Read
        case Coding
    }
    case Worker
    case Driver
}

现在,我们可以通过层级结构来描述角色允许访问的项目条。

let worker = Role.Worker
let code = Role.programmer.coding
  • 包含枚举(Containing Enums)

同样地,你也能够在structsclasses 中内嵌枚举。接着上面的例子:

struct Character {
     enum CharacterType {
     case Thief
     case Warrior
     case Knight
}
  enum Weapon {
     case Bow
     case Sword
     case Lance
     case Dagger
  }
  let type: CharacterType
  let weapon: Weapon
}
let warrior = Character(type: .Warrior, weapon: .Sword)

同样地,这也将有助于我们将相关的信息集中在一个位置。

  • 关联值(Associated Value)

关联值是将额外信息附加到 enum case 中的一种极好的方式。打个比方,你正在开发一款交易引擎,可能存在 买 和 卖 两种不同的交易类型。除此之外每手交易还要制定明确的股票名称和交易数量:

  • 简单例程(Simple Example)
enum Trade {
    case Buy
    case Sell
}
func trade(tradeType: Trade, stock: String, amount: Int) {}

然而股票的价值和数量显然从属于交易,让他们作为独立的参数显得模棱两可。你可能已经想到要往 struct 中内嵌一个枚举了,不过关联值提供了一种更清爽的解决方案:

enum Trade {
    case Buy(stock: String, amount: Int)
    case Sell(stock: String, amount: Int)
}
func trade(type: Trade) {}
  • 模式匹配(Pattern Matching)
    如果想要访问这些值,如果你想要访问这些值, 模式匹配 再次救场:
let trade = Trade.Buy(stock: "APPL", amount: 500)
if case let Trade.Buy(stock, amount) = trade {
    print("buy \(amount) of \(stock)")
}
  • 标签(Labels)
    关联值不需要附加标签的声明:
enum Trade {
   case Buy(String, Int)
   case Sell(String, Int)
}

倘若你添加了,那么,每当创建枚举用例时,你都需要将这些标签标示出来。

  • 元组参数(Tuple as Arguments)
    更重要的是, Swift 内部相关信息其实是一个元组,所以你可以像下面这样做:
let tp = (stock: "TSLA", amount: 100)
let trade = Trade.Sell(tp)

if case let Trade.Sell(stock, amount) = trade {
    print("buy \(amount) of \(stock)")
}
// Prints: "buy 100 of TSLA"

语法允许您将元组当作一个简单的数据结构,稍后元组将自动转换到高级类型,就比如 enum case 。想象一个应用程序可以让用户来配置电脑:

typealias Config = (RAM: Int, CPU: String, GPU: String)

// Each of these takes a config and returns an updated config
func selectRAM(_ config: Config) -> Config {return (RAM: 32, CPU: config.CPU, GPU: config.GPU)}
func selectCPU(_ config: Config) -> Config {return (RAM: config.RAM, CPU: "3.2GHZ", GPU: config.GPU)}
func selectGPU(_ config: Config) -> Config {return (RAM: config.RAM, CPU: "3.2GHZ", GPU: "NVidia")}

enum Desktop {
   case Cube(Config)
   case Tower(Config)
   case Rack(Config)
}

let aTower = Desktop.Tower(selectGPU(selectCPU(selectRAM((0, "", "") as Config))))

配置的每个步骤均通过递交元组到enum
中进行内容更新。倘若我们从 函数式编程中获得启发,这将变得更好。

infix operator <^> { associativity left }

func <^>(a: Config, f: (Config) -> Config) -> Config { 
    return f(a)
}

最后,我们可以将不同配置步骤串联起来。这在配置步骤繁多的情况下相当有用。

let config = (0, "", "") <^> selectRAM  <^> selectCPU <^> selectGPU
let aCube = Desktop.Cube(config)
  • 使用案例(Use Case Example)

关联值可以以多种方式使用。常言道:一码胜千言, 下面就上几段简单的示例代码,这几段代码没有特定的顺序。

// 拥有不同值的用例
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>])
}

// 或者映射不同的标识码
enum Barcode {
    case UPCA(numberSystem: Int, manufacturer: Int, product: Int, check: Int)
    case QRCode(productCode: String)
}

// 又或者假设你在封装一个 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)
  • 方法和属性(Methods and properties)
    你也可以在 enum 中像这样定义方法:
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: w.rawValue * 4)
       }
    }
}
let woodenHelmetProps = Wearable.Helmet(weight: .Light, armor: .Light).attributes()
print (woodenHelmetProps)
// prints "(2, 4)"

枚举中的方法为每一个 enum case 而“生”。所以倘若想要在特定情况执行特定代码的话,你需要分支处理或采用switch 语句来明确正确的代码路径。

enum Device { 
    case iPad, iPhone, AppleTV, AppleWatch 
    func introduced() -> String {
       switch self {
         case AppleTV: return "\(self) was introduced 2006"
         case iPhone: return "\(self) was introduced 2007"
         case iPad: return "\(self) was introduced 2010"
         case AppleWatch: return "\(self) was introduced 2014"
       }
    }
}
print (Device.iPhone.introduced())
// prints: "iPhone was introduced 2007"
  • 属性(Properties)

尽管增加一个存储属性到枚举中不被允许,但你依然能够创建计算属性。当然,计算属性的内容都是建立在枚举值下或者枚举关联值得到的。

enum Device {
  case iPad, iPhone
  var year: Int {
    switch self {
        case iPhone: return 2007
        case iPad: return 2010
     }
  }
}
  • 静态方法(Static Methods)
    你也能够为枚举创建一些静态方法(static methods )。换言之通过一个非枚举类型来创建一个枚举。举个栗子,我们在实际开发的时候,有时tableView有时需要返回两种Cell,我们可以根据后台返回的数据来判断返回那种cell表示。
enum StatusCellID: String
{
    case NormalCell = "NormalCell"
    case ForwardCell = "ForwardCell"
    
    /// 静态函数, 相当于类方法
    /// 根据微博数据,返回对应的标示符
    static func cellId(status: Status) ->String
    {
        return status.retweeted_status != nil ? StatusCellID.ForwardCell.rawValue : StatusCellID.NormalCell.rawValue
    }
}
  • 可变方法(Mutating Methods)
    方法可以声明为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 
      } 
    }
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight 现在等于.On
ovenLight.next()
// ovenLight 现在等于.Off
  • 小结(To Recap)

至此,我们已经大致了解了Swift中枚举语法的基本用例。在开始迈向进阶之路之前,让我们重新审视文章开篇给出的定义,看看现在是否变得更清晰了。
现在我们已经对这个定义更加清晰了。确实,如果我们添加关联值和嵌套, enum 就看起来就像一个封闭的、简化的 struct 。相比较 struct ,前者优势体现在能够为分类与层次结构编码。

// Struct Example
struct Point { let x: Int, let y: Int }
struct Rect { let x: Int, let y: Int, let width: Int, let height: Int }

// Enum Example
enum GeometricEntity {
   case Point(x: Int, y: Int)
   case Rect(x: Int, y: Int, width: Int, height: Int)
}

方法和静态方法的添加允许我们为 enum附加功能,这意味着无须依靠额外函数就能实现。

// C-Like example
enum Trade {
   case Buy
   case Sell
}
func order(trade: Trade)

// Swift Enum example
enum Trade {
   case Buy
   case Sell
   func order()
}

*参考原文 * http://swift.gg/2015/11/20/advanced-practical-enum-examples/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容