Swift笔记6:枚举Enum

1,071 阅读8分钟

c语言中的枚举

定义一周7天的枚举值

    enum week {
        MON,
        TUE,
        WED,
        THU,
        FRI,
        SAT,
        SUN
    };

枚举是一种类型可以用来声明变量

    enum week {
        MON,
        TUE,
        WED,
        THU,
        FRI,
        SAT,
        SUN
    }a=MON,b=TUE;

可以这样

    enum {
        MON,
        TUE,
        WED,
        THU,
        FRI,
        SAT,
        SUN
    }a=MON,b=TUE;

或者这样

    enum week a = MON, b = TUE;

看一下枚举项的值是什么

    enum week a = MON, b = TUE,c=WED,d=THU,e=FRI,f=SAT,g=SUN;
    printf("%d,%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f,g);

默认情况下c语言的枚举成员是整形数值,第一个是0后面累加,我们也可以手动给枚举成员赋值

    enum week {
        MON=-10,
        TUE,
        WED=208,
        THU,
        FRI,
        SAT=208,
        SUN
    };

    enum week a = MON, b = TUE,c=WED,d=THU,e=FRI,f=SAT,g=SUN;
    printf("%d,%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f,g);

观察到枚举成员值可以为负值并且可以重复,但是从某一个确定值的枚举成员开始后面的依次递增,那么是不是枚举成员其实相当于定义了一组宏???每个成员其实是对应了一个字面量吗??

    enum week a = MON, b = TUE,c=WED,d=THU,e=FRI,f=SAT,g=SUN;
    if (c==f) {
        printf("eque\n");
    }

c语言的枚举成员值也可以直接当做整形值做运算

enum week {
    MON=-10,
    TUE,
    WED=208,
    THU,
    FRI,
    SAT=208,
    SUN
};

int main(int argc, const char * argv[]) {
    enum week a = MON;
    enum week b = TUE;
    int c = a+b;
    printf("c = %d\n",c);
    return 0;
}

Swift中的枚举

//写法1
enum week {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
//写法2
enum week1 {
    case MON,TUE,WED,THU,FRI,SAT,SUN
}
var a:week = week.MON
let b = week1.TUE

默认类型时枚举项打印出来是一个和枚举项名称相同的字符串 但是不能当成字符串来运算 默认类型的枚举也没有rawValue

Int类型有rawValue,是从0开始的整形,并且可以用来做计算,这个时候和c语言的枚举有些相似 但是rawValue的值不能重复

String类型有rawValue,默认rawValue的值是和枚举项名称相同的字符串,可以用来计算但是不能重复

通过汇编查看枚举值的存储过程,点击Debug->Debug workflow->always show disassembly

enum week {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
//声明变量或者常量
var a:week = week.MON
var b:week = week.TUE
var c:week = week.WED
var d:week = week.THU
var e:week = week.FRI
var f:week = week.SAT
var g:week = week.SUN

Int类型

enum Week:Int {
    case MON
    case TUE=5
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
//声明变量或者常量
var a:Week = Week.MON
var b:Week = Week.TUE
var c:Week = Week.WED
var d:Week = Week.THU
var e:Week = Week.FRI
var f:Week = Week.SAT
var g:Week = Week.SUN

String类型

enum Week:String {
    case MON
    case TUE="abc"
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
//声明变量或者常量
var a:Week = Week.MON
var b:Week = Week.TUE
var c:Week = Week.WED
var d:Week = Week.THU
var e:Week = Week.FRI
var f:Week = Week.SAT
var g:Week = Week.SUN

通过观察可以看到Swift枚举底层默认也是通过整形递增来存储的,下面我们通过swift中间语言SIL来查看其内部实现,查看Swift中间语言SIL,进入main.swift文件所在位置执行命令即可查看sil文件

swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil

如果目前打开sil文件的软件不是你想要的怎么修改呢?选中sil文件右键->显示简介->打开方式->选择一个你希望默认打开的软件->全部更改,以后默认就会使用此软件打开.sil文件

对于默认类型的枚举项没有初始化方法,从前面汇编分析可以看到每一个枚举项在其内部实现中都有一个整形值与其对应

enum Week {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
var a:Week = Week.WED

对于Int类型枚举rawValue值默认也是从0开始递增的,如果有一个地方修改了rawValue那么后面的递增

enum Week:Int {
    case MON
    case TUE
    case WED=100
    case THU
    case FRI
    case SAT
    case SUN
}
var a:Week = Week.WED

对于String类型的枚举,rawValue默认和枚举项名称对应

enum Week:String {
    case MON
    case TUE="ABCD"
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
var a:Week = Week.WED

可以看到数组初始化成功之后执行了一个_findStringSwitchCase方法,去swift源码查找

这些字符串都存储在mach-o文件的__cstring段,是一段连续地址

思考一下下面这段代码的打印结果是什么

enum Week:String {
    case MON = "MON1"
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
print(Week.init(rawValue: "MON"))
print(Week.init(rawValue: "MON1"))

对于字符串类型的枚举匹配的是其rawValue值,如果没有手动赋值过那么rawValue就是枚举项名称,如果手动赋值过就成了我们赋的值

关联值

一般情况下枚举值只能表达一个值,但是如果我们想要表达一个复杂的枚举值,例如形状

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

也可以将参数名称省略,但是不推荐这样写,可读性差

enum Shape {
    case circle(Double)
    case rectangle(Double,Double)
}

我们查看sil文件发现没有初始化方法了

我们可以初始关联值也可以更改关联值,这相当于重新分配一个值给变量

enum Shape {
    case circle(radious:Double)
    case rectangle(width:Int,height:Int)
}
var s:Shape = Shape.circle(radious: 10.0)
s = Shape.circle(radious: 20.0)
print(s)

模式匹配

enum Week:String{
    case MON
    case TUE
    case WED
    case THU
    case SAT
    case FRI
    case SUN
}
var currentDay:Week = .WED
switch currentDay {
case .MON:
    print(Week.MON.rawValue)
default:
    print("unknowDay")
}

关联值怎么匹配呢

enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
}
var rad:Double
var w:Double
var h:Double
var circle:Shape = .circle(radious: 10)
switch circle {
    case .circle(let theR):
        rad = theR
        break
    case .rectangle(let theW, let theH):
        w = theW
        h = theH
}

那么我能不能只匹配一个case呢,答案是可以的

enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
}
var circle:Shape = .circle(radious: 10)

if case let Shape.circle(radious) = circle{
    print("\(radious)")
}

假如我只关心当width=10.0时height是多少

enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
    case squarw(width:Double,height:Double)
}
var h:Double = 0.0

var s = Shape.squarw(width: 10.0,height:20.0)
switch s {
    case .circle(let theR):
        print("\(theR)")
    case .rectangle(10.0, let theH), .squarw(10.0, let theH):
        h = theH
    default:
        print("end")
}

print("\(h)")

查看sil文件

枚举的嵌套

我们可以在一个枚举中嵌套枚举

enum CombineDirect {
    enum BaseDirect {
        case up
        case down
        case left
        case right
    }
    case leftUp(combineElement1:BaseDirect,combineElement2:BaseDirect)
    case leftDown(combineElement1:BaseDirect,combineElement2:BaseDirect)
    case rightUp(combineElement1:BaseDirect,combineElement2:BaseDirect)
    case rightDown(combineElement1:BaseDirect,combineElement2:BaseDirect)
}
var combine = CombineDirect.leftUp(combineElement1: .left, combineElement2: .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 Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
//    var radious:Double //这是不被允许的
    static var height = 20 //类型属性是被定义在全局区的,不占用枚举属性大小
    var width:Double{//同理不占用枚举大小
        get{
            return 10.0
        }
    }
}

枚举中可以包含方法

enum Week {
    case MON
    case TUE
    case WEN
    case THU
    case FRI
    case SAT
    case SUN
    mutating func nextDay() {
        if self == .SUN {
            self = .MON
        }
    }
}

枚举大小

1、普通枚举的大小
enum NoMean {
    case a
}
print(MemoryLayout<NoMean>.stride)
print(MemoryLayout<NoMean>.size)

枚举项数量大于1时用来区分不同的状态才有意义,只有一个case时应该是编译器做了优化,size=0

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

从前面的分析的值每一个枚举项默认是用1个字节存储的,如果枚举项超过了UInt8的容量那么升级为UInt16,当枚举case只有一个时使用0x0标记

2、关联枚举的大小

关联枚举的大小取决于最大关联项大小

enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double)
}
print(MemoryLayout<Shape>.stride) //内存对齐到8的整数倍 16
print(MemoryLayout<Shape>.size) //实际大小包括Double类型占用8字节 case占用1字节

enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
}
print(MemoryLayout<Shape>.stride) //内存对齐到8的整数倍 24
print(MemoryLayout<Shape>.size) //两个Double类型共占用16字节 case占用1字节

enum Shape {
    enum A { //size = 0
        case a1
    }
    case circle(a1:A)
    case rectangle(a1:A,a2:A)
}
print(MemoryLayout<Shape>.stride)
print(MemoryLayout<Shape>.size) //Shape枚举的两个case占用一个字节

enum Shape {
    enum A { //size = 0
        case a1
        case a2
    }
    case circle(a1:A)
    case rectangle(a1:A,a2:A) //两个参数各占用一个字节,如果关联属性也是枚举那么编译器做了优化了,不需要+1
}
print(MemoryLayout<Shape>.stride)
print(MemoryLayout<Shape>.size) //Shape枚举的两个case占用一个字节

indirect

如果声明递归枚举会提示要标记indirect关键字

 indirect enum List<T> {
    case end
    case node(T,next:List<T>)
}
var node2 = List<Int>.node(11, next: List<Int>.end)
var node1 = List<Int>.node(10, next: node2)

print("end")

indirect关键字也可以放在需要变为引用类型的case前面

 enum List<T> {
    case end
    indirect case node(T,next:List<T>)
}
var node2 = List<Int>.node(11, next: List<Int>.end)
var node1 = List<Int>.node(10, next: node2)

print("end")

Swift和OC混编

OC中使用Swift中的枚举

在OC中使用swift中的枚举需要在枚举前标记@objc

@objc enum Week:Int {
    case Mon
    case Tue
}

这时在swift的.h文件中就已经有了定义好的枚举

我们就可以在OC的文件中直接访问Swift的枚举

可以看到标记@objc的swift枚举仅能使用Int类型

Swift中使用OC中的枚举

在OC中定义枚举

NS_ENUM(NSInteger,OCENUM){
    Value1,
    Value2
};

自动帮我们转化好了