我正在参加「启航计划」
主要内容:
- 枚举的底层结构体认识
- rawValue的完成原理
- init的完成原理
- 形式匹配进程
- 内存巨细剖析
- Swift和OC混编
1. 枚举的底层结构体认识
1.1 原始值
代码:
/*
1、原始值
*/
enum Week: String{
case MON = "MON"
case TUE = "TUE"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case SAT = "SAT"
case SUN = "SUN"
}
SIL:
//只需原始值
//主动生成init?初始化办法,已经rawValue核算特点
enum Week : String {
case MON
case TUE
case WED
case THU
case FRI
case SAT
case SUN
//参数是rawValue
init?(rawValue: String)
//RawValue类型也便是String
typealias RawValue = String
//rawValue只读核算特点
var rawValue: String { get }
}
阐明:
1.2 相关值
代码:
SIL:
阐明:
- 当运用了相关值后,就没有RawValue了,主要是由于case能够用一组值来表明,而rawValue是单个的值
- 留意只需枚举中存在一个相关值,那么这个枚举就不存在rawValue了。(后边好好剖析一下)
2. rawValue的完成原理
代码:
/*
3、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"
}
let mon = Week.MON.rawValue
SIL的main办法:
阐明:
- 拿到枚举
- 获取到getter办法
- apply调用办法,而且传入枚举
- 赋值给全局变量
- 因而这里最重要的便是getter办法传入枚举获取到rawValue值
SIL的getter办法:
阐明:
- 经过传入的枚举经过匹配枚举项找到对应的分支
- 在不同的分支上构建对应的字符串
- 跳转到b8回来这个字符串,而这个字符串便是rawValue拿到的值
- 构建字符串本质便是到底层获取到字符串
在底层存储的字符串:
3. init的完成原理
3.1 调用机遇
代码:
/*
4、init办法
*/
enum Week: String{
case MON = "MON"
case TUE = "TUE"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case SAT = "SAT"
case SUN = "SUN"
}
print(Week.MON.rawValue)
let w = Week.MON.rawValue
Week.init(rawValue: "MON")
print("end")
界说一个符号断点
阐明:
- 能够看到只需经过Init办法进行调用时才会履行init办法
- 直接获取ravwValue,是不会履行init流程的
3.2 底层剖析
代码:
SIL的init办法:
阐明:
- 在init办法中是将一切enum的字符串从Mach-O文件中取出,顺次放入数组中
- 放完后,然后调用_findStringSwitchCase办法进行匹配
- 1、先创立一个数组
- 2、将第一个值存储到数组中
- 3、核算得到第二个值的地址,将第二个值存储到数组中
- 4、顺次履行,把一切元素放到数组中
_findStringSwitchCase: 经过init办法来创立枚举对象时,需求判别枚举中是否存在该元素
阐明:
- 1、遍历数组,假如匹配则回来对应的index
- 2、假如不匹配,则回来-1
匹配之后的判别:
阐明:
- 假如没有匹配成功,则构建一个.none类型的Optional,表明nil
- 假如匹配成功,则构建一个.some类型的Optional,表明有值
4. 形式匹配进程
4.1 原始值
代码:
/*
5、形式匹配,原始值
*/
/*
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")
}
SIL:
阐明:
- 其内部是将nil放入current全局变量,然后匹配case,做对应的代码跳转
4.2 相关值
代码:
/*
6、形式匹配,相关值
*/
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
let shape = Shape.circle(radius: 10)
switch shape{
case .circle(let radius):
print("circle radius: \(radius)")
case .rectangle(let width, var height):
height += 1
print("rectangle width: \(width) height: \(height)")
}
SIL:
阐明:
- 首要构建一个相关值的元组
- 根据当时case枚举值,匹配对应的case,并跳转
- 取出元组中的值,将其赋值给匹配case中的参数
5. 内存巨细剖析
5.1 原始值
代码:
/*
7、内存巨细
*/
//只需一个成员
enum NoMean1{
case a
}
print(MemoryLayout<NoMean1>.size)
print(MemoryLayout<NoMean1>.stride)
//多个成员
enum NoMean2{
case a
case b
}
print(MemoryLayout<NoMean2>.size)
print(MemoryLayout<NoMean2>.stride)
成果:
阐明:
- 一个成员,不占用内存
- 多个成员时,会占用一个字节巨细的内存
LLDB检查:
enumNoMean{
casea
caseb
casec
cased
}
vartmp=NoMean.a
vartmp1=NoMean.b
vartmp2=NoMean.c
vartmp3=NoMean.d
阐明:
- 在枚举内存中存储的是序号,用来符号枚举成员(从0开端,一共4个序号)
- case是UInt8,即1字节(8位),最大能够存储255
- 假如超过了255,会主动从UInt8 -> UInt16 -> UInt32 -> UInt64 …(再看下教案)
总结:
- 假如enum中有原始值,即rawValue,其巨细取决于case的多少,假如没有超过UInt8即255,则便是1字节存储case
- 只需一个成员时,此刻不需求运用序号符号,因而枚举内存没有存储任何值
- 假如有多个成员,就需求运用需求符号成员,枚举就会占用1个字节巨细的内存
5.2 相关值
代码:
/*
8、内存巨细:相关值
*/
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
成果:
LLDB:
阐明:
- num有相关值时,相关值的巨细 取 对应枚举相关值 最大的
- circle中相关值巨细是8,而rectangle中相关值巨细是16,所以取16
- 可是由于有多个成员,所以还需求一个字节巨细用来存储序号,所以便是17个
- 分配内存时遵从字节对齐准则,因而分配了24个字节的空间
5.3 递归枚举
运用关键字indirect就能够完成枚举的递归 ,能够润饰在enum前,也能够润饰在枚举内的成员前
5.3.1 运用:
具体的能够看另一篇博客
/*
9、递归枚举
*/
//用枚举表明链表结构
//放在case前
enum List1<T>{
case end
//表明case使是引用来存储
indirect case node(T, next: List1<T>)
}
//放在enum前
indirect enum List2<T>{
case end
case node(T, next: List2<T>)
}
5.3.2 检查巨细:
代码:
阐明:
- 不管是String类型仍是Int类型,此刻都是8个字节,而且占用内存巨细便是8个字节
- 此刻枚举包括两个成员,可是end是原始值,所以内存巨细取决于node的成员的巨细
5.3.3 LLDB检查内存结构
1、先检查node
代码:
enumList<T>{
caseend
indirectcasenode(T,next:List<T>)
}
varnode=List<Int>.node(10,next:List<Int>.end)
print(MemoryLayout.size(ofValue:node))
print(MemoryLayout.stride(ofValue:node))
LLDB:
阐明:
- 假如枚举被indirect润饰,编译器就不再核算这个枚举的巨细
- 在堆中开辟了一个空间,将这个地址放到枚举中
- 因而递归的时候,是无法确认实践巨细的,只能经过运转中递归的判别条件来决议
2、再检查end
代码:
LLDB:
阐明:
- 假如存储的是end,那么他就和普通的枚举一样
- 只不过这里存储的值是的当时序号的2倍
6. Swift和OC混编
上面咱们能够知道swift有很强大的功能,能够增加办法特点,还有相关值原始值,而且成员有多种类型,而OC的枚举只能是Int类型,局限性比较大,所以假如要进行混编,就需求进行必定的特别考虑
6.1 OC调用Swift
代码:
<!--swift中界说-->
@objcenumWeek:Int{
caseMON,TUE,WED,THU,FRI,SAT,SUN
}
<!--OC运用-->
-(void)test{
Weekmon=WeekMON;
}
阐明:
- 用@objc进行润饰,露出给OC
- 有必要是Int类型
OC如何访问swift中String类型的enum:
@objcenumWeek:Int{
caseMON,TUE,WED
varval:String?{
switchself{
case.MON:
return"MON"
case.TUE:
return"TUE"
case.WED:
return"WED"
default:
returnnil
}
}
}
<!--OC中运用-->
Weekmon=WeekMON;
<!--swift中运用-->
letWeek=Week.MON.val
阐明:
- swift中的enum尽量声明成Int整型
- 然后OC调用时,运用的是Int整型的
- enum在声明一个变量/办法,用于回来固定的字符串,用于在swift中运用
6.2 Swift调用OC
6.3.1 方法一:typedef enum
OC代码:
Swift代码:
阐明:
- OC的这种声明方法,会转化成Swift的结构体
- 之后就需求留意运用时的样式
- 并遵从了两个协议:Equatable 和 RawRepresentable
6.3.2 方法二:typedef NS_ENUM
OC代码:
Swift代码:
阐明:
- OC指定类型的方法,Swift也会指定类型
6.3.3 方法三:NS_ENUM
OC代码:
Swift代码:
阐明:
- 主动转化