Swift枚举类型(enumeration)

枚举类型的知识点:基本语法、关联值、原始值、枚举类型和方法、递归枚举

枚举语法

要点
  • 枚举类型名大写开头
  • 多个成员值可以出现在同一行,用逗号隔开
  • 枚举类型跟其他自定义类型一样,可以通过类型推断来进行初始化。
  • 已知成员类型,可以省略枚举类型名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 枚举类型名大写开头
enum CompassPoint {
case north
case south
case east
case west
}

// 多个成员值可以出现在同一行上,用逗号隔开
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune

}

// 类型推断和省略枚举类型名
var directionToHead = CompassPoint.west //初始化使用类型推断
directionToHead = .east //已知类型时,可以省略枚举类型名

使用Swift语句匹配枚举值

在判断一个枚举类型的值时,switch语句必须穷举所有情况,以确保了枚举成员不会被意外遗漏。

不需要匹配每个枚举成员时,可以使用一个default分支来涵盖所有未明确处理的枚举成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//穷举匹配
directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// 打印 "Watch out for penguins”

// 使用default分支匹配
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// 打印 "Mostly harmless”

与Objective-C不同

Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的CompassPoint例子中,north,south,east和west不会被隐式地赋值为0,1,2和3。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的CompassPoint类型。

Swift中的枚举不必给每个枚举成员提供一个值。如果给枚举成员提供一个值,则该值的类型可能是字符串、字符、整型或浮点型。

原始值

原始值是在定义枚举类型时被预先填充的值,对于一个特定的枚举成员,它的原始值是是始终不变的。如上所述,Swift中不必给每个枚举成员提供一个值。如果给枚举成员提供一个值,则该值的类型可能是字符串、字符、整型或浮点型。

指定原始值类型

指定Int类型,会给每个成员设置一个该类型的原始值

1
2
3
4
5
6
//指定原始值类型
enum TextAlignment: Int{
case left //原始值为0
case right //原始值为1
case center //原始值为2
}
指定原始值

使用rawValue获取成员变量的原始值;

每个带原始值的枚举类型都可以用rawValue参数创建,并返回可空枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum TextAlignment: Int{
case left = 20
case right = 30
case center = 50
}
//枚举成员转换成原始值
let myRawValue = TextAlignment.left
//myRawValue为20

//由原始值获取枚举成员
let textRawValue = 60
if let myAlignment = TextAlignment(rawValue: textRawValue){
//转化成功
print("successfully converted \(textRawValue) into a TextAlignment")
}else{
//转化失败
print("\(textRawValue) has no corresponding TextAlignment case")
}
字符串原始值

如果只声明枚举为字符串类型不指定原始值,Swift会自动给成员指定默认的字符串原始值。也可以只指定部分原始值。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum ProgrammingLanguage: String{
case swift = "swift"
case objectiveC = "Objective-C"
case c = "c"
case java = "java"
}
// 这里可以只给objectiveC指定原始值,因为其他case默认原始值和指定的值完全一样。
enum ProgrammingLanguage: String{
case swift
case objectiveC = "Objective-C"
case c
case java
}

方法

方法是和类型关联的函数。在Swift中,方法不仅可以与类关联,也可以和枚举关联。

特点:枚举的方法会接受一个隐式参数self,self类型即枚举类型;在Swift中,枚举类型为值类型,值类型的方法不能对self进行修改。**如果需要值类型的方法修改self,需要标记这个方法为mutating**,否则会产生编译错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
enum Lightbulb{
case on
case off

// 获取表面温度
func surfaceTemperature(forAmbientTemperature ambient: Double) -> Double{
switch self{
case .on:
return ambient + 150
case .off:
return ambient
}
}

// 开关方法:对self进行修改的方法需要用mutating标记
mutating func toggle(){
switch self{
case .on:
self = .off
case .off:
self = .on
}
}
}

var bulb = Lightbulb.on
var ambientTemperature = 77.0
var bulbTemperature = bulb.surfaceTemperature(forAmbientTemperature:ambientTemperature)
print("the bulb's surface temperature is \(bulbTemperature)")

关联值

关联值给枚举类型带来了一些变化,因为原始值只是给成员绑定不同的静态值,不同的成员可以有不同类型的关联值。比如我们创建一个枚举类型来记录一些基本图形的尺寸,每种图形都有不同的属性。正方形需要一个边长就够了,长方形需要宽和高两个值等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum ShapeDimensions{
case square(side: Double)// 正方形的关联值:边长
case rectangle(width: Double, height: Double)// 长方形的关联值:宽高
case point

func area()->Double{
switch self{
case .square(side: side):
return side * side
case .rectangle(width: width, height: height):
return width * height
case .point
return 0
}
}
}

var squareShape = ShapeDimensions.square(side: 12.0)
var rectShape = ShapeDimensions.rectangle(width: 3.0, height: 5.0)
var pointShape = ShapeDimensions.point
print("point's area = \(pointShape.area())")

关联值可以被提取出来作为switch语句 的一部分。可以在switch的case分支代码中提取每个关联值作为一个常量(用let前缀)或作为一个变量(用var前缀)来使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Barcode{
case upc(Int,Int,Int,Int)
case qrCode(String)
}

var productCode = Barcode.upc(8,83734,75744,4)
//同一个商品可以被分配一个不同类型的条形码
productCode = .qrCode("ABCDEGH")

switch productBarcode{
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC:\(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code:\(productCode)")
}

如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个let或者var:

1
2
3
4
5
6
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}

递归枚举

枚举成员可以有不同的关联值,那么成员可以关联自己类型的关联值吗?这就涉及到递归枚举了。在Swift中,编译器必须知道程序中每种类型的每个实例占据多少内存空间,当编译器需要判断一个枚举实例需要多少内存的时候,会查看每种枚举值,并找出需要最多内存的那个。如果给枚举成员附上枚举类型的关联值,则这个枚举类型需要无穷内存。为了解决这个问题,Swift引入了一个间接层,不需要再判断递归的成员需要多少内存,而是用关键字indirect告诉编译器把枚举的数据放到一个指针指向的地方,则编译器会保存一个指向关联数据的指针,把数据放在内存中的其他地方,而不是让枚举类型的实例有足够的空间存放数据。

1
2
3
4
5
6
7
// 用indirect标记枚举类型
indirect enum FamilyTree{
case noKnownParents
case oneKnownParent(name: String, ancestors: FamilyTree)
case twoKnownParents(fatherName: String, fatherAncestors: FamilyTree,
motherName: String, motherAncestors: FamilyTree)
}

还可以把递归的成员单独标记为间接:

1
2
3
4
5
6
7
8
9
10
11
12
enum FamilyTree{
case noKnownParents
indirect case oneKnownParent(name: String, ancestors: FamilyTree)
indirect case twoKnownparents(father: String, fatherAncestors: FamilyTree,
mother: String, motherAncestors: FamilyTree)
}

let fredAncestors = FamilyTree.twoKnownParents(father: "Fred Sr",
fatherAncestors: .oneKnownParent(name:"Beth", fatherAncestors: .noKnownParents),
mother:"Marsha",
motherAncestors: .noKnownParents
)