枚举的遍历
合成枚举 allCases
Swift4.2引入 protocol CaseIterable,它被用于合成简单枚举类型的allCases静态属性
代码如下
enum LTSeasonFour: String, CaseIterable {
case FIRST, SECOND, THIRD, FORTH
}
for c in LTSeasonFour.allCases{
print("***c=\(c)***")
}
所谓“简单枚举类型”,指的是不带关联值的枚举类型。
所以,上述LTSeasonFour枚举声明称enum LTSeasonFour: String, CaseIterable的话(指定rawType),编译器也是能够自动合成的
自实现CaseIterable
当无法自动合成的时候,可以自己实现CaseIterable。例如,带关联值的枚举:
enum LTMartialStatus: CaseIterable {
case single
case married(spouse: String)
static var allCases: [LTMartialStatus]{
return [.single, .married(spouse: "Totti")]
}
}
再比如:
某个case在某种情况下不可用,或者默认合成的实现不是你想要的时候,也可以自己实现
enum LTMartialStatus2: CaseIterable{
@available(*, unavailable)
case single
case married
static var allCases: [LTMartialStatus2]{
return [.married]
}
}
print(LTMartialStatus.allCases)
print(LTMartialStatus2.allCases)
总结:
CaseIterable 如同 Equatable 和 Hashable 一样,是泛型约束,而不是泛型类型
public protocol CaseIterable {
/// A type that can represent a collection of all values of this type.
associatedtype AllCases : Collection where Self.AllCases.Element == Self
/// A collection of all values of this type.
static var allCases: Self.AllCases { get }
}
它定义了一个关联类型AllCases作为allCases的返回值,然后约束这个类型是一个Collection,并且Element和这个枚举一致
关联值
如果希望用枚举表示复杂的含义,关联更多的信息,就需要使用关联值了(如上述:LTMartialStatus)
注意:具有关联值的枚举,就没有rawValue属性了,主要是因为一个case可以用一个或者多个值来表示,而rawValue只有单个的值
如下是对应的SIL文件
enum LTMartialStatus : CaseIterable {
case single
case married(spouse: String)
static var allCases: [LTMartialStatus] { get }
typealias AllCases = [LTMartialStatus]
}
enum LTMartialStatus2 : CaseIterable {
@available(*, unavailable)
case single
case married
static var allCases: [LTMartialStatus2] { get }
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: LTMartialStatus2, _ b: LTMartialStatus2) -> Bool
func hash(into hasher: inout Hasher)
typealias AllCases = [LTMartialStatus2]
var hashValue: Int { get }
}
通过查看SIL文件,发现enum中即没有别名,也没有init方法,计算属性rawValue也没有啦
枚举的其他用法
简单enum的模式匹配
枚举的匹配模式,其实就是匹配case的枚举值
具有关联值的enum模式匹配
关联值的模式匹配主要有两种
1、通过switch匹配所有case
let married = LTMartialStatus.married(spouse: "TOTTI")
switch married {
case let .married(spouse)://相当于将TOTTI赋值给了声明的spouse常量
print("***married***\(spouse)")
case .single:
print("***single***")
}
将关联值的参数使用let、var修饰
也可以case let .married(spouse): 这么写,做了Value-Binding,相当于将TOTTI赋值给了的spouse常量
2、通过if case 匹配单个case,如下所示
if case let LTMartialStatus.married(spouse) = married{
print("***married=\(spouse)**")
}
枚举嵌套枚举
enum LTDirect{
//枚举中嵌套枚举
enum LTBaseDirect{
case up
case down
case left
case right
}
//通过内部枚举组合的枚举值
case leftUp(baseDirect1:LTBaseDirect, baseDirect2:LTBaseDirect)
case leftDown(baseDirect1: LTBaseDirect, baseDirect2: LTBaseDirect)
case rightUp(baseDirect1:LTBaseDirect, baseDirect2:LTBaseDirect)
case rightDown(baseDirect1: LTBaseDirect, baseDirect2: LTBaseDirect)
}
//使用:
let leftUp = LTDirect.leftUp(baseDirect1: LTDirect.LTBaseDirect.left, baseDirect2: LTDirect.LTBaseDirect.up)
结构体嵌套枚举
struct LTSkill {
enum LTKeyType {
case up
case down
case left
case right
}
let key: LTKeyType
func launchSkill(){
switch key{
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
枚举中包含属性
enum中只能包含计算属性,类型属性,不能包含存储属性
enum LTShape{
case cricle(radius: Double)
case rectangle(width: Double, height: Double)
//var radius: Double
//Eunms must not contain stored properties 定义属性radius会报错,因为enum本身是值类型
//计算属性 - 本质是方法(get/set方法)
var with: Double{
get{
return10.0
}
}
//类型属性 - 是一个全局变量
static let height = 20.0
}
疑问:
struct也是值类型,为什么struct可以放存储属性,而enum不可以?
struct中可以包含存储属性是因为其大小就是存储属性的大小。而对enum来说就不一样,enum枚举的大小是取决于case的个数的,如果没有超过255,enum的大小就是1字节(8位)
枚举中包含方法
可以在enum中定义实例方法、static修饰的方法
enum LTSeasonFive: Int{
case FIRST, SECOND, THIRD, FORTH
mutating func nextSeason(){
if self == .FORTH{
self = LTSeasonFive(rawValue:0)!
}else{
self = LTSeasonFive(rawValue: self.rawValue+1)!
}
}
}
//调用
var s = LTSeasonFive.FIRST
s.nextSeason()
indirect关键字
如果要表达的enum是一个复杂的关键数据结构时,可以通过indirect关键字来让当前的enum更简洁
如下:用枚举表示链表结构
enum List
{ case end
//表示case是引用来存储
indirect case node(T, next: List
) //也可以将indirect放在enum前: indirect enum List
}
enum是值类型,也就是在编译时期就确定了,但是使用indirect这么写的话对于当前的enum的大小是不能确定的,从系统的角度来说,不知道需要给enum分配多大的空间;
var node = List
使用indirect关键字就是通知编译器,当前的enum是递归的,大小是不确定的,需要分配一块堆的内存空间,用来存放enum
如果是end,此时存储的是case值,而case为node时存储的是引用地址
枚举的大小
1、普通enum
2、关联值的enum
3、enum嵌套enum
4、struct嵌套enum
普通enum的大小
enum LTNormalEnum{
case one
}
print(MemoryLayout
print(MemoryLayout
enum LTNormalEnum1{
case one
case two
}
print(MemoryLayout
print(MemoryLayout
enum LTNormalEnum2{
case one
case two
case three
case four
case five
}
print(MemoryLayout
print(MemoryLayout
从结果来看,enum就是以1字节存在在内存中的
case是UInt8,即1字节(8位),最大可存储255
如果超过了255,会自动从UInt8->UInt16->UInt32->UInt64...
总结:
如果enum中有原始值,其大小取决于case的多少,如果没有超过UInt8即255,就是1字节存储
Int标识的其实就是RawValue的值
只有一个case的情况下,size是0,说明这个enum没有意义
当有两个以上case时,enum才有意义,如果未超过255,则case的步长时1字节,如果超过,则会自动扩大UInt8->UInt16->UInt32->UInt64...
2、具有关联值enum的大小分析
分析上面定义的LTShape枚举
print(MemoryLayout
print(MemoryLayout
从打印结果可知,enum中有关联值时,其内存大小取决于关联值的大小
enum有关联值时,关联值的大小取对应枚举关联值最大的,例如LTShape中circle中关联值大小是8,
而rectangle中关联大小是16,所以取16
enum的size = 最大关联值大小+case大小 = 16+1=17
而stride由于8字节对齐,所以自动补齐到24
总结:
具有关联值的enum大小,取决于最大case的内存大小
关联枚举的大小=最大case的内存大小+1 (case的大小)
size表示实际大小
stride表示对齐后的大小(内存空间中真实占用的大小)
3、enum嵌套enum的大小分析
分析上面定义的LTDirect枚举
print(MemoryLayout
print(MemoryLayout
4、结构体嵌套enum的大小分析
struct LTSkill {
enum LTKeyType {
case up
case down
case left
case right
}
let key: LTKeyType //1字节
func launchSkill(){
switch key{
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
//结构体中包含枚举变量
print(MemoryLayout
.size) //输出:1 print(MemoryLayout
.stride) //输出:1
struct LTSkill1 {
enum LTKeyType {
case up
case down
case left
case right
}
}
//结构体中不包含任何变量
print(MemoryLayout
.size) //输出:0 print(MemoryLayout
.stride) //输出:1 struct LTSkill2 {
enum LTKeyType {
case up
case down
case left
case right
}
var num: UInt8 //1字节
let key: LTKeyType //1字节
func launchSkill(){
switch key{
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
//结构体中包含UInt8类型变量
print(MemoryLayout
.size) //输出:2 print(MemoryLayout
.stride) //输出:2
struct LTSkill3 {
enum LTKeyType {
case up
case down
case left
case right
}
var sum: Int //8字节
let key: LTKeyType //1字节
func launchSkill(){
switch key{
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
//结构体中即包含枚举变量,也包含Int变量
print(MemoryLayout
.size) //输出:9 print(MemoryLayout
.stride) //输出:16
通过上面的分析可知:
如果结构体中没有其他属性,只有枚举变量,那么结构体的大小就是枚举的大小,即size=1
如果结构体中嵌套了结构体,但没有声明变量,此时的size是0,stride是1
如果结构体中还有其他属性,按照OC的结构体对齐三原则进行分析
总之结构体的大小取决于包含的属性的大小之和
内存对齐&字节对齐
1、内存对齐:iOS中是8字节对齐(在分配对象时apple采用的是16字节对齐)
2、字节对齐:存储属性的位置必须是偶地址,即OC内存对齐中的min(m,n),其中m表示存储的位置,n表示属性的大小,需要满足位置m整除n时,才能从该位置存放属性。
简单来说,就是必须在自身的倍数位置开始
3、外部调用对象时,对象时服从内存对齐
4、单纯从结构上说,结构内部服从最大字节对齐