美文网首页swift mvvm
Swift底层进阶--010:枚举

Swift底层进阶--010:枚举

作者: 帅驼驼 | 来源:发表于2021-01-09 17:30 被阅读0次
C语⾔枚举

先来回顾⼀下C语⾔的枚举写法:

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

⽐如表示⼀周 7天,⽤C语⾔的枚举写法应该是这样的:

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

第⼀个枚举成员默认值为0,后⾯枚举值依次类推。如果更改只需这样操作:

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

定义⼀个枚举变量week

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

同样是定义⼀个枚举变量week,省略枚举名称:

enum {
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
} week;
Swift中的枚举
枚举基础

⽐如表示⼀周 7天,⽤Swift的枚举写法应该是这样的:

enum Week {
    case MONDAY
    case TUEDAY
    case WEDDAY
    case THUDAY
    case FRIDAY
    case SATDAY
    case SUNDAY
}

上述代码也可以直接⼀个case,然后⽤逗号隔开:

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

Swift中可以创建String类型枚举。在Swift=左边的值叫枚举值,右边的叫rawValue原始值,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分配。未指定类型的枚举,使用rawValue属性,编译报错

未指定类型的枚举

Int类型枚举,枚举值FRI分配rawValue10MON依然是从0开始,后⾯枚举值依次类推。在FRI之后的枚举值从11开始,依次类推

enum Week: Int {
    case MON, TUE, WED, THU, FRI = 10, SAT, SUN
}

print("MON:\(Week.MON.rawValue),SAT:\(Week.SAT.rawValue)")

//输出以下内容:
//MON:0,SAT:11

String类型枚举,枚举值FRI分配rawValue打印Hello,其他未分配rawValue打印自身枚举值

enum Week: String {
    case MON, TUE, WED, THU, FRI = "Hello", SAT, SUN
}

print("MON:\(Week.MON.rawValue),FRI:\(Week.FRI.rawValue),SUN:\(Week.SUN.rawValue)")

//输出以下内容:
//MON:MON,FRI:Hello,SUN:SUN

通过SIL代码,分析String类型枚举,是如何打印rawValue

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

let w: Week = .MON

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

enum Week : String {
  case MON, TUE, WED, THU, FRI, SAT, SUN
  typealias RawValue = String
  init?(rawValue: String)
  var rawValue: String { get }
}

SIL代码的枚举声明除了case还多了一些东西:

  • 首先通过typealias取别名,在枚举Week里把String取名为RawValue
  • 生成可选的初始化方法init?(rawValue: String),也就是说初始化可以返回nil
  • 生成rawValue计算属性,所以在代码中访问rawValue属性,本质就是访问它的get方法
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.w : Swift.String                     // id: %2
  %3 = global_addr @main.w : Swift.String : $*String      // user: %8
  %4 = metatype $@thin Week.Type
  %5 = enum $Week, #Week.MON!enumelt              // user: %7
  // function_ref Week.rawValue.getter
  %6 = function_ref @main.Week.rawValue.getter : Swift.String : $@convention(method) (Week) -> @owned String // user: %7
  %7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
  store %7 to %3 : $*String                       // id: %8
  %9 = integer_literal $Builtin.Int32, 0          // user: %10
  %10 = struct $Int32 (%9 : $Builtin.Int32)       // user: %11
  return %10 : $Int32                             // id: %11
} // end sil function 'main'

main方法:

  • %5接收枚举值
  • %6获取Week.rawValue.getter方法地址
  • 通过apply调用getter方法%6,传入参数枚举值%5,将返回值赋值给%7
  • 将返回结果%7存储到%3
// Week.rawValue.getter
sil hidden @main.Week.rawValue.getter : Swift.String : $@convention(method) (Week) -> @owned String {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Week):
  debug_value %0 : $Week, let, name "self", argno 1 // id: %1
  switch_enum %0 : $Week, case #Week.MON!enumelt: bb1, case #Week.TUE!enumelt: bb2, case #Week.WED!enumelt: bb3, case #Week.THU!enumelt: bb4, case #Week.FRI!enumelt: bb5, case #Week.SAT!enumelt: bb6, case #Week.SUN!enumelt: bb7 // id: %2

bb1:                                              // Preds: bb0
  %3 = string_literal utf8 "MON"                  // user: %8
  %4 = integer_literal $Builtin.Word, 3           // user: %8
  %5 = integer_literal $Builtin.Int1, -1          // user: %8
  %6 = metatype $@thin String.Type                // user: %8
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %7 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
  %8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
  br bb8(%8 : $String)                            // id: %9

bb2:                                              // Preds: bb0
  %10 = string_literal utf8 "TUE"                 // user: %15
  %11 = integer_literal $Builtin.Word, 3          // user: %15
  %12 = integer_literal $Builtin.Int1, -1         // user: %15
  %13 = metatype $@thin String.Type               // user: %15
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %14 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %15
  %15 = apply %14(%10, %11, %12, %13) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %16
  br bb8(%15 : $String)                           // id: %16

bb3:                                              // Preds: bb0
  %17 = string_literal utf8 "WED"                 // user: %22
  %18 = integer_literal $Builtin.Word, 3          // user: %22
  %19 = integer_literal $Builtin.Int1, -1         // user: %22
  %20 = metatype $@thin String.Type               // user: %22
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %21 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %22
  %22 = apply %21(%17, %18, %19, %20) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %23
  br bb8(%22 : $String)                           // id: %23

bb4:                                              // Preds: bb0
  %24 = string_literal utf8 "THU"                 // user: %29
  %25 = integer_literal $Builtin.Word, 3          // user: %29
  %26 = integer_literal $Builtin.Int1, -1         // user: %29
  %27 = metatype $@thin String.Type               // user: %29
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %28 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %29
  %29 = apply %28(%24, %25, %26, %27) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %30
  br bb8(%29 : $String)                           // id: %30

bb5:                                              // Preds: bb0
  %31 = string_literal utf8 "FRI"                 // user: %36
  %32 = integer_literal $Builtin.Word, 3          // user: %36
  %33 = integer_literal $Builtin.Int1, -1         // user: %36
  %34 = metatype $@thin String.Type               // user: %36
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %35 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %36
  %36 = apply %35(%31, %32, %33, %34) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %37
  br bb8(%36 : $String)                           // id: %37

bb6:                                              // Preds: bb0
  %38 = string_literal utf8 "SAT"                 // user: %43
  %39 = integer_literal $Builtin.Word, 3          // user: %43
  %40 = integer_literal $Builtin.Int1, -1         // user: %43
  %41 = metatype $@thin String.Type               // user: %43
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %42 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %43
  %43 = apply %42(%38, %39, %40, %41) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %44
  br bb8(%43 : $String)                           // id: %44

bb7:                                              // Preds: bb0
  %45 = string_literal utf8 "SUN"                 // user: %50
  %46 = integer_literal $Builtin.Word, 3          // user: %50
  %47 = integer_literal $Builtin.Int1, -1         // user: %50
  %48 = metatype $@thin String.Type               // user: %50
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %49 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %50
  %50 = apply %49(%45, %46, %47, %48) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %51
  br bb8(%50 : $String)                           // id: %51

// %52                                            // user: %53
bb8(%52 : $String):                               // Preds: bb7 bb6 bb5 bb4 bb3 bb2 bb1
  return %52 : $String                            // id: %53
} // end sil function 'main.Week.rawValue.getter : Swift.String'

Week.rawValue.getter方法:

  • bb0,接收一个枚举值,self就是该枚举值,通过switch_enum匹配跳转到对应bb1bb7代码分支
  • bb1bb7,构建出对应枚举值的字符串,最终都调用bb8
  • bb8返回对应字符串

rawValue.getter返回的字符串在编译时期已经存储好,通过Mach-O文件查看它们的存储位置

Mach-O
rawValue的字符串存储在__TEXT.__cstring段,而且内存地址是连续的。在上述SIL代码中构建枚举值字符串,本质就是从Mach-O里把对应地址的字符串取出来
区分case和rawValue
enum Week: String {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

print("case枚举值:\(Week.MON)")
print("rawValue原始值:\(Week.MON.rawValue)")

//输出以下内容:
//case枚举值:MON
//rawValue原始值:MON

上述代码中,case枚举值Week.MONrawValue原始值Week.MON.rawValue打印出的内容完全一致,但一个是输出枚举值,一个是访问rawValueget方法,它们是完全不同的东西

编译报错 做一个简单的测试,如果将String类型的Week.MON.rawValue赋值给Week类型week1,再将Week类型Week.MON赋值给String类型week2,都会编译报错
init方法的调用时机

添加符号断点Week.init,预期断住枚举初始化方法

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

let week: Week = Week.MON

print("case枚举值:\(week)")
print("rawValue原始值:\(week.rawValue)")

//输出以下内容:
//case枚举值:MON
//rawValue原始值:MON

运行上述代码,没有达到预期,没有任何断点被触发

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

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

//输出以下内容:
//Optional(LGSwiftTest.Week.MON)
//Optional(LGSwiftTest.Week.TUE)
//nil

修改上述代码,使用Week(rawValue:)Week.init(rawValue:)成功触发断点

Week.init

继续执行代码,从运行结果来看,前两个打印Optional可选值,第三个打印nil,因为找不到对应Hello的枚举值

运行结果

通过SIL代码,分析枚举的初始化构造函数

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

print(Week(rawValue: "Hello"))

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

来到Week.init(rawValue:)方法

init

来到bb1方法

bb1

bb1bb14,将所有字符串存储到数组内,进行case匹配

bb14

通过源码查看_findStringSwitchCase方法,两个入参分别是数组和需要匹配的字符串,然后遍历数组,如果匹配成功返回对应index,如果匹配失败返回-1

_findStringSwitchCase

bb15匹配成功,直接跳转bb29bb16匹配失败,继续后续的匹配

bb15、bb16

bb28全部匹配失败,构建一个.none类型的Optional返回,就是nilbb29匹配成功,构建一个.some类型的Optional返回,就是对应的枚举值

bb28、bb29
关联值枚举

如果想⽤枚举表达更复杂的信息,⽽不仅仅是⼀个rawValue这么简单,这个时候可以使⽤关联值

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

关联值枚举,没有rawValue属性。因为关联值枚举可以使用单个值或一组值来表示,但rawValue只能针对单个值

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

关联值枚举 只有case和关联值,没有typealias取别名,没有init方法,没有rawValue计算属性

关联值枚举可以省略关联值的标签,例如radiouswidthheight,但并不推荐这种书写方式,因为可读性太差

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

关联值枚举的使用

//创建
var circle = Shape.circle(radious: 10.0)
//重新分配
circle=Shape.circle(radious: 20.0)
模式匹配

使⽤switch匹配enum的时候,必须列举当前所有可能的情况,否则编译报错

编译报错

匹配enum可以列举出所有情况,也可以使用default表示默认情况

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

var week: Week?

switch week {
    case .MON:
        print(Week.MON.rawValue)
    default:
        print("unknow day")
}

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

SIL
匹配关联值枚举
  • 方式一:通过switch匹配所有case
enum Shape {
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

var shape = Shape.circle(radious: 10.0)

switch shape {
    case let .circle(radious):
        print("circle-radious:\(radious)")
    case let .rectangle(width, height):
        print("rectangle-width:\(width),height:\(height)")
}

//输出以下内容:
//circle-radious:10.0

case let .circle(radious)相当于做了value-binding,如果case匹配上,相当于把10.0赋值给常量radious

另一种写法:将关联值的参数使用letvar修饰

switch shape {
    case .circle(var radious):
        print("circle-radious:\(radious)")
    case .rectangle(let width, let height):
        print("rectangle-width:\(width),height:\(height)")
}

通过SIL代码,查看关联值枚举的匹配模式

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

var shape = Shape.circle(radious: 10.0)

var temR: Double
var w: Int
var h: Int

switch shape {
    case .circle(let radious):
        temR=radious
    case .rectangle(let width, let height):
        w=width
        h=height
}

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

main
bb1
  • 方式二:通过单个case进行匹配
enum Shape {
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

var shape = Shape.circle(radious: 10.0)

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

//输出以下内容:
//circle-radious:10.0
匹配不同枚举值的相同关联值
enum Shape {
    case circle(radious: Double, diameter: Double)
    case rectangle(width: Double, height: Double)
    case square(width: Double, width: Double)
}

var shape = Shape.circle(radious: 10.0, diameter: 20.0)

switch shape {
    case let .circle(x, 20.0), let .square(x, 20.0):
        print("x:\(x)")
    default:
        print("default")
}

//输出以下内容:
//x:10.0

上述代码,将多个枚举值中,我们想要匹配的关联值用x代替。如果枚举值为circlesquare,且第二个关联值为20.0,即为匹配成功

通过SIL代码,查看不同枚举值的相同关联值是如何匹配的

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

var shape = Shape.circle(radious: 10.0, diameter: 20.0)

var tmpR: Double

switch shape {
    case let .circle(x, 20.0), let .square(x, 20.0):
        tmpR=x
    default:
        print("default")
}

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

main bb1、bb2、bb3

不同枚举值里,用到匹配的变量或常量x,必须名称相同,不能一个用x一个用y,否则编译报错

编译报错

使用通配符_方式匹配

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

var shape = Shape.circle(radious: 10.0, diameter: 30.0)

switch shape {
    case let .circle(x, _), let .square(x, _):
        print("x:\(x)")
    default:
        print("default")
}

//输出以下内容:
//x:10.0

上述代码,我们不关心第二个关联值是什么,可以使用通配符_代替。如果枚举值为circlesquare,第二个关联为任意值,都能匹配成功

同样使用单个case进行匹配,也可以使用通配符_

var shape = Shape.circle(radious: 10.0, diameter: 20.0)

if case let Shape.circle(x, _) = shape{
    print("x:\(x)")
}

//输出以下内容:
//x:10.0
枚举嵌套
enum CombineDirect{
    enum BaseDirect{
        case up
        case down
        case left
        case 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)
}

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

上述代码,通过BaseDirect枚举的updownleftright四个case,组合出CombineDirect枚举中leftUprightUpleftDownrightDown四个case。这种方式下BaseDirect枚举相当于是私有的,外界无法直接访问

结构体中嵌套枚举
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")
        }
    }
}

let kill = Skill.init(key: .up)
kill.launchSkill()

//输出以下内容:
//up, down
枚举中包含属性

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

enum Shape {

    case circle(radius: Double)
    case rectangle(width: Double, height: Double)

    var width: Double{
        get{
            return 10.0
        }
    }

    static let height = 20.0
}

enum中包含存储属性,编译报错

编译报错
结构体可以包含存储属性,因为结构体的大小就是存储属性的大小。但enum大小取决于case的个数,只要case个数没有超过255enum的大小就是1字节
  • 计算属性本质是getset方法,对于值类型来说根本不用存储方法
  • 类型属性是全局变量,它的存储也和enum没有任何关系
枚举中包含⽅法

可以在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 week: Week = .SUN
week.nextDay()

print(week)

enumnextDay方法中修改自身,需要使用mutating关键字修饰

枚举的大小

rawValue枚举值大小

enum NoMean{
    case a
}

print("stride:\(MemoryLayout<NoMean>.stride)")
print("size:\(MemoryLayout<NoMean>.size)")

//输出以下内容:
//stride:1
//size:0

enum中只有一个case,大小为0,步长为1。当只有一个case的枚举,大小为0表示这个enum是没有意义的

enum NoMean{
    case a
    case b
}

print("stride:\(MemoryLayout<NoMean>.stride)")
print("size:\(MemoryLayout<NoMean>.size)")

//输出以下内容:
//stride:1
//size:1

enum中有两个case,大小为1,步长为1

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

print("stride:\(MemoryLayout<NoMean>.stride)")
print("size:\(MemoryLayout<NoMean>.size)")

//输出以下内容:
//stride:1
//size:1

enum中存在更多case,依然是大小为1,步长为1

将枚举值abc赋值给三个常量

赋值

通过断点查看汇编代码,分析枚举值abc

汇编代码 通过断点可以看出abc分别是0x00x10x2,对系统来说就是012。所以rawValue枚举值默认是UInt8类型,占1字节,最大可以存储255。超过255个枚举值,系统会将UInt8升级为UInt16UInt32UInt64

通过lldb查看内存

lldb 当前枚举的步⻓是1字节,也就意味着如果在内存中连续存储NoMean,需要跨越1字节的⻓度。1字节也就是8 位,最⼤可以表达的数字是255

关联值枚举的大小

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

print("stride:\(MemoryLayout<Shape>.stride)")
print("size:\(MemoryLayout<Shape>.size)")

//输出以下内容:
//stride:24
//size:17

关联值枚举⼤⼩,取决于最⼤关联值⼤⼩,并加上1字节枚举值大小。

  • circle有一个Double类型关联值,占8字节
  • rectangle有两个Double类型关联值,占16字节
  • enum的大小,就是最大关联值16字节,再加枚举值1字节,共占17字节
  • stride由于8字节对齐,所以自动补齐到24字节

通过lldb查看内存

lldb

枚举嵌套的大小

enum CombineDirect{
    enum BaseDirect{
        case up
        case down
        case left
        case 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("stride:\(MemoryLayout<CombineDirect>.stride)")
print("size:\(MemoryLayout<CombineDirect>.size)")

//输出以下内容:
//stride:2
//size:2

枚举嵌套和关联值枚举一样,同样取决于关联值大小

通过lldb查看内存

lldb
  • BaseDirect中的updownleftright对应的枚举值分别为0123
  • CombineDirect中的leftUprightUpleftDownrightDown对应的枚举值分别为04812。这里并没有规律可寻,如果加入更多case,也会变成0123...向后递增,通过源码分析目前还未找到相关定义
  • 图中输出的02left的枚举值,81要拆开来看,8leftDown的枚举值,1down的枚举值
  • enum大小占2字节,因为leftDown的枚举值和down的枚举值存储在同一字节内,属于系统优化

结构体中嵌套枚举的大小

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

    let key: KeyType
}

print("stride:\(MemoryLayout<Skill>.stride)")
print("size:\(MemoryLayout<Skill>.size)")

//输出以下内容:
//stride:1
//size:1

结构体中有一个KeyType枚举类型的成员变量key,所以结构体大小为1,步长为1

indirect关键字

如果想要表达的enum是⼀个复杂的关键数据结构,可以通过indrect关键字让当前的enum更简洁

创建链表结构enum,对应当前递归枚举来说,不添加indirect关键字,编译报错

编译报错

因为enum是值类型,它会在编译时期确定大小。但对于接收泛型Tenum,编译时期无法确定enum大小,系统无法分配空间

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

上述代码,在case node前面添加indirect关键字,可以编译通过

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

另一种方式,可以在enum List<T>前面添加indirect关键字,同样可以编译通过

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

print("List<Int> stride:\(MemoryLayout<List<Int>>.stride)")
print("List<Int> size:\(MemoryLayout<List<Int>>.size)")

print("List<String> stride:\(MemoryLayout<List<String>>.stride)")
print("List<String> size:\(MemoryLayout<List<String>>.size)")

上述代码,分别将IntString传入enum,打印出来的大小都是8字节,下面来分析一下原因

通过lldb查看内存
打印case end,存储的是end枚举值

end

打印case node,存储的是堆区地址

node

indirect关键字本质就是通知编译器,当前enum是递归枚举,无法确定大小,需要在堆区空间分配内存,并存储enum

通过SIL代码,查看indirect关键字,如何分配堆区内存空间

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

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

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

main

通过断点查看汇编代码,确实执行了swift_allocObject

lldb
枚举-Swift和OC混编
OC调用Swift的枚举

OC只能调用SwiftInt类型枚举

@objc enum NoMean: Int{
    case a
    case b
    case c
    case d
}

通过@objc声明后,桥接文件中自动生成SWIFT_ENUM

typedef SWIFT_ENUM(NSInteger, NoMean, closed) {
  NoMeanA = 0,
  NoMeanB = 1,
  NoMeanC = 2,
  NoMeanD = 3,
};

OCLGTest.m文件中可以直接调用

@implementation LGTest

- (void)test{
    NoMean a = NoMeanA;
}

@end

如果enum不声明类型,同时使用@objc修饰,编译报错

不声明类型

如果enum声明String类型,同时使用@objc修饰,编译报错

声明String类型
Swift调用OC的枚举

OCLGTest.h中,使用typedef NS_ENUM声明枚举

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

在桥接文件中,自动生成enum CEnum

public enum CEnum : Int {
    case invalid = 0
    case A = 1
    case B = 2
    case C = 3
}

OCLGTest.h中,使用typedef enum声明枚举

typedef enum{
    Num1,
    Num2
} OCNum;

在桥接文件中,自动生成struct OCNum,变成了结构体,并遵循了 EquatableRawRepresentable协议

public struct OCNum : Equatable, RawRepresentable {
    public init(_ rawValue: UInt32)
    public init(rawValue: UInt32)
    public var rawValue: UInt32
}

Swiftmain.swift文件中可以直接调用

let a: CEnum = .A
let b: OCNum = OCNum.init(rawValue: 1)

print("CEnum:\(a.rawValue)")
print("OCNum:\(b.rawValue)")

//输出以下内容:
//CEnum:1
//OCNum:1
内存对齐 & 字节对齐
  • 内存对齐:iOS采用8字节对齐方式,只会在对象初始化分配内存时出现。例如malloccalloc
  • 字节对齐:第一个数据成员放在offset0的位置,以后每个数据成员存储的起始位置要从该成员大小或该成员的子成员大小(只要该成员有子成员,比如数组、结构体等)的整数倍开始。比如Int8字节,则要从8的整数倍地址开始存储
enum Shape {
    case circle(radius: Double)
    case rectangle(width: Int8, height: Int, w: Int16, h: Int32)
}

print("stride:\(MemoryLayout<Shape>.stride)")
print("size:\(MemoryLayout<Shape>.size)")

//输出以下内容:
//stride:24
//size:24
  • widthInt8类型,占1字节
  • heightInt类型,占8字节
  • wInt16类型,占2字节
  • hInt32类型,占4字节
  • width1字节,但第二成员height8字节。按字节对齐规则,height起始位置必须是自身的整数倍,所以width要补齐到8字节。而w2字节,但h4字节,所以同理w要补齐到4字节。最终size大小:8 + 8 + 4 + 4 = 24字节

相关文章

  • Swift底层进阶--010:枚举

    C语⾔枚举 先来回顾⼀下C语⾔的枚举写法: ⽐如表示⼀周 7天,⽤C语⾔的枚举写法应该是这样的: 第⼀个枚举成员默...

  • Swift 枚举(enum)详解

    Swift 枚举(enum)详解 [TOC] 本文将介绍Swift中枚举的一些用法和其底层原理的一些探索,以及探索...

  • Swift枚举底层研究

    本文我们来探究Swift枚举类型(Enum)的底层实现逻辑。如果不想看分析过程,可以直接看最后的总结。如果对文中的...

  • Swift 枚举底层探究

    反汇编: Debug -- Debug Workflow -- Always Show Disassembly 内...

  • swift 进阶:枚举enum

    swift 进阶之路:学习大纲[https://www.jianshu.com/p/115367c3eefd] 本...

  • Swift 基础笔记 - 枚举

    枚举 OC定义和使用枚举 Swift定义枚举类型 Swift判断枚举类型 枚举成员类型

  • Swift中enum的总结

    前言 和switch语句类似,Swift中的枚举乍看之下更像是C语言中枚举的进阶版本,即允许你定义一种类型,用于表...

  • Swift进阶之RxSwift(四)

    前言 Swift进阶之RxSwift(一) Swift进阶之RxSwift(二) Swift进阶之RxSwift(...

  • Swift与OC的语法简单对比(常用语法二)

    20- 枚举,枚举原始值,枚举相关值,switch提取枚举关联值 Swift枚举: Swift中的枚举比OC中的枚...

  • Swift-进阶:Mirror源码解析

    本文主要是分析Mirror的底层实现,以及根据Mirror底层原理仿写其结构的实现 在Swift-进阶:反射Mir...

网友评论

    本文标题:Swift底层进阶--010:枚举

    本文链接:https://www.haomeiwen.com/subject/pafnoktx.html