Swift09 -枚举(enum)

374 阅读6分钟

Swift 进阶之路 文章汇总

前言:

Swift语言中,具有类特征的类型包括三种,即枚举类型结构类型(包括基本类型,基本类型实际都是结构类型的特例)、。其中枚举类型结构类型是属于值类型属于引用类型。三种类型都可以添加属性、方法、下标方法,能够使用扩展进行功能扩,使用协议等.下面就分享下Swift中的枚举类型.

C语⾔枚举的写法回顾

在将 Swift 的枚举之前,我们先来回顾⼀下 C 语⾔的枚举写法:

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

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

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

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

Swift 中枚举写法类⽐ 

接下来回到 Swift 枚举中,我们思考⼀个例⼦:如果说我们要定⼀个颜⾊的枚举 LGColor ,在

Swift 中应该怎么写

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

<!----------也可以这样写----------->

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

上述代码中我们的枚举值默认是整形,这个 C 是⼀致的,如果我们想要表达的 String 怎么办? 

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

<!-------隐士的书写方法------->
enum Weak: Int {  
    mon, tue, wed, thu, fri = 10, sat, sun  
} 
<!-------String类型---------->
enum Weak: String { 
    case mon, tue, wed, thu, fri = "Hello", sat, sun
} 
 print(Weak.mon.rawValue) 
 print(Weak.fri.rawValue) 

= 号左边的值在 Swift 中我们把他叫做 RawValue ,如果我们不想写后⾯的字符串,这个时候我们就可以使⽤ `隐⼠ RawValue 分配

枚举的遍历

Swift中的枚举能像集合一样进行遍历

enum Weak: String { 
     case mon, tue, wed, thu, fri = "Hello", sat, sun
}
extension Weak: CaseIterable{}
var allCase = Weak.allCases 
<!-------方式1:for循环------->
for c in allCase{ 
    print(c)
} 
<!-----打印结果------>
mon
tue
wed
thu
fri
sat
sun 

<!--方式2:通过函数式编程遍历-->
let allCase = Weak.allCases.map({"\($0)"}).joined(separator: ", ")
print(allCase)

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

mon, tue, wed, thu, fri, sat, sun

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

枚举关联值

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

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

enum Shape{
    //case枚举值后括号内的就是关联值,例如 radius
    case circle(radius: Double)
    case rectangle(width: Int, height: Int)
}
<!-----使用----->
<!--创建-->
var shape = Shape.circle(radius: 10.0)

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


 // 使用关联值
 switch shape {
    case .circle(let radius):
       print("面积:\(radius*radius)")
    case .rectangle(let with,let height)
       print("面积:\(with*height)")
}

 // 使用关联值
 switch shape {
    case let .circle(radius):
       print("面积:\(radius*radius)")
    case let .rectangle(with,height)
       print("面积:\(with*height)")
}

// 判断s是否是square类型。并获取`关联内容`
// 1. 内部声明关联内容类型(如: let)
if case .circle(let width) = s {
    print(width)
}

// 2. 声明case所有关联内容类型(如: var)
if case var .rectangle(radius, borderWidth) = c {
    radius += 200
    borderWidth += 100
    print(radius, borderWidth)
}

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

枚举的嵌套

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

  • 一个复杂枚举是由一个多个枚举组成(枚举嵌套枚举)

  • enum是不对外公开的,即是私有的(结构体嵌套枚举)

枚举嵌套枚举

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

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
}

枚举中包含方法

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

enum Weak: Int{
    case MON, TUE, WED, THU, FRI, SAT, SUN
    
    mutating func nextDay(){
        if self == .SUN{
            self = Weak(rawValue: 0)!
        }else{
            self = Weak(rawValue: self.rawValue+1)!
        }
    }
}

<!--使用-->
var w = Weak.MON
w.nextDay()
print(w)

枚举的SIL分析

sil分析

以DayOfWeak枚举为例,查看枚举编译和取值的过程

enum DayOfWeak:String {
    case monday ,tuesday ,wednesday = "Hello" ,thursday ,friday ,saturday ,sunday
}

print(DayOfWeak.monday.rawValue)
print (DayOfWeak.wednesday.rawValue)


<!-------打印结果----->
monday
Hello

进入SIR文件查看下枚举具体的编译

输出当前 case 的 RawValue 发⽣了什么?

查看getter方法

使用rawValue的本质是调用get方法,但是get方法中的String是从哪里来的呢?String存储在哪里?其实这些对应分支的字符串在编译时期就已经存储好了,即存放在Maach-O文件的__TEXT.cstring中,且是连续的内存空间,可以通过编译后查看Mach-O文件来验证

是从Mach-O文件对应地址取出的字符串,然后再返回给w

枚举值和 RawValue 的区别

从以下案例分析枚举值和RawValue的区别

enum DayOfWeak:String {
    case monday ,tuesday ,wednesday = "Hello" ,thursday ,friday ,saturday ,sunday
}
print (DayOfWeak.monday)
print(DayOfWeak.monday.rawValue)

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

以上打印的都是“monday”,是不是意味着枚举值RawValue是同一个东西?并不是,第一个输出的输出的case枚举值,第二个输出的是通过rawValue访问的rawValueget方法

举个例子定义一个String类型的变量,并赋值

所以上⾯ print 出来的就是具体的枚举值,⽽通过 rawValue 访问的就是 rawValueget ⽅法

那如何访问enum的枚举值呢?

SIR文件分析

初始化返回Optional类型,DayOfWeak赋值调用%19

index_addr 其实就是去当前数组取第 n 个元素值的地址,然后在把构建好的字符串放到当前地址中。所以可以看到,当前我们指定 enum 的 RawValueTypeString 之后,系统⾃动创建了⼀块连续的内存空间默认存放当前 case 对应的字符串

继续分支跳转

以上就是枚举匹配的流程

补充:

_findStringSwitchCase

swift-source中查找_findStringSwitchCase方法,接收两个参数,分别是 数组 + 需要匹配的String

总结:

  • enum中使用rawValue的本质是调用get方法,即在get方法中从Mach-O对应地址中取出字符串并返回的操作

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

  • 如果希望获取所有枚举值,需要遵循CaseIterable协议,然后通过枚举名.allCase的方式获取

  • case枚举值和rawValue原始值的关系:case 枚举值 = rawValue原始值

未完待续....(下篇文章继续用sil方式分析枚举)