枚举
作用 在 OC
相比,枚举只支持 整数型
;而在 Swift
中枚举有很多功能:
可以定义 成员类型
,支持 Int、Double、String等基础类型
,也有 默认枚举值
可以 定义构造函数
(initializers)来提供一个初始成员值,可以在原始的实现基础上扩展他们的功能
可以 关联枚举值
可以遵守协议(protocol)
来 提供标准化功能
。
可以 嵌套枚举
枚举的语法
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)
下面的 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、自定义类型
的枚举(关联值)的模式匹配。
注: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
为什么 struct
或 class
中可以放 存储属性
,而 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
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是不对外公开的,即是 私有
的。
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)
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
来表示该成员可递归。
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 在 Swift
中 enum
非常强大,而在 OC
中,enum
仅仅只是一个 整数型
。 因此,OC调用Swift枚举,必须具备2个条件:①、用@objc关键字标记enum
;②、当前enum应该是Int类型。
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)")