Swift学习40:枚举

张建 lol

枚举

  • 枚举 是一种 数据类型,只包含自定义的特定数据

  • 枚举 是一组有共同特性的数据集合,使你可以在你的代码中以 类型安全 的方式来使用这些值。

作用

OC 相比,枚举只支持 整数型;而在 Swift 中枚举有很多功能:

  • 可以定义 成员类型,支持 Int、Double、String等基础类型,也有 默认枚举值

  • 可以 定义构造函数(initializers)来提供一个初始成员值,可以在原始的实现基础上扩展他们的功能

  • 可以 关联枚举值

  • 可以遵守协议(protocol)提供标准化功能

  • 可以 嵌套枚举

枚举的语法

  • enum 关键字来定义一个枚举,然后将其所有的定义内容放在一个大括号 {}

  • 多个成员值可以出现在同一行中,用逗号隔开

  • 每个枚举都定义了一个全新的类型。正如swift中其它类型那样,它的名称(例如: ComP)需要首字母大写。给枚举类型起一个单数的而不是复数的名字,从而使得他们能够顾名思义

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
30
31
32
33
// 写法一
// 不需要逗号隔开
enum ComP {
case east
case south
case west
case north
}

// 写法二
// 也可以直接一个case,然后逗号隔开
enum ComP2 {
case east, south, west, north
}

// 定义一个枚举变量
var comp = ComP.east

/*
String类型的enum
- =左边的值是枚举值,例如 MON
- =右边的值在swift中称为 RawValue(原始值),例如 "MON"
- 两者的关系为:case 枚举值 = rawValue原始值
*/
enum Week: String {
case MON = "MON"
case TUE = "TUE"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case SAT = "SAT"
case SUN = "SUN"
}

如果不想写枚举后的字符串,也可以使用 隐士RawValue 分配

1
2
3
4
5
6
7
8
enum Week: String{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
var w = Week.MON.rawValue
print(w)

=======
MON

使用 Switch 语句来匹配枚举值

  • 你可以用 switch 语句来匹配每一个单独的枚举值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// switch 访问枚举
let week = Week.MON
switch week {
case .TUE:
print("TUE")
case .WED:
print("WED")
case .THU:
print("TUE")
case .FRI:
print("FRI")
case .SAT:
print("SAT")
case .SUN:
print("SUN")
case .MON:
print("MON")
}

======
MON
  • CaseIterable协议 通常用于没有关联值的枚举,只需要遵守 CaseIterable 协议来 允许枚举被遍历,写法:在枚举名后面写 :CaseIterable,然后通过 allCases 获取所有枚举值,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 遍历枚举
enum Direction : CaseIterable{
case east
case south
case west
case north
}
for direction in Direction.allCases {
print(direction)
}
或 高阶函数
Direction.allCases.map { print($0) }

=========
east
south
west
north

string 类型

1
2
3
4
5
6
7
8
9
10
11
12
// string 类型
enum Direction2: String {
case east, south, west, north
}
extension Direction2: CaseIterable {}
Direction2.allCases.map{ print( $0 ) }

=======
east
south
west
north

枚举 关联值 和 枚举值

枚举中有两个很容易混淆的概念:原始值(raw value)关联值(associated value),两个词听起来比较模糊,下面简单介绍一下:

  • 原始值(raw value)

枚举成员可以用相同类型的默认值预先填充,这样的值我们称为 原始值(raw value)

下面的 StudentType 中三个成员分别被 Int 类型的 10 、15、 20 填充表示不同阶段学生的年龄。注意:Int 修饰的是 StudentType 成员原始值的类型而不是 StudentType 的类型,StudentType类型从定义开始就是一个全新的枚举类型。

1
2
3
4
5
enum StudentType: Int{
case pupil = 10
case middleSchoolStudent = 15
case collegeStudents = 20
}

定义好StudentType成员的原始值之后,我们可以使用枚举成员的 rawValue 属性来 访问 成员的 原始值,或者是使用原始值初始化器来尝试创建一个枚举的新实例

1
2
3
4
5
6
7
8
9
10
//  常量student1值是 10
let student1 = StudentType.pupil.rawValue
// 变量student2值是 15
var student2 = StudentType.middleSchoolStudent.rawValue
// 使用成员rawValue属性创建一个`StudentType`枚举的新实例
let student3 = StudentType.init(rawValue: 15)
// student3的值是 Optional<Senson>.Type
type(of: student3)
// student4的值是nil,因为并不能通过整数30得到一个StudentType实例的值
let student4 = StudentType.init(rawValue: 30)

使用原始值初始化器这种方式初始化创建得到 StudentType 的实例 student4 是一个StudentType 的可选类型,因为并不是给定一个年龄就能找到对应的学生类型,比如在StudentType 中给定年龄为30就找不到对应的学生类型(很可能30岁的人已经是博士了)。所以原始值初始化器是一个可失败初始化器。

总结一句:原始值是为枚举的成员们绑定了一组 类型必须相同值不同 的固定的值(可能是整型,浮点型,字符类型等等)。这样很好解释为什么提供原始值的时候用的是等号。

  • 枚举的关联值(associated value)

    • 如果希望用枚举表示复杂的含义,关联更多的信息,就需要使用关联值了。
    • 他与普通类型的枚举不同:没有rawValue,没有rawValue的getter方法,没有初始化init方法
    • 关联值更像是为枚举的成员们绑定了一组类型,不同的成员可以是不同的类型(提供关联值时用的是括号)。例如下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
// 关联值
// 注:当使用了关联值后,就没有RawValue了
// 因为:case可以用一组值来表示,而rawValue是单个的值
// 定义一个表示学生类型的枚举类型 StudentType,他有三个成员分别是pupil、middleSchoolStudent、collegeStudents
enum StudentType {
// 学生
case pupil(String)
// 中学
case middleSchoolStudent(Int, String)
// 大学
case collegeStudents(Int, String)
}

这里我们并没有为 StudentType 的成员提供具体的值,而是为他们绑定了不同的类型,分别是pupil 绑定 String 类型,middleSchoolStudent collegeStudents 绑定 (Int, String) 元祖类型。接下来就可以创建不同 StudentType 枚举实例并为对应的成员赋值了。

1
2
3
4
5
6
7
8
// student1 是一个StudentType类型的常量,其值为pupil(小学生),特征是"have fun"(总是在玩耍)
let student1 = StudentType.pupil("have fun")

// student2 是一个StudentType类型的常量,其值为middleSchoolStudent(中学生),特征是 7, "always study"(一周7天总是在学习)
let student2 = StudentType.middleSchoolStudent(7, "always study")

// student3 是一个StudentType类型的常量,其值为collegeStudent(大学生),特征是 7, "always LOL"(一周7天总是在撸啊撸)
let student3 = StudentType.middleSchoolStudent(7, "always LOL")

这个时候如果需要判断某个 StudentType 实例的具体的值就需要这样做了:

1
2
3
4
5
6
7
8
switch student3 {
case .pupil(let things):
print("is a pupil and \(things)")
case .middleSchoolStudent(let day, let things):
print("is a middleSchoolStudent and \(day) days \(things)")
case .collegeStudent(let day, let things):
print("is a collegeStudent and \(day) days \(things)")
}

控制台输出:is a collegeStudent and 7 days always LOL,看到这你可能会想,是否可以为一个枚举成员提供原始值并且绑定类型呢,答案是不能的!因为首先给成员提供了固定的原始值,那他以后就不能改变了;而为成员提供关联值(绑定类型)就是为了创建枚举实例的时候赋值。这不是互相矛盾吗。

模式匹配

enum 中的模式匹配其实就是 匹配case枚举值,根据枚举类型,分为2种:

1、简单类型 的枚举的模式匹配;
2、自定义类型 的枚举(关联值)的模式匹配。

  • 简单enum的模式匹配

注:swift中的enum 模式匹配需要将所有情况都列举,或者使用 default 表示默认情况,否则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
// 模式匹配
enum Week: String{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
var current: Week?
switch current {
case .MON: print(Week.MON.rawValue)
case .TUE: print(Week.TUE.rawValue)
default: print("unknow day")
}

======
unknow day
  • 关联值类型的模式匹配

关联值类型的模式匹配有两种方式:

1、switch - case, 匹配所有case;
2、if - case, 匹配单个case

switch - case

定义关联值枚举:

1
2
3
4
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}

可以 let、var 修饰关联值的入参:

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 Shape{
// case枚举值后括号内的就是关联值,如 radius
case circle(radius: Double)
case rectangle(width: Int,height: Int)
}
// 定义枚举变量
let shape = Shape.circle(radius: 10.0)
switch shape {
// 关联外部
case let .circle(radius):
print("circle radius: \(radius)")
// 关联内部
case .rectangle(let width,var height):
height += 1
print("rectangle width: \(width) height: \(height)")
}
let shape2 = Shape.rectangle(width: 1, height: 2)
switch shape2 {
case let .circle(radius):
print("circle radius: \(radius)")
case .rectangle(let width,var height):
height += 10
print("rectangle width: \(width) height: \(height)")
}

=======
circle radius: 10.0
rectangle width: 1 height: 12

通过 if-case 匹配单个case,如下:

1
2
3
4
5
6
7
8
// 单个匹配
let circle = Shape.circle(radius: 5.0)
if case let Shape.circle(radius) = circle {
print("circle radius: \(radius)")
}

=====
circle radius: 5.0
  • 如果我们只关心 不同case的相同关联值(即关心不同case的某一个值),需要使用 同一个参数
    例如,案例中的x,如果分别使用x、y, 编译器会报错:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, height: Double)
}
let shape = Shape.circle(radius: 10)
switch shape{
case let .circle(x), let .square(20, x):
print(x)
default:
break
}

====
10.0

也可以使用 通配符_(表示匹配一切) 的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, height: Double)
}
let shape = Shape.rectangle(width: 2.0, height: 2.0)
switch shape {
case let .rectangle(x, _), let .square(_,x):
print("square:\(x * x)")
default:
break
}

=========
square:4.0

注:枚举使用过程中 不关心某一个关联值,可以使用 通配符_ 表示。OC 只能调用 swift 中 Int类型 的枚举。

属性 & 函数

  • enum 中可以包含 成员类型、计算属性、类型属性,不能包含 存储属性
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
enum Direct: Int {
// 成员类型
case up
case down
case left
case right

// 计算属性
var desc: String {
switch self {
case .up:
return "up"
case .down:
return "down"
default:
return "这是self"
}

}

// 存储属性:编译器报错,Enums must not contain stored properties
// var radius: Double

// 类型属性
static let height = 20.0

// 函数
func printSelf() {
print(desc)
}

// 异变函数
mutating func nextDay() {
if self == .up {
self = Direct(rawValue: 1)!
}else {
self = Direct(rawValue: self.rawValue+1)!
}
}
}

Direct.down.printSelf()
// 打印结果:down

var direct = Direct.left
direct.nextDay()
// 打印结果是 right
direct.printSelf()
// 打印结果是 right

为什么 structclass 中可以放 存储属性,而 enum 不可以?
struct 中可以包含存储属性,是因为其大小就是 存储属性的大小。而 enum 是不一样的(请查阅后文的enum大小讲解),enum枚举的大小是取决于case的个数的,如果没有超过255,enum的大小就是1字节(8位)

  • 可以在 enum 中定义 实例方法、static修饰的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum Week: Int {
case MON, TUE, WED, THU, FRI, SAT, SUN

// 实例方法
mutating func nextDay() {
if self == .SUN {
self = Week(rawValue: 0)!
}else {
self = Week(rawValue: self.rawValue+1)!
}
}
}
// 使用
var w = Week.MON
w.nextDay()
print(w)

=========
TUE
  • 可以在 enum 中定义 下标脚本,用于获取值
1
2
3
4
5
6
7
8
9
10
11
// 定义下标脚本
enum Direct {
case east, south, west, north
// 下标
subscript(index: Int) -> Int{
return index
}
}
var direct = Direct.east
print(direct[4])

  • 定义 构造器 用于 初始化值

枚举与结构体和类的构造方法最大的不同在于,枚举的构造方法需要将隐式的 self 属性设置为正确的 case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义初始化器
enum Num{
// 成员类型
case Small, Medium, Big, Huge
// 定义初始化器
init(number n: Int) {
if n < 10 { self = .Small }
else if n < 20 && n >= 10 { self = .Medium }
else if n < 30 && n >= 20 { self = .Big }
else { self = .Huge }
}
}
let num = Num(number: 30)
print(num)

枚举的嵌套

枚举的嵌套主要用于以下场景:
1、枚举嵌套枚举:一个复杂枚举是由一个或多个枚举组成;
2、结构体嵌套枚举:enum是不对外公开的,即是 私有 的。

  • enum 嵌套 enum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum CombineDirect {
// 枚举中嵌套枚举
enum BaseDirect {
case up,down,lett,right
}
// 通过内部枚举组合的枚举值
case leftUp(baseDirect1: BaseDirect,baseDirect2: BaseDirect)
case leftDown(baseDirect1: BaseDirect,baseDirect2: BaseDirect)
case rightUp(baseDirect1: BaseDirect,baseDirect2: BaseDirect)
case rightDown(baseDirect1: BaseDirect,baseDirect2: BaseDirect)
}
// 使用
let direct = CombineDirect.leftUp(baseDirect1: CombineDirect.BaseDirect.lett, baseDirect2: CombineDirect.BaseDirect.up)
print(direct)
  • 结构体 嵌套 enum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Skill {
// 结构体嵌套枚举
enum KeyType {
case up,down,lett,right
}

// 存储属性
let key: KeyType

// 函数
func launchSkill() {
switch key {
case .lett, .right:
print("left,right")
case .up, .down:
print("up,down")
}
}
}

枚举的递归

  • 枚举的递归:indirect

递归枚举 是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 indirect 来表示该成员可递归。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 一、用枚举表示链表结构
enum List<T> {
case end
// 表示case是用引用来存储
indirect case node(T,next: List<T>)
}

// 二、也可以将indirect放在enum前
// 表示整个enum是用引用来存储
indirect enum List<T>{
case end
case node(T, next: List<T>)
}

第一种写法,如果没有关键字 indirect,编译报错。原因是使用该枚举时,enum的大小需要case来确定,而case的大小又需要使用到enum大小。所以无法计算enmu的大小,于是报错!
根据编译器提示,需要使用 关键字indirect,意思就是将该枚举标记位递归,同时也支持标记单个case。

indirect关键字 其实就是通知编译器,我当前的enum是递归的,大小是不确定的,需要 分配一块堆区的内存空间,用来存放enum。

swift 和 oc 混编 enum

Swiftenum 非常强大,而在 OC 中,enum 仅仅只是一个 整数型
因此,OC调用Swift枚举,必须具备2个条件:①、用@objc关键字标记enum;②、当前enum应该是Int类型。

  • OC 中使用 Swift 枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
// 枚举
@objc enum Weak: Int {
case MON, TUE, WED, THU, FRI, SAT, SUN
}

// OC 使用
// 1. 用@objc关键字标记enum,当前enum应该是Int类型。
// 2. 导入头文件 xxx-Swift.h
- (void)testEnum{
Weak mon = WeakMON;
NSLog(@"mon:%ld",(long)mon);
// 打印结果 0
}
  • Swift 使用 OC 枚举,OC 中的枚举会自动转换成 swift中的enum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// OC定义1:NS_ENUM
NS_ENUM(NSInteger,ENUM_OC_TYPE){
Value1,
Value2
};

// OC定义2:typedef enum
typedef enum{
Num1,
num2
}OCEnumType;

// Swift 中使用 OC 枚举
// 1. 将OC头文件导入桥接文件 xxx-Bridging-Header.h
#import "xxx.h"
// 2. 使用
let ocEnum1 = ENUM_OC_TYPE.Value1.rawValue;
let ocEnum2 = OCEnumType.init(rawValue: 0)
print("\(ocEnum1)\n\(ocEnum2.rawValue)")
  • Post title:Swift学习40:枚举
  • Post author:张建
  • Create time:2023-03-05 17:04:28
  • Post link:https://redefine.ohevan.com/2023/03/05/Swift课程/Swift学习40:枚举/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.