其他分享
首页 > 其他分享> > iOS开发-Swift进阶之枚举enum!

iOS开发-Swift进阶之枚举enum!

作者:互联网

swift进阶总汇

本文主要介绍enum的常见使用形式,以及枚举大小是如何计算的
iOS开发-Swift进阶之枚举enum!

补充:添加脚本自动生成SIL

iOS开发-Swift进阶之枚举enum!

swiftc -emit-sil ${SRCROOT}/06、EnumTest/main.swift | xcrun swift-demangle > ./main.sil && code main.sil

然后我们就可以通过脚本自动生成SIL并自动打开啦 ✿✿ヽ(°▽°)ノ✿✿

C中的枚举

在介绍swift中的枚举之前,首先我们来回顾下C中的枚举写法,如下所示

enum 枚举名{
    枚举值1,
    枚举值2,
    ......
};

<!--举例:表示一周7天-->
enum Week{
    MON, TUE, WED, THU, FRI, SAT, SUN
};

<!--更改C中枚举默认值-->
//如果没有设置枚举默认值,一般第一个枚举成员的默认值为整型0,后面依次递推
enum Week{
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
};

<!--C中定义一个枚举变量-->
//表明创建了一个枚举,并声明了一个枚举变量Week
enum Week{
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
}week;
//或者下面这种写法,省略枚举名称
enum{
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
}week;

Swift中的枚举

在swift中,枚举的创建方式如下所示,如果没有指定枚举值的类型,那么enum默认枚举值是整型

<!--1、写法一-->
enum Week{
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}

<!--2、写法二-->
//也可以直接一个case,然后使用逗号隔开
enum Week{
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

<!--定义一个枚举变量-->
var w: Week = .MON
/*
- =左边的值是枚举值,例如 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"
}
<!--String类型-->
enum Week: String{
    case MON, TUE, WED = "WED", THU, FRI, SAT, SUN
}

<!--Int类型-->
//MON是从0开始一次递推,而WED往后是从10开始一次递推
enum Week: Int{
    case MON, TUE, WED = 10, THU, FRI, SAT, SUN
}

枚举的访问

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

注:如果enum没有声明类型,是没有rawValue属性的

iOS开发-Swift进阶之枚举enum!

枚举的访问方式如下所示

enum Week: String{
    case MON, TUE, WED, THU, FRI, SAT, SUN
}
var w = Week.MON.rawValue
<!--访问-->
print(w)

<!--打印结果-->
MON

这里就有一个疑问,swift是如何做到打印 MON的?我们通过SIL文件分析

iOS开发-Swift进阶之枚举enum!

iOS开发-Swift进阶之枚举enum!

结论1:使用rawValue的本质是调用get方法

但是get方法中的String是从哪里来的呢?String存储在哪里?

结论2rawValueget方法中的分支构建的字符串,主要是从Mach-O文件对应地址取出的字符串,然后再返回给w

总结

区分 case枚举值 & rawValue原始值

请问下面这段代码的打印结果是什么?

//输出 case枚举值
print(Week.MON)
//输出 rawValue 
print(Week.MON.rawValue)

<!--打印结果-->
MON
MON

虽然这两个输出的值从结果来看是没有什么区别的,虽然输出的都是MON,但并不是同一个东西

如果我们像下面这种写法,编译器就会报错

iOS开发-Swift进阶之枚举enum!

枚举的init调用时机

主要是探索枚举的init会在什么时候调用

print(Week.MON.rawValue)

let w = Week.MON.rawValue

通过运行结果发现,都是不会走init方法的

print(Week.init(rawValue: "MON"))

运行结果如下

iOS开发-Swift进阶之枚举enum!

注:这个断点首先需要通过init前的一个断点 + Week.init符号断点+init符号断点,一起配合,才能断住

总结:enum中init方法的调用是通过枚举.init(rawValue:)或者枚举(rawValue:)触发的

我们再继续来分析init方法,来看下面这段代码的打印结果是什么?

print(Week.init(rawValue: "MON"))
print(Week.init(rawValue: "Hello"))

<!--打印结果-->
Optional(_6_EnumTest.Week.MON)
nil

从结果中可以看出,第一个输出的可选值,第二个输出的是nil,表示没有找到对应的case枚举值。为什么会出现这样的情况呢?

iOS开发-Swift进阶之枚举enum!

其中

- `struct_extract` 表示`取出当前的Int值`,Int类型在系统中也是结构体
- `cond_br` 表示比较的表达式,即分支条件跳转
    - 如果匹配成功,则构建一个`.some的Optional`返回
    - 如果匹配不成功,则继续匹配,知道最后还是没有匹配上,则构建一个`.none的Optional`返回
@_semantics("findStringSwitchCase")
public // COMPILER_INTRINSIC
// 接收一个数组 + 需要匹配的string
func _findStringSwitchCase( 
  cases: [StaticString],
  string: String) -> Int {
// 遍历之前创建的字符串数组,如果匹配则返回对应的index
  for (idx, s) in cases.enumerated() {
    if String(_builtinStringLiteral: s.utf8Start._rawValue,
              utf8CodeUnitCount: s._utf8CodeUnitCount,
              isASCII: s.isASCII._value) == string {
      return idx
    }
  }
  // 如果不匹配,则返回-1
  return -1
}

iOS开发-Swift进阶之枚举enum!

所以,这也是为什么一个打印可选值,一个打印nil的原因

枚举的遍历

CaseIterable协议通常用于没有关联值的枚举,用来访问所有的枚举值,只需要对应的枚举遵守该协议即可,然后通过allCases获取所有枚举值,如下所示

<!--1、定义无关联值枚举,并遵守协议-->
enum Week: String{
    case MON, TUE, WED, THU, FRI, SAT, SUN
}
extension Week: CaseIterable{}

<!--2、通过for循环遍历-->
var allCase = Week.allCases
for c in allCase{
    print(c)
}

<!--3、通过函数式编程遍历-->
let allCase = Week.allCases.map({"\($0)"}).joined(separator: ", ")
print(allCase)
//******打印结果******
MON, TUE, WED, THU, FRI, SAT, SUN

关联值

如果希望用枚举表示复杂的含义,关联更多的信息,就需要使用关联值了

例如,使用enum表达一个形状,其中有圆形、长方形等,圆形有半径,长方形有宽、高,我们可以通过下面具有关联值的enum来表示

//注:当使用了关联值后,就没有RawValue了,主要是因为case可以用一组值来表示,而rawValue是单个的值
enum Shape{
    //case枚举值后括号内的就是关联值,例如 radius
    case circle(radius: Double)
    case rectangle(width: Int, height: Int)
}

注:具有关联值的枚举,就没有rawValue属性了,主要是因为一个case可以用一个或者多个值来表示,而rawValue只有单个的值

这一点我们也可以通过SIL文件 来验证

enum Shape{
    //case枚举值后括号内的就是关联值,例如 radius
    case circle(Double)
    case rectangle(Int, Int)
}

那么如何创建一个有关联值的枚举值呢?可以直接在使用时给定值来创建一个关联的枚举值

<!--创建-->
var circle = Shape.circle(radius: 10.0)

<!--重新分配-->
circle = Shape.rectangle(width: 10, height: 10)

枚举的其他用法

模式匹配

enum中的模式匹配其实就是匹配case枚举值

简单enum的模式匹配

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

enum Week: String{
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}

var current: Week?
switch current {
    case .MON:print(Week.MON.rawValue)
    case .TUE:print(Week.MON.rawValue)
    case .WED:print(Week.MON.rawValue)
    default:print("unknow day")
}

<!--打印结果-->
unknow day

查看其SIL文件,其内部是将nil放入current全局变量,然后匹配case,做对应的代码跳转

iOS开发-Swift进阶之枚举enum!

具有关联值enum的模式匹配

关联值的模式匹配主要有两种:

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

let shape = Shape.circle(radius: 10.0)
switch shape{
    //相当于将10.0赋值给了声明的radius常量
    case let .circle(radius):
        print("circle radius: \(radius)")
    case let .rectangle(width, height):
        print("rectangle width: \(width) height: \(height)")
}

<!--打印结果-->
circle radius: 10.0

也可以这么写,将关联值的参数使用let、var修饰

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

let shape = Shape.circle(radius: 10)
switch shape{
    //做了Value-Binding,相当于将10.0赋值给了声明的radius常量
    case .circle(let radius):
        print("circle radius: \(radius)")
    case .rectangle(let width, var height):
        height += 1
        print("rectangle width: \(width) height: \(height)")
}

<!--打印结果-->
circle radius: 10.0

然后查看SIL中的关联值的模式匹配,如下图所示

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

let circle = Shape.circle(radius: 10)

<!--匹配单个case-->
if case let Shape.circle(radius) = circle {
    print("circle radius: \(radius)")
}
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
}

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

enum Shape{
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
    case square(width: Double, height: Double)
}
let shape = Shape.rectangle(width: 10, height:20)
switch shape{
case let .rectangle(_, x), let .square(_, x):
    print("x = \(x)")
default:
    break
}

<!--另一种方式-->
enum Shape{
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
    case square(width: Double, height: Double)
}
let shape = Shape.rectangle(width: 10, height:20)
switch shape{
case let .rectangle(x, _), let .square(_, x):
    print("x = \(x)")
default:
    break
}

注:

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

枚举的嵌套

枚举的嵌套主要用于以下场景:

枚举嵌套枚举

例如,以吃鸡游戏中的方向键为例,有上下左右四个方向键,不同的组合会沿着不同的方向前进

enum CombineDirect{
    //枚举中嵌套的枚举
    enum BaseDirect{
        case up
        case down
        case left
        case 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 leftUp = CombineDirect.leftUp(baseDIrect1: CombineDirect.BaseDirect.left, baseDirect2: CombineDirect.BaseDirect.up)

结构体嵌套枚举

//结构体嵌套枚举
struct Skill {
    enum KeyType{
        case up
        case down
        case left
        case right
    }

    let key: KeyType

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

枚举中包含属性

enum中只能包含计算属性、类型属性,不能包含存储属性

enum Shape{
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)

    //编译器报错:Enums must not contain stored properties 不能包含存储属性,因为enum本身是值类型
//    var radius: Double

    //计算属性 - 本质是方法(get、set方法)
    var with: Double{
        get{
            return 10.0
        }
    }
    //类型属性 - 是一个全局变量
    static let height = 20.0
}

为什么struct中可以放存储属性,而enum不可以?

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

枚举中包含方法

可以在enum中定义实例方法、static修饰的方法

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)

indirect关键字

如果我们想要表达的enum是一个复杂的关键数据结构时,可以通过indirect关键字来让当前的enum更简洁

//用枚举表示链表结构
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>)
}

为什么呢?

You indicate that an enumeration case is recursive by writing indi rect before it, which tells the compiler to insert the necessary l ayer of indirection.
enum List<T>{
    case end
    indirect case node(T, next: List<T>)
}
print(MemoryLayout<List<Int>>.size)
print(MemoryLayout<List<Int>>.stride)

<!--打印结果-->
8 //size大小是8
8 //stride大小是8

如果传入的类型是String呢?

iOS开发-Swift进阶之枚举enum!

从结果发现,换成其他类型,其结果依旧是8,这是为什么呢?

下面来分析其内存结构,首先需要定义一个全局变量

enum List<T>{
    case end
    indirect case node(T, next: List<T>)
}

var node = List<Int>.node(10, next: List<Int>.end)

print(MemoryLayout.size(ofValue: node))
print(MemoryLayout.stride(ofValue: node))

通过lldb分析其内存

iOS开发-Swift进阶之枚举enum!

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

swift和OC混编enum

在swift中,enum非常强大,可以添加方法、添加extension
而在OC中,enum仅仅只是一个整数值

如果想将swift中的enum暴露给OC使用:

OC调用Swift的enum

<!--swift中定义-->
@objc enum Week: Int{
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

<!--OC使用-->
- (void)test{
    Week mon = WeekMON;
}

Swift调用OC的enum
OC中的枚举会自动转换成swift中的enum

<!--OC定义-->
//会自动转换成swift的enum
NS_ENUM(NSInteger, OCENUM){
    Value1,
    Value2
};

<!--swift使用-->
//1、将OC头文件导入桥接文件
#import "CJLTest.h"
//2、使用
let ocEnum = OCENUM.Value1

如果OC中是使用typedef enum定义的,自动转换成swift就成了下面这样

typedef enum {
    Num1,
    Num2
}OCNum;

<!--swift中使用-->
let ocEnum = OCNum.init(0)
print(ocEnum)

//*******打印结果*******
OCNum(rawValue: 0)

自动转换成swift中的如下所示,通过typedef enum定义的enum,在swift中变成了一个结构体,并遵循了两个协议:EquatableRawRepresentable

iOS开发-Swift进阶之枚举enum!

如果在OC中使用typedef NS_ENUM定义枚举呢?

typedef NS_ENUM(NSInteger, CENUM){
    CEnumInvalid = 0,
    CEnumA = 1,
    CEnumB,
    CEnumC
};

自动转换成swift后的结果如下

iOS开发-Swift进阶之枚举enum!

问题:OC如何访问swift中String类型的enum?

@objc enum Week: Int{
    case MON, TUE, WED

    var val: String?{
        switch self {
        case .MON:
            return "MON"
        case .TUE:
            return "TUE"
        case .WED:
            return "WED"
        default:
            return nil
        }
    }
}

<!--OC中使用-->
Week mon = WeekMON;

<!--swift中使用-->
let Week = Week.MON.val

枚举的大小

主要分析以下几种情况的大小:

1、普通enum大小分析

在前面提及enum中不能包含存储属性,其根本在于enum的大小与Struct的计算方式是不一样的,这里我们将展开详细的分析

enum NoMean{
    case a
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)

<!--打印结果-->
0 //size大小是0
1 //表示访问下一个NoMean的case时,需要跨越1字节的步长
enum NoMean{
    case a
    case b
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)

<!--打印结果-->
1 //size大小是1
1 //步长是1
enum NoMean{
    case a
    case b
    case c
    case d
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)

<!--打印结果-->
1
1

从结果来看,仍然是1,说明enum就是以1字节存储在内存中的,这是为什么呢?我们来分析下

断点分析

LLDB分析

enum NoMean{
    case a
    case b
    case c
    case d
}

var tmp = NoMean.a
var tmp1 = NoMean.b
var tmp2 = NoMean.c
var tmp3 = NoMean.d

通过lldb查看内存情况如下,case都是1字节大小

iOS开发-Swift进阶之枚举enum!

普通enum总结

2、具有关联值enum的大小分析

如果enum中有关联值,其大小又是多少呢?有如下代码,打印其size和stride

enum Shape{
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)

<!--打印结果-->
17 //size的大小是17
24 //stride的步长是24

说明从打印结果可以说明 enum中有关联值时,其内存大小取决于关联值的大小

总结

3、enum嵌套enum的大小分析

请问下面这段代码的打印结果是什么?

enum CombineDirect{
    enum BaseDirect{
        case up, down, left, right
    }

    case leftUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
    case rightUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
    case leftDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
    case rightDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
}

print(MemoryLayout<CombineDirect>.size)
print(MemoryLayout<CombineDirect>.stride)

<!--打印结果-->
2 //size大小,enum有关联值取决于关联值的大小,每个case都有2个大小为1的enum,所以为2
2 //stride大小

从结果中说明enum嵌套enum同具有关联值的enum是一样的,同样取决于关联值的大小,其内存大小是最大关联值的大小

通过嵌套枚举定义一个全局变量

var combine = CombineDirect.leftDown(baseDirect1: .left, baseDirect2: .down)

查看其内存情况如下

iOS开发-Swift进阶之枚举enum!

这里我们会有一个疑问,其中的81到底指的是什么?这里先提前剧透下:8表示 case leftDown的枚举值,1表示其中down的枚举值,下面我们来验证

在上面这个例子中,是有4个case,其case在内存中是用0、4、8、12体现的,如果是有很多个case,是否还满足我们现在这样的规律呢?

PS:至于为什么会是这样的结果,目前也没找到任何依据,后续如果有了依据,再来补充吧(有知道的童鞋,欢迎留言~)

总结

4、结构体嵌套enum的大小分析

请问下面这段代码的打印结果是什么?

struct Skill {
    enum KeyType{
        case up
        case down
        case left
        case right
    }

    let key: KeyType

    func launchSkill(){
        switch key {
        case .left, .right:
            print("left, right")
        case .up, .down:
            print("up, down")
        }
    }
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)

<!--打印结果-->
1
1
struct Skill {
    enum KeyType{
        case up
        case down
        case left
        case right
    }
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)

<!--打印结果-->
0 //size的大小取决于成员变量,但是struct中目前没有属性
1
struct Skill {
    enum KeyType{
        case up
        case down
        case left
        case right
    }

    let key: KeyType //1字节

    var height: UInt8 //1字节

    func launchSkill(){
        switch key {
        case .left, .right:
            print("left, right")
        case .up, .down:
            print("up, down")
        }
    }
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)

<!--打印结果-->
2
2
struct Skill {
    enum KeyType{
        case up
        case down
        case left
        case right
    }

    var width: Int //8字节

    let key: KeyType //1字节

    var height: UInt8 //1字节

    func launchSkill(){
        switch key {
        case .left, .right:
            print("left, right")
        case .up, .down:
            print("up, down")
        }
    }
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)

<!--打印结果-->
10 //size大小(与OC中的结构体大小计算是一致的,min(m,n),其中m表示存储的位置,n表示属性的大小,要求是:m必须整除n)
16 //stride大小

结论

例如下面这个例子

struct Skill {
    var age: Int //8字节
    var height: UInt8 //1字节
    var width: UInt16 //2字节
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)

<!--打印结果-->
12
16

总结

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

标签:case,进阶,enum,iOS,枚举,MON,print,rawValue
来源: https://blog.51cto.com/u_15146321/2847296