enum ios swift

2016-05-27 12:45:46 like_sky_ 阅读数 185

 一、定义枚举

    在Swift中枚举的定义与其他编程语言中定义枚举不同,在每个枚举元素前面多了一个关键字case,  case后紧跟的是枚举元素,下面是定义枚举类型的两种方式。

    1. 多个case, 每个枚举元素前都有个case

复制代码
1 //枚举的定义
2 enum KindOfAnimal {
3     case Cat
4     case Dog
5     case Cow
6     case Duck
7     case Sheep
8 }
复制代码

 

    2.一个case搞定所有元素,枚举元素之间使用逗号隔开

1 //你也可以这样定义枚举类型
2 enum KindOfAnimalTwo {
3     case Cat, Dog, Cow, Duck, Sheep
4 }

 

  二、枚举类型的使用

    定义完枚举类型就是为了使用的对吧,直接使用枚举类型声明变量即可, 在Swift中是不需要typedef来定义枚举类型的,我们可以直接使用枚举类型。

//定义枚举变量并赋值
var animal1: KindOfAnimal = KindOfAnimal.Cat

 

    给枚举变量赋值时也可以把枚举类型名省略掉,因为在声明枚举变量时就已经指定了枚举变量的类型。

var animal2: KindOfAnimal = .Dog

 

    在Switch中使用我们的枚举变量

复制代码
 1 //在Switch…Case中使用枚举
 2 switch animal1  {
 3     case KindOfAnimal.Cat:
 4         println("Cat")
 5     case KindOfAnimal.Dog:
 6         println("Dog")
 7     case KindOfAnimal.Cow:
 8         println("Cow")
 9     case KindOfAnimal.Duck:
10         println("Duck")
11     case KindOfAnimal.Sheep:
12         println("Sheep")
13     default:
14         println("error = 呵呵")
15 }
复制代码

 

  三、给枚举成员赋值

    在Swift中声明枚举时,是可以给每个枚举成员赋一个值的,下面的City枚举的成员就被指定了一个值,如下所示:

复制代码
//给枚举赋值
enum City: String{
    case Beijing = "北京"
    case ShangHai = "上海"
    case GuangZhou = "广州"
    case ShengZhen = "深圳"
}
复制代码

 

    使用枚举变量的rawValue可以获取给每个枚举成员赋的值,代码如下:

复制代码
//定义枚举变量并赋值
var myInCity: City = City.Beijing

//获取枚举变量的值
var myInCityString: String = myInCity.rawValue;
println(myInCityString)    //输出:北京
复制代码

 

  四、通过枚举成员的值给枚举变量赋值

    什么是通过枚举成员的值给枚举变量赋值呢?举个例子,以上面的枚举类型City为例,假如我们只知道一个枚举成员的值是“北京”,而不知道“北京”这个值对应的枚举成员是"Beijing", 在Swift中是可以通过“北京”这个值给枚举变量赋一个枚举成员“Beijing”的。

    是不是有点绕啊,来点实例吧,下面就是通过枚举成员的原始值给枚举变量赋值的代码

//通过枚举成员的值,来给枚举成员赋值
var youInCity: City? = City(rawValue: "北京");

 

    为啥我们的youInCity是可选值类型的呢?原因很简单,我们不确定枚举City中的成员的值是否含有“北京”,枚举变量youInCity的值是不确定的,所以喽是可选类型的,下面我们就取出youInCity的值,首先判断youInCity是否为City.Beijing, 如果是,说明赋值成功就输出值。

//取出youInCity中的值
if youInCity == City.Beijing {
    var cityName: NSString = youInCity!.rawValue
    println(cityName) //输出:北京
}

 

    找一个枚举成员的值中不包含的字符串用来给枚举变量赋值,观察一下结果,下面的testCity的值打印出来为nil, 因为枚举中没有一个成员的值为“京”。

//传入一个枚举中没有的值
var testCity: City? = City(rawValue: "");

// testCity为nil
println(testCity)

 

  五、枚举值自增

    好东西还是要保留的,在Swift中的枚举值如果是整数的话,第一个赋值后,后边的会自增的。关于枚举值自增就不多说了,直接看实例吧。

复制代码
//枚举值自增
enum Hour: Int{
    case One = 1
    case Two
    case Three
    case Four
    case Five
    case Six
    case Seven
    case Eight
}

var hourTest: Int = Hour.Eight.rawValue
println(hourTest)      // hourTest = 8
复制代码

 

  六、枚举关联值

    什么是枚举的关联值呢?从字面意思上看是给枚举成员关联一个值,没错,就是在给枚举变量赋值时,给枚举变量关联一个值。在Swift中如何做的呢?就是在声明枚举类型中的元素时使用小括号来制定关联值的类型,然后在给枚举变量赋值的时候关联一个或者多个值即可,直接看实例。

    下面的代码是给iOS指定了两个String类型的关联值,在给枚举变量赋值的时候关联两个值。关联这两个值可以在Switch语句中进行使用。

复制代码
//枚举的关联值
enum mobileLanguage{
    case IOS (String, String)
    case Android (String)
}

var iPhone: mobileLanguage = mobileLanguage.IOS("Objective-C", "Swift")

switch iPhone {
    case  mobileLanguage.IOS(let language1, let language2):
        println("language1 = \(language1), language2 = \(language2)")
    
    case mobileLanguage.Android(let temp):
        println(temp);
    default:
        println("NO")
}

//输出结果:language1 = Objective-C, language2 = Swift
复制代码

 

  七、枚举函数

    在Swift中的枚举是可以添加函数的,有没有眼前一亮呢。下面的代码段是在上面的关联值代码的基础上添加了一个描述函数,返回的就是当前枚举变量的枚举信息,如下代码段所示:

复制代码
 1 //枚举函数
 2 enum mobileLanguageFun{
 3     case IOS (String, String)
 4     case Android (String)
 5     //定义枚举函数
 6     var description: String{
 7         switch self {
 8             case  mobileLanguageFun.IOS(let language1, let language2):
 9                 return "language1 = \(language1), language2 = \(language2)"
10                 
11             case mobileLanguageFun.Android(let temp):
12                 return temp
13             default:
14                 return ("NO")
15         }
16 
17     }
18 }
19 
20 var myMobile: mobileLanguageFun = mobileLanguageFun.IOS("objc", "swift")
21 
22 println(myMobile.description)  //language1 = objc, language2 = swift
复制代码
转载:http://www.cnblogs.com/ludashi/p/4721158.html
2017-09-19 16:34:53 wiseuc_jianghai 阅读数 2071

iOS Swift Crash的捕获


crash捕获介绍

  • 如果对crash捕获不太了解,可以先参考这篇文章,本文进行Mach异常+Unix信号方式捕获crash。

  • NSException一般只在OC当中被捕获,一般情况下在捕获NSException异常后同时也会捕获到一个对应的signal异常。但如果你使用的是纯swift开发,如下代码并不会捕获相关的crash

      NSSetUncaughtExceptionHandler(UncaughtExceptionHandler)
    

swift崩溃捕获

  • swift通常都是通过对应的signal来捕获crash。对于swift的崩溃捕获,Apple的文档中有描述说需要通过SIGTRAP信号捕获强转失败,及非可选的nil值导致的崩溃.具体描述如下:

      Trace Trap[EXC_BREAKPOINT // SIGTRAP]
      类似于异常退出,此异常旨在使附加的调试器有机会在其执行中的特定点中断进程。您可以使用该__builtin_trap()函数从您自己的代码触发此异常。如果没有附加调试器,则该过程将终止并生成崩溃报告。
      较低级的库(例如,libdispatch)会在遇到致命错误时捕获进程。有关错误的其他信息可以在崩溃报告的“ 附加诊断信息”部分或设备的控制台中找到。
    
      如果在运行时遇到意外情况,Swift代码将以此异常类型终止,例如:
          1.具有nil值的非可选类型
          2.一个失败的强制类型转换
    
  • 对于swift还有一种崩溃需要捕获(Intel处理器,我认为应该是指在模拟器上的崩溃),为保险起见,也需要将信号SIGILL进行注册,Apple同样对其中做了描述

      Illegal Instruction[EXC_BAD_INSTRUCTION // SIGILL]
      该过程尝试执行非法或未定义的指令。该过程可能尝试通过错误配置的函数指针跳转到无效地址。
      在Intel处理器上,ud2操作码引起EXC_BAD_INSTRUCTION异常,但通常用于进程调试目的。如果在运行时遇到意外情况,Intel处理器上的Swift代码将以此异常类型终止。有关详细信息,请参阅Trace Trap。
    

crash捕获实现代码参考

    //对于OC的exception采取如下方式捕获
    NSSetUncaughtExceptionHandler(UncaughtExceptionHandler)
    //对于Swift则捕获相关signa,一般来说如下几种已经能够捕获大部分crash。(其中SIGTRAP一定要捕获,swift大量的crash都会通过它)
     signal(SIGABRT, SignalExceptionHandler)
    signal(SIGSEGV, SignalExceptionHandler)
    signal(SIGBUS, SignalExceptionHandler)
    signal(SIGTRAP, SignalExceptionHandler)
    signal(SIGILL, SignalExceptionHandler)

获取Slide Address

通过获取到偏移量地址Slide Address错误信息的内存地址基本即可定位错误,错误信息的内存地址在捕获的crash信息中会体现,Slide Address则需要我们自己获取,通过调用如下C方法我们可以获取到偏移量地址,这里通过OC文件来调用C方法。方法如下:

//MARK: - 获取偏移量地址
long  calculate(void){
long slide = 0;
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
    if (_dyld_get_image_header(i)->filetype == MH_EXECUTE) {
        slide = _dyld_get_image_vmaddr_slide(i);
        break;
    }
}
return slide;
}

crash分析介绍

  • 如果想要定位错误,通过拿到Slide Address错误信息的内存地址即可定位(实际上错误信息地址可以通过Slide Address加上偏移量获得)
  • 拿到Slide Address错误信息的内存地址后我们可以通过一个开源工具dSYMTools直接定位到我们想要的信息。感谢作者的贡献,让我们更加方便快捷的分析问题。
  • dSYMTools 需要传入.xcarchive文件,你可以通过Xcode找到你对应提交版本的.xcarchive。你也可以在提交前保留对应的.xcarchive,以供万一产生crash分析使用,这里的.xcarchive一定要与产生crash信息的版本对应,否则无法定位到崩溃信息

具体分析

  • 当自己捕获NSException Crash并上传到服务器之后,正常crash大概会显示信息如下,我们大概能够知道是由于数组溢出导致的崩溃。

      Stack:
      slideAdress:0xec000
      name:NSRangeException 
      reason:Optional("*** -[__NSArray0 objectAtIndex:]: index 66         beyond bounds for empty NSArray") 
      0   CoreFoundation                      0x0000000181646ff0      <redacted> + 148
      1   libobjc.A.dylib                     0x00000001800a8538      objc_exception_throw + 56
      2   CoreFoundation                      0x00000001815b2eb8      <redacted> + 0
      3   CrashManager                        0x00000001000f3000      CrashManager + 28672
      4   UIKit                               0x00000001877ab0ec      <redacted> + 96
      5   UIKit                               0x00000001877ab06c      <redacted> + 80
      6   UIKit                               0x00000001877955e0      <redacted> + 440
      7   UIKit                               0x00000001877aa950      <redacted> + 576
      8   UIKit                               0x00000001877aa46c      <redacted> + 2480
      9   UIKit                               0x00000001877a5804      <redacted> + 3192
      10  UIKit                               0x0000000187776418      <redacted> + 340
      11  UIKit                               0x0000000187f6ff64      <redacted> + 2400
      12  UIKit                               0x0000000187f6a6c0      <redacted> + 4268
      13  UIKit                               0x0000000187f6aaec      <redacted> + 148
      14  CoreFoundation                      0x00000001815f5424      <redacted> + 24
      15  CoreFoundation                      0x00000001815f4d94      <redacted> + 540
      16  CoreFoundation                      0x00000001815f29a0      <redacted> + 744
      17  CoreFoundation                      0x0000000181522d94      CFRunLoopRunSpecific + 424
      18  GraphicsServices                    0x0000000182f8c074      GSEventRunModal + 100
      19  UIKit                               0x00000001877db130      UIApplicationMain + 208
      20  CrashManager                        0x00000001000f139c      CrashManager + 21404
      21  libdyld.dylib                       0x000000018053159c <redacted> + 4 
    

    如上所示,slideAdress我们已经通过程序获取,这里是0xec000,由上往下找,我们可以看到3 CrashManager 0x00000001000f3000 CrashManager + 28672出现了CrashManager信息,那么0x00000001000f3000则有可能为我们想要定位的错误信息地址。将我们得到数据带入工具可清晰定位到错误:

    NSException错误信息.png
    NSException错误信息.png
  • 如果捕获到的Signal Crash,可能crash显示的信息大概会如下:

      Stack:
      slideAdress:0xa4000
      0   CrashManager                        0x00000001000a8f10      CrashManager + 20240
      1   CrashManager                        0x00000001000a9024      CrashManager + 20516
      2   libsystem_platform.dylib            0x000000018070530c      _sigtramp + 36
      3   CrashManager                        0x00000001000ab1bc CrashManager + 29116
      4   CrashManager                        0x00000001000aaa64 CrashManager + 27236
      5   UIKit                               0x00000001877ab0ec <redacted> + 96
      6   UIKit                               0x00000001877ab06c <redacted> + 80
      7   UIKit                               0x00000001877955e0 <redacted> + 440
      8   UIKit                               0x00000001877aa950 <redacted> + 576
      9   UIKit                               0x00000001877aa46c <redacted> + 2480
      10  UIKit                               0x00000001877a5804 <redacted> + 3192
      11  UIKit                               0x0000000187776418 <redacted> + 340
      12  UIKit                               0x0000000187f6ff64 <redacted> + 2400
      13  UIKit                               0x0000000187f6a6c0 <redacted> + 4268
      14  UIKit                               0x0000000187f6aaec <redacted> + 148
      15  CoreFoundation                      0x00000001815f5424 <redacted> + 24
      16  CoreFoundation                      0x00000001815f4d94 <redacted> + 540
      17  CoreFoundation                      0x00000001815f29a0 <redacted> + 744
      18  CoreFoundation                      0x0000000181522d94 CFRunLoopRunSpecific + 424
      19  GraphicsServices                    0x0000000182f8c074 GSEventRunModal + 100
      20  UIKit                               0x00000001877db130 UIApplicationMain + 208
      21  CrashManager                        0x00000001000a939c CrashManager + 21404
      22  libdyld.dylib                       0x000000018053159c <redacted> +
    

一般我们无法从signal产生的这些信息中直观获取到crash产生的原因,这里崩溃的地址我们一般优先选择_sigtramp后第一条有我们程序信息的地址,所以这里将slideAdress:0xa4000和可能的错误信息地址0x00000001000ab1bc代入:

signalCrash.png
signalCrash.png

注意事项

  • 如果你使用工具解析后得到的信息是类似于这样的_hidden#30_ (in libswiftObjectiveC.dylib) (__hidden#70_:0),那么极有可能是因为你提交版本的时候勾选了BitCode,勾选了Bitcode之后,用户安装的二进制文件是苹果服务器经过优化后生成的,其对应的调试符号信息丢失了,所以你看到的全部是类似于__hidden#70_:0这样的信息,所以也无法通过还原奔溃现场找原因了.如果你如果你不太理解BitCode,可以参考这篇BitCode介绍和这篇讨论
  • 如果你使用模拟器,那么由于本身绑定相关dSYM文件,你获取到的crash信息中可能不是错误地址而是很明显的相关错误信息,这不在本文讨论范围内,毕竟在调试过程中获取崩溃信息相对容易,本文阐述的是你的应用已经提交后捕获和分析用户使用过程中产生的crash

资源支持

这是一个完整的Demo,本篇文章所阐述的内容在Demo中都有体现,你可以直接使用Demo中的模块完成Crash捕获,也可以参考Demo阅读本文,相信可以更快理解,如果对你有帮助,给个star呗。
crash捕获参考文档连接

2018-09-29 08:43:43 kmonarch 阅读数 207

在开发中我们经常会使用enum来枚举不同的状态。

举个例子,在测试登录服务器的时候,可能会有多中权限的账号存在,比如管理员、普通用户、开发者等,我们可以用一个enum来表示他们

enum Enum_Account{
    case Adminstrator
    case User
    case Developer
}

假如我需要对每一种状态,返回固定的中文显示的String,最常规的方法可能就类似下面

func getAccountAuth(accont:Enum_Account)-> String{
    switch status{
        case: .Adminstrator
            return "管理员"
        case: .User
            return "普通用户"
        case: .Developer
            return "开发者"
    }
}

当然除此之外还有更好的办法,Swift原生就支持对enum指定一个类型

enum Enum_Account : String {
    case Adminstrator = "管理员"
    case User = "普通用户"
    case Developer = "开发者"
}

那我们使用的时候,就直接可以通过 Enum_Account.rawValue取到上面的String值
但是这种方法有一个局限性,就是只能对enum指定一个类型,而且对enum指定的类型只能是字面量类,例如,如果把enum 的类型设置为UIColor:

enum Enum_Color:UIColor {

}

此时会提示错误:
Raw Raw type 'UIColor' is not expressible by any literal

这个时候我们可以使用extension解决这个问题,我们可能常常会为一个class做extension, 其实enum也可以做extension!

enum Enum_Common_Color {

    case Login_background

    case Negative_Text

    case Positive_Text

}


extension  Enum_Common_Color{

    var color:UIColor{

    switch self {

        case .Login_background:

            return UIColor(red: 57/255.0, green: 60/255.0, blue: 69/255.0, alpha: 1.0)

        case .Negative_Text:

            return UIColor(red: 0x19/255.0, green: 0x7F/255.0, blue: 0xBF/255.0, alpha: 1.0)

        case .Positive_Text:

            return UIColor(red: 0xF7/255.0, green: 0x44/255.0, blue: 0x44/255.0, alpha: 1.0)

/*default:

return UIColor.blue*/

        }

    }

}

那我们要获取状态对应颜色的时候,只需要通过Enum_Common_Color.Login_background.color这样的方式就可以获取颜色啦!

2017-08-31 16:40:00 itchosen 阅读数 14488

 

本文是一篇详细且具有实战意义的教程,涵盖几乎所有枚举(Enum)知识点,为你解答Swift中枚举的应用场合以及使用方法。

switch语句类似,Swift中的枚举乍看之下更像是C语言中枚举的进阶版本,即允许你定义一种类型,用于表示普通事情中某种用例。不过深入挖掘之后,凭借Swift背后特别的设计理念,相比较C语言枚举来说其在实际场景中的应用更为广泛。特别是作为强大的工具,Swift中的枚举能够清晰表达代码的意图。

本文中,我们将首先了解基础语法和使用枚举的可能性,接着通过实战教你如何以及何时使用枚举。最后我们还会大致了解下Swift标准库中枚举是如何被使用的。

正式开始学习之前,先给出枚举的定义。之后我们将回过头再来讨论它。

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

深入理解(Diving In)

简要概述如何定义和使用枚举。

定义基本的枚举类型(Defining Basic Enums)

试想我们正在开发一款游戏,玩家能够朝四个方向移动。所以喽,玩家的运动轨迹受到了限制。显然,我们能够使用枚举来表述这一情况:

enum Movement{
    case Left
    case Right
    case Top
    case Bottom
}

紧接着,你可以使用多种模式匹配结构获取到Movement的枚举值,或者按照特定情况执行操作:

let aMovement = Movement.Left

// switch 分情况处理
switch aMovement{
case .Left: print("left")
default:()
}

// 明确的case情况
if case .Left = aMovement{
    print("left")
}

if aMovement == .Left { print("left") }

案例中,我们无须明确指出enum的实际名称(即case Move.Left:print("Left"))。因为类型检查器能够自动为此进行类型推算。这对于那些UIKit以及AppKit中错综复杂的枚举是灰常有用的。

枚举值(Enum Values)

当然,你可能想要为enum中每个case分配一个值。这相当有用,比如枚举自身实际与某事或某物挂钩时,往往这些东西又需要使用不同类型来表述。在C语言中,你只能为枚举case分配整型值,而Swift则提供了更多的灵活性。

// 映射到整型
enum Movement: Int {
    case Left = 0
    case Right = 1
    case Top = 2
    case Bottom = 3
}

// 同样你可以与字符串一一对应
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"
}

// 或者float double都可以(同时注意枚举中的花式unicode)
enum Constants: Double {
    case π = 3.14159
    case e = 2.71828
    case φ = 1.61803398874
    case λ = 1.30357
}

对于StringInt类型来说,你甚至可以忽略为枚举中的case赋值,Swift编译器也能正常工作。

// Mercury = 1, Venus = 2, ... Neptune = 8
enum Planet: Int {
    case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

// North = "North", ... West = "West"
// 译者注: 这个是swift2.0新增语法
enum CompassPoint: String {
    case North, South, East, West
}

Swift枚举中支持以下四种关联值类型:

  • 整型(Integer)
  • 浮点数(Float Point)
  • 字符串(String)
  • 布尔类型(Boolean)

因此你无法1为枚举分配诸如CGPoint类型的值。

倘若你想要读取枚举的值,可以通过rawValue属性来实现:

let bestHouse = House.Stark
print(bestHouse.rawValue)
// prints "Winter is coming"

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

enum Movement: 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)。换言之,构造方法返回值为可选类型值,因为有时候传入的值可能与任意一个case都不匹配。比如Movement(rawValue:42)

如果你想要以底层 C 二进制编码形式呈现某物或某事,使得更具可读性,这是一个非常有用的功能。例如,可以看一下BSD kqeue library中的VNode Flags标志位的编码方式:

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
}

如此便可以使你的DeleteWrite用例声明一目了然,稍后一旦需要,只需将raw value传入 C 函数中即可。

嵌套枚举(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

包含枚举(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 Mathching)

如果你想要访问这些值,模式匹配再次救场:

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中进行内容更新。倘若我们从函数式编程2中获得启发,这将变得更好。

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)。换言之通过一个非枚举类型来创建一个枚举。在这个示例中,我们需要考虑用户有时将苹果设备叫错的情况(比如AppleWatch叫成iWatch),需要返回一个合适的名称。

enum Device { 
    case AppleWatch 
    static func fromSlang(term: String) -> Device? {
      if term == "iWatch" {
      return .AppleWatch
      }
      return nil
    }
}
print (Device.fromSlang("iWatch"))

可变方法(Mutating Methods)

方法可以声明为mutating。这样就允许改变隐藏参数selfcase值了3

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中枚举语法的基本用例。在开始迈向进阶之路之前,让我们重新审视文章开篇给出的定义,看看现在是否变得更清晰了。

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

现在我们已经对这个定义更加清晰了。确实,如果我们添加关联值和嵌套,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附加功能,这意味着无须依靠额外函数就能实现4

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

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

枚举进阶(Advanced Enum Usage)

协议(Protocols)

我已经提及了structsenums之间的相似性。除了附加方法的能力之外,Swift也允许你在枚举中使用协议(Protocols)协议扩展(Protocol Extension)

Swift协议定义一个接口或类型以供其他数据结构来遵循。enum当然也不例外。我们先从Swift标准库中的一个例子开始.

CustomStringConvertible是一个以打印为目的的自定义格式化输出的类型。

protocol CustomStringConvertible {
  var description: String { get }
}

该协议只有一个要求,即一个只读(getter)类型的字符串(String类型)。我们可以很容易为enum实现这个协议。

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"
       }
   }
}

let action = Trade.Buy
print("this action is \(action)")
// prints: this action is We're buying something

一些协议的实现可能需要根据内部状态来相应处理要求。例如定义一个管理银行账号的协议。

protocol AccountCompatible {
  var remainingFunds: Int { get }
  mutating func addFunds(amount: Int) throws
  mutating func removeFunds(amount: Int) throws
}

你也许会简单地拿struct实现这个协议,但是考虑应用的上下文,enum是一个更明智的处理方法。不过你无法添加一个存储属性到enum中,就像var remainingFuns:Int。那么你会如何构造呢?答案灰常简单,你可以使用关联值完美解决:

enum Account {
  case Empty
  case Funds(remaining: Int)

  enum Error: ErrorType {
    case Overdraft(amount: Int)
  }

  var remainingFunds: Int {
    switch self {
    case Empty: return 0
    case Funds(let remaining): return remaining
    }
  }
}

为了保持代码清爽,我们可以在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 Error.Overdraft(amount: -newAmount)
    } else if newAmount == 0 {
      self = .Empty
    } else {
      self = .Funds(remaining: newAmount)
    }
  }

  mutating func removeFunds(amount: Int) throws {
    try self.addFunds(amount * -1)
  }

}
var account = Account.Funds(remaining: 20)
print("add: ", try? account.addFunds(10))
print ("remove 1: ", try? account.removeFunds(15))
print ("remove 2: ", try? account.removeFunds(55))
// prints:
// : add:  Optional(())
// : remove 1:  Optional(())
// : remove 2:  nil

正如你所看见的,我们通过将值存储到enum cases中实现了协议所有要求项。如此做法还有一个妙不可言的地方:现在整个代码基础上你只需要一个模式匹配就能测试空账号输入的情况。你不需要关心剩余资金是否等于零。

同时,我们也在账号(Accout)中内嵌了一个遵循ErrorType协议的枚举,这样我们就可以使用Swift2.0语法来进行错误处理了。这里给出更详细的使用案例教程。

扩展(Extensions)

正如刚才所见,枚举也可以进行扩展。最明显的用例就是将枚举的casemethod分离,这样阅读你的代码能够简单快速地消化掉enum内容,紧接着转移到方法定义:

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)"
    }
  }
}

枚举泛型(Generic Enums)

枚举也支持泛型参数定义。你可以使用它们以适应枚举中的关联值。就拿直接来自Swift标准库中的简单例子来说,即Optional类型。你主要可能通过以下几种方式使用它:可选链(optional chaining(?))、if-let可选绑定、guard let、或switch,但是从语法角度来说你也可以这么使用Optional:

let aValue = Optional<Int>.Some(5)
let noValue = Optional<Int>.None
if noValue == Optional.None { print("No value") }

这是Optional最直接的用例,并未使用任何语法糖,但是不可否认Swift中语法糖的加入使得你的工作更简单。如果你观察上面的实例代码,你恐怕已经猜到Optional内部实现是这样的5:

// 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中所有在classstruct中奏效的类型约束,在enum中同样适用。

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

递归 / 间接(Indirect)类型

间接类型是 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>)
}

这是一个很强大的特性,可以让我们用非常简洁的方式来定义一个有着复杂关联的数据结构。

使用自定义类型作为枚举的值

如果我们忽略关联值,则枚举的值就只能是整型,浮点型,字符串和布尔类型。如果想要支持别的类型,则可以通过实现 StringLiteralConvertible 协议来完成,这可以让我们通过对字符串的序列化和反序列化来使枚举支持自定义类型。

作为一个例子,假设我们要定义一个枚举来保存不同的 iOS 设备的屏幕尺寸:

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)
}

然而,这段代码不能通过编译。因为 CGPoint 并不是一个常量,不能用来定义枚举的值。我们需要为想要支持的自定义类型增加一个扩展,让其实现 StringLiteralConvertible 协议。这个协议要求我们实现三个构造方法,这三个方法都需要使用一个String类型的参数,并且我们需要将这个字符串转换成我们需要的类型(此处是CGSize)。

extension CGSize: StringLiteralConvertible {
    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)
    }
}

现在就可以来实现我们需要的枚举了,不过这里有一个缺点:初始化的值必须写成字符串形式,因为这就是我们定义的枚举需要接受的类型(记住,我们实现了 StringLiteralConvertible,因此String可以转化成CGSize类型)

enum Devices: CGSize {
   case iPhone3GS = "{320, 480}"
   case iPhone5 = "{320, 568}"
   case iPhone6 = "{375, 667}"
   case iPhone6Plus = "{414, 736}"
}

终于,我们可以使用 CGPoint 类型的枚举了。需要注意的是,当要获取真实的 CGPoint 的值的时候,我们需要访问枚举的是 rawValue 属性。

let a = Devices.iPhone5
let b = a.rawValue
print("the phone size string is \(a), width is \(b.width), height is \(b.height)")
// prints : the phone size string is iPhone5, width is 320.0, height is 568.0

使用字符串序列化的形式,会让使用自定义类型的枚举比较困难,然而在某些特定的情况下,这也会给我们增加不少便利(比较使用NSColor / UIColor的时候)。不仅如此,我们完全可以对自己定义的类型使用这个方法。

对枚举的关联值进行比较

在通常情况下,枚举是很容易进行相等性判断的。一个简单的 enum T { case a, b } 实现默认支持相等性判断 T.a == T.b, T.b != T.a

然而,一旦我们为枚举增加了关联值,Swift 就没有办法正确地为两个枚举进行相等性判断,需要我们自己实现 == 运行符。这并不是很困难:

enum Trade {
    case Buy(stock: String, amount: Int)
    case Sell(stock: String, amount: Int)
}
func ==(lhs: Trade, rhs: Trade) -> 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 是匹配的时候(比如 Buy 和 Buy)才对它们的真实关联值进行判断。

自定义构造方法

静态方法 一节当中我们已经提到它们可以作为从不同数据构造枚举的方便形式。在之前的例子里也展示过,对出版社经常误用的苹果设备名返回正确的名字:

enum Device { 
    case AppleWatch 
    static func fromSlang(term: String) -> Device? {
      if term == "iWatch" {
      return .AppleWatch
      }
      return nil
    }
}

我们也可以使用自定义构造方法来替换静态方法。枚举与结构体和类的构造方法最大的不同在于,枚举的构造方法需要将隐式的 self 属性设置为正确的 case。

enum Device { 
    case AppleWatch 
    init?(term: String) {
      if term == "iWatch" {
      self = .AppleWatch
      }
      return nil
    }
}

在这个例子中,我们使用了可失败(failable)的构造方法。但是,普通的构造方法也可以工作得很好:

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 aNumber = NumberCategory(number: 100)
print(aNumber)
// prints: "Small"

对枚举的 case 进行迭代

一个特别经常被问到的问题就是如何对枚举中的 case 进行迭代。可惜的是,枚举并没有遵守SequenceType协议,因此没有一个官方的做法来对其进行迭代。取决于枚举的类型,对其进行迭代可能也简单,也有可能很困难。在StackOverflow上有一个很好的讨论贴。贴子里面讨论到的不同情况太多了,如果只在这里摘取一些会有片面性,而如果将全部情况都列出来,则会太多。

对 Objective-C 的支持

基于整型的枚举,如 enum Bit: Int { case Zero = 0; case One = 1 } 可以通过 @objc 标识来将其桥接到 Objective-C 当中。然而,一旦使用整型之外的类型(如 String)或者开始使用关联值,我们就无法在 Objective-C 当中使用这些枚举了。

有一个名为_ObjectiveCBridgeable的隐藏协议,可以让规范我们以定义合适的方法,如此一来,Swift 便可以正确地将枚举转成 Objective-C 类型,但我猜这个协议被隐藏起来一定是有原因的。然而,从理论上来讲,这个协议还是允许我们将枚举(包括其实枚举值)正确地桥接到 Objective-C 当中。

但是,我们并不一定非要使用上面提到的这个方法。为枚举添加两个方法,使用 @objc 定义一个替代类型,如此一来我们便可以自由地将枚举进行转换了,并且这种方式不需要遵守私有协议:

enum Trade {
    case Buy(stock: String, amount: Int)
    case Sell(stock: String, amount: Int)
}

// 这个类型也可以定义在 Objective-C 的代码中
@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
    }
}

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 中的 NSObject 基础类型(我们也可以直接使用 NSDictionary),但是,当我们碰到一些确实需要在 Objective-C 当中获取有关联值的枚举时,这是一个可以使用的方法。

枚举底层

Erica Sadun 写过一篇很流弊的关于枚举底层的博客,涉及到枚举底层的方方面面。在生产代码中绝不应该使用到这些东西,但是学习一下还是相当有趣的。在这里,我准备只提到那篇博客中一条,如果想了解更多,请移步到原文:

枚举通常都是一个字节长度。[...]如果你真的很傻很天真,你当然可以定义一个有成百上千个 case 的枚举,在这种情况下,取决于最少所需要的比特数,枚举可能占据两个字节或者更多。

Swift 标准库中的枚举

在我们准备继续探索枚举在项目中的不同用例之前,先看一下在 Swift 标准库当中是如何使用枚举可能会更诱人,所以现在让我们先来看看。

Bit 这个枚举有两个值,OneZero。它被作为 CollectionOfOne<T> 中的 Index 类型。

FloatingPointClassification 这个枚举定义了一系列 IEEE 754 可能的类别,比如 NegativeInfinity, PositiveZeroSignalingNaN

Mirror.AncestorRepresentation Mirror.DisplayStyle 这两个枚举被用在 Swift 反射 API 的上下文当中。

Optional 这个就不用多说了

Process 这个枚举包含了当前进程的命令行参数(Process.argc, Process.arguments)。这是一个相当有趣的枚举类型,因为在 Swift 1.0 当中,它是被作为一个结构体来实现的。

实践用例

我们已经在前面几个小节当中看过了许多有用的枚举类型。包括 OptionalEither, FileNode 还有二叉树。然而,还存在很多场合,使用枚举要胜过使用结构体和类。一般来讲,如果问题可以被分解为有限的不同类别,则使用枚举应该就是正确的选择。即使只有两种 case,这也是一个使用枚举的完美场景,正如 Optional 和 Either 类型所展示的。

以下列举了一些枚举类型在实战中的使用示例,可以用来点燃你的创造力。

错误处理

说到枚举的实践使用,当然少不了在 Swift 2.0 当中新推出的错误处理。标记为可抛出的函数可以抛出任何遵守了 ErrorType 空协议的类型。正如 Swift 官方文档中所写的:

Swift 的枚举特别适用于构建一组相关的错误状态,可以通过关联值来为其增加额外的附加信息。

作为一个示例,我们来看下流行的JSON解析框架 Argo。当 JSON 解析失败的时候,它有可能是以下两种主要原因:

  1. JSON 数据缺少某些最终模型所需要的键(比如你的模型有一个 username 的属性,但是 JSON 中缺少了)
  2. 存在类型不匹配,比如说 username 需要的是 String 类型,而 JSON 中包含的是 NSNull6

除此之外,Argo 还为不包含在上述两个类别中的错误提供了自定义错误。它们的 ErrorType 枚举是类似这样的:

enum DecodeError: ErrorType {
  case TypeMismatch(expected: String, actual: String)
  case MissingKey(String)
  case Custom(String)
}

所有的 case 都有一个关联值用来包含关于错误的附加信息。

一个更加通用的用于完整 HTTP / REST API 错误处理的ErrorType应该是类似这样的:

enum APIError : ErrorType {
    // 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: ErrorType)
    // The Argo decoding Failed
    case JSONMappingError(converstionError: DecodeError)
}

这个 ErrorType 实现了完整的 REST 程序栈解析有可能出现的错误,包含了所有在解析结构体与类时会出现的错误。

如果你看得够仔细,会发现在JSONMappingError中,我们将Argo中的DecodeError封装到了我们的APIError类型当中,因为我们会用 Argo 来作实际的 JSON 解析。

更多关于ErrorType以及此种枚举类型的示例可以参看官方文档

观察者模式

在 Swift 当中,有许多方法来构建观察模式。如果使用 @objc 兼容标记,则我们可以使用 NSNotificationCenter 或者 KVO。即使不用这个标记,didSet语法也可以很容易地实现简单的观察模式。在这里可以使用枚举,它可以使被观察者的变化更加清晰明了。设想我们要对一个集合进行观察。如果我们稍微思考一下就会发现这只有几种可能的情况:一个或多个项被插入,一个或多个项被删除,一个或多个项被更新。这听起来就是枚举可以完成的工作:

enum Change {
     case Insertion(items: [Item])
     case Deletion(items: [Item])
     case Update(items: [Item])
}

之后,观察对象就可以使用一个很简洁的方式来获取已经发生的事情的详细信息。这也可以通过为其增加 oldValue newValue 的简单方法来扩展它的功能。

状态码

如果我们正在使用一个外部系统,而这个系统使用了状态码(或者错误码)来传递错误信息,类似 HTTP 状态码,这种情况下枚举就是一种很明显并且很好的方式来对信息进行封装7

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

结果类型映射(Map Result Types)

枚举也经常被用于将 JSON 解析后的结果映射成 Swift 的原生类型。这里有一个简短的例子:

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

类似地,如果我们解析了其它的东西,也可以使用这种方式将解析结果转化我们 Swift 的类型。

UIKit 标识

枚举可以用来将字符串类型的重用标识或者 storyboard 标识映射为类型系统可以进行检查的类型。假设我们有一个拥有很多原型 Cell 的 UITableView:

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

单位

单位以及单位转换是另一个使用枚举的绝佳场合。可以将单位及其对应的转换率映射起来,然后添加方法来对单位进行自动的转换。以下是一个相当简单的示例:

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))

另一个示例是货币的转换。以及数学符号(比如角度与弧度)也可以从中受益。

游戏

游戏也是枚举中的另一个相当好的用例,屏幕上的大多数实体都属于一个特定种族的类型(敌人,障碍,纹理,...)。相对于本地的 iOS 或者 Mac 应用,游戏更像是一个白板。即开发游戏我们可以使用全新的对象以及全新的关联创造一个全新的世界,而 iOS 或者 OSX 需要使用预定义的 UIButtons,UITableViews,UITableViewCells 或者 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 {}

字符串类型化

在一个稍微大一点的 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开发者8)。

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 端点进行映射。

链表

Airspeed Velocity有一篇极好的文章说明了如何使用枚举来实现一个链表。那篇文章中的大多数代码都超出了枚举的知识,并涉及到了大量其它有趣的主题9,但是,链表最基本的定义是类似这样的(我对其进行了一些简化):

enum List {
    case End
    indirect case Node(Int, next: List)
}

每一个节点(Node) case 都指向了下一个 case, 通过使用枚举而非其它类型,我们可以避免使用一个可选的 next 类型以用来表示链表的结束。

Airspeed Velocity 还写过一篇超赞的博客,关于如何使用 Swift 的间接枚举类型来实现红黑树,所以如果你已经阅读过关于链表的博客,你可能想继续阅读这篇关于红黑树的博客

设置字典(Setting Dictionaries)

这是 Erica Sadun 提出的非常非常机智的解决方案。简单来讲,就是任何我们需要用一个属性的字典来对一个项进行设置的时候,都应该使用一系列有关联值的枚举来替代。使用这方法,类型检查系统可以确保配置的值都是正确的类型。

关于更多的细节,以及合适的例子,可以阅读下她的文章

局限

与之前类似,我将会用一系列枚举的局限性来结束本篇文章。

提取关联值

David Owens写过一篇文章,他觉得当前的关联值提取方式是很笨重的。我墙裂推荐你去看一下他的原文,在这里我对它的要旨进行下说明:为了从一个枚举中获取关联值,我们必须使用模式匹配。然而,关联值就是关联在特定枚举 case 的高效元组。而元组是可以使用更简单的方式来获取它内部值,即 .keyword 或者 .0

// Enums
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 (译者注:一开始我不明白 rdar 是啥意思,后来我 google 了下,如果你也有兴趣,也可以自己去搜索一下)

相等性

拥有关联值的枚举没有遵守 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
    }
}

元组(Tuples)

最大的问题就是对元组的支持。我喜欢使用元组,它们可以使很多事情变得更简单,但是他们目前还处于无文档状态并且在很多场合都无法使用。在枚举当中,我们无法使用元组作为枚举的值:

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")
}

这似乎看起来并不是一个最好的示例,但是我们一旦开始使用枚举,就会经常陷入到需要用到类似上面这个示例的情形中。

迭代枚举的所有case

这个我们已经在前面讨论过了。目前还没有一个很好的方法来获得枚举中的所有 case 的集合以使我们可以对其进行迭代。

默认关联值

另一个会碰到的事是枚举的关联值总是类型,但是我们却无法为这些类型指定默认值。假设有这样一种情况:

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,但是角色的默认设置依然会被映射。

來源:简书

2015-11-30 18:24:02 strawberry2013 阅读数 2430

enum Type: Int{
    case A=1
    case B=3
    
    //普通方法
    func toString()->String{
        var info=""
        
        switch self{
        case .A:
            info = "一"
            
        case .B():
            info = "三"
        }
        return info
    }
    
    //静态方法
    static func toString(let type:Type)->String{
        return "toString() -> \(type)"
    }
    
    // 成员
    var description:String{
        var info=""
        switch self{
        case .A:
            info = "一"
            
        case .B():
            info = "三"
        }
        return "description field = \(type)"
    }
}

let type = Type.B

print(Type.toString(type))
print(type.description)

//toString() -> B
//description field = B


iOS swift 面试题

阅读数 2468

【iOS】swift 枚举

阅读数 312