日常生活中,经常会遇到一组组的数据,它们像兄弟姐妹一样,彼此结伴出现。比较常见的如:东、南、西、北
、男、女
等。这样的数据在软件领域,我们通常会用一种被称为枚举
的类型专门表示。今天我们就来聊聊枚举的使用。
在Swift
中,最简单的定义枚举的方式如下:
enum Gender {
case male
case female
case other
}
这里我们通过enum
关键字定义了名为Gender
的枚举,用来约束性别的几种可能,这些可能性通过case
来逐条声明。其实这三条case
也可以合并成一行书写,像下面这样,也是可以的:
enum Gender {
case male , female , other
}
定义完枚举类型后,我们来演示下怎么使用它。想象定义一个类,用来描述人类,它包含两个基本字段:age
、gender
,分别代表年龄和性别。其中age
的类型显而易见,而gender
的类型,就用我们前面定义的枚举即可,请看下面的代码:
class People {
var age:Int
var gender:Gender
init(age:Int, gender:Gender){
self.age = age
self.gender = gender
}
}
let aGirl = People(age:8, gender:.female)
在上面代码的最后一行,我们定义了一位8岁的女性,所以她是为小女孩,下面我们尝试在People
类里让她开口说话。定义一个sayHello
方法,它可以判断自己的性别,然后给出不同的反馈。这里涉及到的知识点,就是对枚举类型的判断。具体请看下面的代码:
func sayHello()->String{
switch gender{
case .male:
return "I am a boy."
case .female:
return "I am a girl."
case .other:
return "Hello."
}
}
当上面的方法被定义到People
中后,我们就可以这样测试一下效果:
let aGirl = People(age:10, gender:.female)
print(aGirl.sayHello())
这里面要强调一点,枚举一旦参与switch
语句判断,它的每一项都要接受比较。比如上面的场景,如果我想说,.other
的情况我不关心,那么如果我删除了它,而使代码成为这样的话:
func sayHello()->String{
switch gender{
case .male:
return "I am a boy"
case .female:
return "I am a girl"
}
很遗憾,我们收到一个Switch must be exhaustive
编译错误,提醒我们要判断完整。如果你实在需要省略部分case
的选项的话,可以引入default
,来匹配你不关心的选项,比如这样:
func sayHello()->String{
switch gender{
case .female:
return "I am a girl."
default:
return "Hello."
}
}
枚举除了和switch
语句走得比较近之外,还经常参与迭代,也就是和for
循环关系同样紧密。如果想用for
循环遍历枚举的每一项的话,我们需要声明该枚举支持迭代,声明的方式非常简单,需要在类名后用:
跟随CaseIterable
,代码如下:
enum Gender : CaseIterable{
case male , female , other
}
我们在之前介绍过,如果用类与类的继承关系,会用到:
跟随它要继承的父类。而在上面的用法中,并不是在继承父类,属于比较特殊的情况,这个我们以后再详细介绍。话说回来,只要我们追加了魔法般的CaseIterable
之后,我们就可以很方便的对枚举进行迭代了。
for gender in Gender.allCases{
print(gender)
}
输出的结果为:
male
female
other
这里不要疑惑,虽然打印到控制台的内容是字符串。但是本质上for
循环体内的gender
对应的还是Gender
类型,这跟People
定义的gender
变量类型是完全相同的。如果你想验证一下的话,可以用下面的代码:
for gender in Gender.allCases{
print(type(of:gender))
}
print(type(of: aGirl.gender))
所以type
函数可以帮我们确定一个参数的类型。那么上面神奇的Gender.allCases
,咱们也可以观察下。
print(type(of: Gender.allCases))
你将得到结果Array<Gender>
,所以它就是个Array
,你可以像把玩其他数组一样把玩Gender.allCases
,比如这样print(Gender.allCases.count)
可以打印该枚举的大小。
其实有了switch
,有了for
,我们就能对枚举做很多事了。但是某些情况下,可能还是有点力不从心。比如我们尝试构造一个星期的枚举。
enum Week{
case monday , tuesday , wednesday , thursday , friday , saturday , sunday
}
如果我想判断周末是不是到了,诚然可以去跟.saturday
、.sunday
分别比较一下,也不是不行。但是如果能直接判断一个index>5
,那不是很容易么?这就牵扯到枚举类型原始值
的概念。在这里,我希望一个星期的每一天,都能对应一个整型数字,所以需要这样写:
enum Week : Int{ // <--注意这里
case monday , tuesday , wednesday , thursday , friday , saturday , sunday
}
这样我们的每个枚举就拥有了原始值(rawValue)
,以.monday
为例,可以这么访问print(Week.monday.rawValue)
,略显尴尬的是,看到的结果是0
。这也可以理解,计算机世界里,牵扯到计数一般都是从0
开始走的。所以我们需要手动给monday
分配1
,也非常简单,就像这样:
enum Week : Int{
case monday = 1 , tuesday , wednesday , thursday , friday , saturday , sunday
}
如果你想事无巨细地把枚举里的每个元素都分配一个原始值
当然是可以的,但其实swift
已经很聪明的帮你把枚举中的后续元素以1
为起点,逐个的修正好了。想验证的话,我们可以通过前面的for
循环,同时别忘了,要在枚举命名处的:
后面追加CaseIterable
。最后的代码如下:
enum Week : Int , CaseIterable{
case monday = 1 , tuesday , wednesday , thursday , friday , saturday , sunday
}
for week in Week.allCases{
print(week.rawValue)
}
有了原始值
的支持,我们获取一个具体的枚举元素就可以用另外一个方式了。比如:
print(Week(rawValue: 7) == Week.sunday)
返回的就是true
,足以证明通过原始值
拿到的枚举与直接点出来的枚举是一回事。你可能会有疑问,没有原始值
的枚举明明也可以工作的不错,那么原始值
的加入到底能带来什么呢?想象一下,你的业务是跟一个关系型数据库(SQL)打交道的,最终存储在数据库里的数据都是符合数据库标准的,如果你尝试把Week.sunday
存进去的话,你甚至不知道该给这个数据列定义什么类型。而有原始值
的支持,你就可以在数据库支持的类型与Swift
的枚举类型中做优雅的转换工作了。
还有一点需要注意的是,在上面的例子中,我把原始值
的类型定义为Int
,只是觉得它正好满足星期几
的这种需求。如果你的业务有其他场景,完全可以用其他类型表示原始值
。比如下面这个例子:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
在前面的例子中,我们都在用枚举表示某种定数,比如从星期一到星期天,不外乎7种情况。但其实swift
的枚举设计得更为强大,可以存储变幻多端的数据,请看下面的代码:
enum ServerResponse {
case ok(String)
case failure(Int , String)
}
let success = ServerResponse.ok("hello world")
let failure = ServerResponse.failure(502,"Bad Gateway")
switch failure {
case let .ok(context):
print("Response is \(context)")
case let .failure(stateCode , message):
print("Failure... \(stateCode) \(message)")
}
这里的描述的场景是服务器HTTP
返回的数据。如果成功的话,就是ok
,并提供服务器返回的内容;如果失败的话,要提供一个状态码,以及错误的详细信息。你可以通过替换上面代码中的switch failure
为switch success
观察不同的输出效果。
上面的演示只是枚举
能力的冰山一角,更多高阶的功能就等待你自己去探索发觉了。
参考资料:
- https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html
- https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html