Swift - 3 枚举 及底层原理

1:枚举

        enum Direction {
            case north
            case south
            case east
            case west
        }
        enum DirectionNew {
            case north, south, east, west
        }
        
        var dir = Direction.west
        dir = Direction.east
        dir = .north
        print(dir) // north
        
        switch dir {
        case .north:
            print("north")
        case .south:
            print("south")
        case .east:
            print("east")
        case .west:
            print("west")
        }

关联值

有事会将枚举的成员值跟其他类型的关联存储在一起,会非常有用。下面必要时let也可以改为var

关联值是直接存在枚举变量的内存里面的,这点要牢记,对于一个有固定取值范围的变量,设计成枚举比较合适

enum Score {
            case points(Int)
            case grade(Character)
        }
        
        var score = Score.points(96)
        score = .grade("A")
        
        switch score {
        case let .points(i):
            print(i, "points")
        case let .grade(i):
            print("grade", i)
        } // grade A
        
        enum Date {
            case digit(year: Int, mouth: Int, day: Int)
            case string(String)
        }
        var date = Date.digit(year: 2011, mouth: 9, day: 10)
        date = .string("2011-9-10")
        switch date {
        case .digit(year: let year, mouth: let mouth, day: let day):
            print(year, mouth, day)
        case let .string(value):
            print(value)
        }

关联值举例

原始值:Raw Values

枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做原始值

隐式原始值

如果枚举的原始值类型是Int、String。Swift会自动分配原始值。 有赋值的取值的话先取赋值的字段

enum Direction : String {
    case north = "north"
    case south = "south"
    case east = "east"
    case west = "west"
}
enum DirectionNew : String {
    case north, south, east, west
}
print(DirectionNew.north) // north
print(DirectionNew.north.rawValue) // north

enum Season: Int {
    case sping, summer, autumn, winter
}
print(Season.sping.rawValue)  // 0
print(Season.summer.rawValue)  // 1
print(Season.autumn.rawValue)  // 2
print(Season.winter.rawValue)  // 3

enum SeasonNew: Int {
    case sping = 1, summer, autumn = 4, winter
}
print(SeasonNew.sping.rawValue)  // 1
print(SeasonNew.summer.rawValue) // 2
print(SeasonNew.autumn.rawValue) // 4
print(SeasonNew.winter.rawValue) // 5

枚举递归(Recursive Enumeration)

//书写方法一
indirect enum ArithExpr_1 {
    case number(Int)
    case sum(ArithExpr, ArithExpr)
    case difference(ArithExpr, ArithExpr)
}

//书写方法二
enum ArithExpr {
    case number(Int)
    indirect case sum(ArithExpr, ArithExpr)
    indirect case difference(ArithExpr, ArithExpr)
}

let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let difference = ArithExpr.difference(sum, two)


func calculate(_ expr: ArithExpr) -> Int{
    switch expr {
    case let .number(value):
        return value
    case let .sum(left, right):
        return calculate(left) + calculate(right)
    case let .difference(left, right):
        return calculate(left) - calculate(right)
    }
}

可以使用MemoryLayout获取数据类型占用的内存大小, 相当于C里面使用的sizeof

enum Password2 {
    case number(Int, Int, Int, Int)
    case other
}
MemoryLayout<Password2>.stride //系统分配给变量的内存大小--40
MemoryLayout<Password2>.size //实际被使用的内存大小--33
MemoryLayout<Password2>.alignment //对其参数--8

var pd = Password2.number(9, 8, 7, 6)
pd = .other
print(pd) //"other/n"
MemoryLayout.stride(ofValue: pd)  //40
MemoryLayout.size(ofValue: pd)  //33
MemoryLayout.alignment(ofValue: pd)  //8

mutating

结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改 在func关键字前加mutating可以运行这种修改行为

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(delayX: Double, delayY: Double) {
        x += delayX
        y += delayY
        // self = Point(x: x + delayX, y: y + delayY)
    }
}

enum StateSwitch {
    case low, middle, high
    mutating func next() {
        switch self {
        case .low:
            self = .middle
        case .middle:
            self = .high
        case .high:
            self = .low
        }
    }
}

枚举在内存中是如何存储的?下面这转载自

通过MemoryLayout,我们只能简单查看一些内存相关的信息,但还不足以看清枚举在内存中的具体细节,由于Xcode调试工具无法为我们提供枚举变量的内存地址,因此需要借助一些额外的工具,这里推介一下大牛李明杰的一个工具

(1)首先来看下一种简单的情况~~~~~~~

enum TestEnum {
    case test1, test2, test3
}
print("系统实际分配内存",MemoryLayout<TestEnum>.stride)
print("实际使用的内存",MemoryLayout<TestEnum>.size)
print("内存对齐参数",MemoryLayout<TestEnum>.alignment)

var t = TestEnum.test1
print("枚举变量t的内存地址:",Mems.ptr(ofVal: &t)) //这里可以输出变量t的内存地址
t = .test2
t = .test3
print("Stop for debug")

Mems.ptr(ofVal: &t)可以帮我们获得变量t的内存地址,准备好3个断点

然后将程序运行值断点1处,此时我们已经获得t的内存地址,根据该地址,调出内存界面,我们来观察一下此时的内存细节

在继续走到断点2、断点3处,对比一下各自的内存情况如下

在这里插入图片描述

小结:enum TestEnum { case test1, test2, test3 }

  • 系统为TestEnum类型的变量分配1个字节的内存空间
  • test1 、 test2、 test3 三个case对应在内存中用整数0、1、2来表示

(2)把场景调整为有Int型原始值的情形如下~~~~~~~

总结 单层枚举:不是带关联值的枚举

  • 枚举变量本身的就占一个字节,这个字节的初始值为0,也就是成员存储值,如果内部有值则按照+1顺序顺延
  • 枚举变量内部进行的赋值234等这些,是不存储在枚举内部。它可以是不占枚举内存空间的,可以是if的形式表达出来的

(4)带关联值的场景

总结 带关联值的枚举
1个字节存储成员值,存储成员值的前提是有多个case
在枚举变量的内存中先存放的是枚举的case关联值,也就是赋值的值,然后再存放的是成员存储值,这个值可以表示这个内存是第几个成员,上图中画红色的线的部分代表了0、1、2、3、4个成员存储值的顺序
Swift会根据所需内存空间最大的case为枚举变量分配内存,同时再多给出一个字节作为标志用于区分
枚举变量的内存总空间按内存对齐参数进行补齐(计算机常识)

5)一些极端场景

那如果有一个以上的case,是不是就会给成员case分配空间了?咱们试试看,如下

enum TestEnum {
    case aaa
    case test(Int)
    case ccc
    case test3(Int, Int)
    case test2(Int,Int, Int)
    case other
}
print("系统实际分配内存",MemoryLayout<TestEnum>.stride)
print("实际使用的内存",MemoryLayout<TestEnum>.size)
print("内存对齐参数",MemoryLayout<TestEnum>.alignment)

var t = TestEnum.aaa
print("t = .aaa的内存情况:              ",Mems.memStr(ofVal: &t))
t = TestEnum.test(10)
print("t = .test(10)的内存情况:         ",Mems.memStr(ofVal: &t))
t = TestEnum.ccc
print("t = .ccc的内存情况:              ",Mems.memStr(ofVal: &t))
t = TestEnum.test3(16, 32)
print("t = .test3(16, 32)的内存情况:    ",Mems.memStr(ofVal: &t))
t = TestEnum.test2(20, 20, 20)
print("t = .test2(20, 20, 20)的内存情况:",Mems.memStr(ofVal: &t))
t = TestEnum.other
print("t = .other的内存情况:            ",Mems.memStr(ofVal: &t))
print("Stop for debug")



*************************************运行结果
系统实际分配内存 32
实际使用的内存 25
内存对齐参数 8
t = .aaa的内存情况:               0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000003
t = .test(10)的内存情况:          0x000000000000000a 0x0000000000000000 0x0000000000000000 0x0000000000000000
t = .ccc的内存情况:               0x0000000000000001 0x0000000000000000 0x0000000000000000 0x0000000000000003
t = .test3(16, 32)的内存情况:     0x0000000000000010 0x0000000000000020 0x0000000000000000 0x0000000000000001
t = .test2(20, 20, 20)的内存情况: 0x0000000000000014 0x0000000000000014 0x0000000000000014 0x0000000000000002
t = .other的内存情况:             0x0000000000000002 0x0000000000000000 0x0000000000000000 0x0000000000000003
Stop for debug
Program ended with exit code: 0

从上面的调试,又挖掘了一点小细节:

  • 对于有关联值的成员case,它的case值会根据定义的顺序,默认从0开始+1累加,
  • 其余所有不带关联值的成员case,它们的case值相同,而且都等于最后一个可关联成员case 的值+1

关联值 VS 原始值rawValue

以上我们看清楚了简单枚举、关联值枚举、原始值枚举在内存中分别是如何存储的,可以看出,枚举的关联值和原始值又以下区别:

内存角度:关联值是直接存储在枚举变量内存里面的,而原始值则不是,因为原始值是通过xx.rawValue访问的,因此它的值完全不需要存储,可以在枚举定义完之后通过方法提供给外部。
使用角度:原始值必须在枚举定义的时候确定原始值类型,才能被使用 enum Direction : String/Int/... {...}。关联值则必须在枚举定义的时候,确定好case所对应的关联值类型
赋值:关联值只能在枚举case被赋值给变量的时候进行赋值,因为同一个case每次被赋值给变量,都需要设定一个关联值,因此也可以说关联值是可以改变的,如下

enum Score {
    case points(Int)
    case grade(Character)
}
var score = Score.points(96)
score = .grade("A")
score = .grade("B") -->相同的case,不同的关联值

而原始值,只能在枚举定义的时候进行赋值,不赋值则系统会给定相应的默认值,也就是只有一次机会可以赋值,定义完枚举之后,就没有办法可以更改原始值了,示例如下

enum Grade: String {
    case perfect = "A"
    case great
    case good = "C"
    case bad = "D"
}
print(Grade.perfect.rawValue) --> A
print(Grade.great.rawValue) --> 定义时无赋值,系统默认为case的名称 great
print(Grade.good.rawValue) --> C
print(Grade.bad.rawValue) -> D

值类型是不允许在内部修改其变量的值,但是可以设置设置初始值,刚开始,系统会自动为我们创建()的方法

转载自这里

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值