補(bǔ)充:添加腳本自動生成SIL
- 通過target -> +,選擇
other -> Aggregate
帽芽,然后命名為HTScript
- 選中
HTScript
饿这,選擇Build Phases
-> 添加New Run Script Phase
- 在
Run Script
中輸入以下命令
swiftc -emit-sil ${SRCROOT}/HTEnumTest/main.swift | xcrun swift-demangle > ./main.sil && "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" main.sil
- 選中
HTScript
,Build
油湖,可以自動生成SIL文件
(即main.swift
)检激,并且vs code
自動打開文件
C中的枚舉
在介紹swift中的枚舉之前肴捉,首先我們來回顧下C中的枚舉寫法,如下所示
enum 枚舉名{
枚舉值1呵扛,
枚舉值2每庆,
......
};
<!--舉例:表示一周7天-->
enum Weak{
MON, TUE, WED, THU, FRI, SAT, SUN
};
<!--更改C中枚舉默認(rèn)值-->
//如果沒有設(shè)置枚舉默認(rèn)值,一般第一個枚舉成員的默認(rèn)值為整型0今穿,后面依此類推
enum Weak{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
<!--C中定義一個枚舉變量-->
//表明創(chuàng)建了一個枚舉,并聲明了一個枚舉變量weak
enum Weak{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
}weak;
//或者下面這種寫法伦籍,省略枚舉名稱
enum{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
}weak;
Swift中的枚舉
在swift中蓝晒,枚舉的創(chuàng)建方式如下所示,如果沒有指定枚舉值的類型帖鸦,那么enum默認(rèn)枚舉值是整型的
<!--1芝薇、寫法一-->
enum Weak{
case mon
case tue
case wed
case thu
case fri
case sat
case sun
}
<!--2、寫法二-->
//也可以直接一個case作儿,然后使用逗號隔開
enum Weak{
case mon, tue, wed, thu, fri, sat, sun
}
<!--定義一個枚舉變量-->
var w: Weak = .mon
- 如果此時想創(chuàng)建一個枚舉值是
String類型的enum
洛二,可以通過指定enum的枚舉值的類型來創(chuàng)建,其中枚舉值和原始值rawValue
的關(guān)系為case 枚舉值 = rawValue原始值
/*
- =左邊的值是枚舉值,例如 mon
- =右邊的值在swift中稱為 RawValue(原始值)晾嘶,例如 "mon"
- 兩者的關(guān)系為:case 枚舉值 = rawValue原始值
*/
enum Weak: String{
case mon = "mon"
case tue = "tue"
case wed = "wed"
case thu = "thu"
case fri = "fri"
case sat = "sat"
case sun = "sun"
}
- 如果不想寫枚舉值后的字符串妓雾,也可以使用
隱式RawValue
分配,如下所示
//String類型
enum Weak: String {
case mon, tue, wed = "wed", thu, fri, sat, sun
}
//Int類型垒迂,mon是從0開始依此類推械姻,wed后是從10依此類推
enum Weak: Int {
case mon, tue, wed = 10, thu, fri, sat, sun
}
- 【注??】:如果
enum
沒有聲明類型,是沒有rawValue
屬性的
枚舉的原始值
代碼如下:
enum Weak: String {
case mon, tue, wed, thu, fri, sat, sun
}
var value = Weak.mon.rawValue
print(value)
//---打印結(jié)果 mon
這里就有一個疑問机断,swift是如何做到打印 mon
的楷拳?我們通過SIL文件分析
- 首先查看
SIL
文件中的enum
,底層多增加了一些東西- 1吏奸、給枚舉值的類型欢揖,通過
typealias
取了一個別名RawValue
- 2、默認(rèn)添加了一個
可選類型的init方法
- 3奋蔚、增加一個
計算屬性rawValue
浸颓,用于獲取枚舉值的原始值
- 1吏奸、給枚舉值的類型欢揖,通過
- 查看SIL中的
main
方法,可以得知變量value
是通過枚舉值的rawValue
的get
方法獲取
- 查看SIL文件
rawValue
的get
方法旺拉,主要有以下幾步:- 1产上、接收一個枚舉值,用于匹配對應(yīng)的分支
- 2蛾狗、在對應(yīng)分支創(chuàng)建對應(yīng)的
String
- 3晋涣、返回對應(yīng)的
String
結(jié)論1:使用rawValue
的本質(zhì)是調(diào)用get
方法
但是get
方法中的String
是從哪里來的呢?String存儲
在哪里沉桌?
- 其實(shí)這些對應(yīng)分支的字符串在編譯時期就已經(jīng)存儲好了谢鹊,即存放在
Maach-O
文件的__TEXT.cstring
中,且是連續(xù)的內(nèi)存空間留凭,可以通過編譯后查看Mach-O
文件來驗證
結(jié)論2:rawValue
的get
方法中的分支構(gòu)建的字符串佃扼,主要是從Mach-O
文件對應(yīng)地址取出的字符串
,然后再返回給value
總結(jié)
- 使用
rawValue
的本質(zhì)就是在底層調(diào)用get
方法蔼夜,即在get方法中從Mach-O
對應(yīng)地址中取出字符串并返回
的操作
區(qū)分 case枚舉值 & rawValue原始值
請問下面這段代碼的打印結(jié)果是什么兼耀?
//輸出 case枚舉值
print(Weak.mon)
//輸出 rawValue
print(Weak.mon.rawValue)
//---打印結(jié)果
mon
mon
雖然這兩個輸出的值從結(jié)果來看是沒有什么區(qū)別的,雖然輸出的都是mon
求冷,但并不是同一個東西
- 第一個輸出的
case枚舉值
- 第二個是通過
rawValue
訪問的rawValue
的get
方法
如果我們像下面這種寫法瘤运,編譯器就會報錯
枚舉的init初始化
主要是探索枚舉的init會在什么時候調(diào)用
- 定義一個符號斷點(diǎn)
Weak.init
定義如下代碼
enum Weak: String {
case mon, tue, wed, thu, fri, sat, sun
}
var w = Weak.init(rawValue: "mon")
var sun = Weak(rawValue: "mon")
運(yùn)行發(fā)現(xiàn),這兩種初始化方法都會觸發(fā)調(diào)試斷點(diǎn)
總結(jié):enum
中init
方法的調(diào)用是通過枚舉.init(rawValue:)
或者枚舉(rawValue:)
觸發(fā)的
- 我們再繼續(xù)來分析
init
方法匠题,來看下面這段代碼的打印結(jié)果是什么拯坟?
var w = Weak.init(rawValue: "mon")
var sun = Weak(rawValue: "sunday")
print(w)
print(sun)
從運(yùn)行結(jié)果可以看出,第一個輸出的是
可選值
韭山;第二個輸出的是nil
郁季,表示沒有找到對應(yīng)的case
枚舉值冷溃。為什么會出現(xiàn)這樣的情況呢?
- 首先分析
SIL
文件中的Weak.init
方法梦裂,主要有以下幾步:- 1似枕、在
init
方法中是將所有enum
的字符串從Mach-O文件中取出,依次放入數(shù)組中 - 2塞琼、放完后菠净,然后調(diào)用
_findStringSwitchCase
方法進(jìn)行匹配
- 1似枕、在
其中 index_addr
表示獲取當(dāng)前數(shù)組中的第n個元素值的地址
,然后再把構(gòu)建好的字符串放到當(dāng)前地址中
- `struct_extract` 表示`取出當(dāng)前的Int值`彪杉,Int類型在系統(tǒng)中也是結(jié)構(gòu)體
- `cond_br` 表示比較的表達(dá)式毅往,即分支條件跳轉(zhuǎn)
- 如果匹配成功,則構(gòu)建一個`.some的Optional`返回
- 如果匹配不成功派近,則繼續(xù)匹配攀唯,知道最后還是沒有匹配上,則構(gòu)建一個`.none的Optional`返回
- 在
swift-source
中查找_findStringSwitchCase
方法渴丸,接收兩個參數(shù)侯嘀,分別是數(shù)組 + 需要匹配的String
- 1、遍歷數(shù)組谱轨,如果匹配則返回對應(yīng)的
index
- 2戒幔、如果不匹配,則返回-1
- 1、遍歷數(shù)組谱轨,如果匹配則返回對應(yīng)的
@_semantics("findStringSwitchCase")
public // COMPILER_INTRINSIC
// 接收一個數(shù)組 + 需要匹配的string
func _findStringSwitchCase(
cases: [StaticString],
string: String) -> Int {
// 遍歷之前創(chuàng)建的字符串?dāng)?shù)組土童,如果匹配則返回對應(yīng)的index
for (idx, s) in cases.enumerated() {
if String(_builtinStringLiteral: s.utf8Start._rawValue,
utf8CodeUnitCount: s._utf8CodeUnitCount,
isASCII: s.isASCII._value) == string {
return idx
}
}
// 如果不匹配诗茎,則返回-1
return -1
}
枚舉的遍歷
- 對于某些枚舉來說,如果能有一個集合包含了枚舉的所有情況就好了献汗。你可以通過在枚舉名字后面寫 :
CaseIterable
來允許枚舉被遍歷敢订。Swift 會暴露一個包含對應(yīng)枚舉類型所有情況的集合名為allCases
CaseIterable協(xié)議
通常用于沒有關(guān)聯(lián)值的枚舉
,用來訪問所有的枚舉值
罢吃,只需要對應(yīng)的枚舉遵守該協(xié)議即可楚午,然后通過allCases
獲取所有枚舉值,如下所示
//1尿招、定義無關(guān)聯(lián)值枚舉矾柜,并遵守協(xié)議
enum Weak: String {
case mon, tue, wed, thu, fri, sat, sun
}
extension Weak: CaseIterable {}
//2、通過for循環(huán)遍歷
for oneCase in Weak.allCases {
print(oneCase)
}
//3泊业、通過函數(shù)式編程遍歷
let allCase = Weak.allCases.map { $0.rawValue }.joined(separator: ",")
print(allCase)
//打印結(jié)果: mon,tue,wed,thu,fri,sat,sun
關(guān)聯(lián)值
- 如果希望用枚舉表示復(fù)雜的含義把沼,關(guān)聯(lián)更多的信息,就需要使用
關(guān)聯(lián)值
例如吁伺,使用enum表達(dá)一個形狀,其中有圓形租谈、長方形等篮奄,圓形有半徑捆愁,長方形有寬、高窟却,我們可以通過下面具有關(guān)聯(lián)值的enum來表示
//注:當(dāng)使用了關(guān)聯(lián)值后昼丑,就沒有RawValue了,主要是因為case可以用一組值來表示夸赫,而rawValue是單個的值
enum Shape{
//case枚舉值后括號內(nèi)的就是關(guān)聯(lián)值菩帝,例如 radius
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
【注??】具有關(guān)聯(lián)值的枚舉,就沒有rawValue屬性
了茬腿,主要是因為一個case可以用一個或者多個值來表示呼奢,而rawValue只有單個的值
這一點(diǎn)我們也可以通過SIL文件 來驗證
- 首先查看
SIL
文件,發(fā)現(xiàn)此時的enum中既沒有別名
切平,也沒有init
方法握础、計算屬性rawValue
了
- 其中關(guān)聯(lián)值中
radius
、width
悴品、height
這些都是自定義的標(biāo)簽
禀综,也可以不寫,如下所示苔严,但并不推薦這種方式定枷,因為可讀性非常差
enum Shape{
//case枚舉值后括號內(nèi)的就是關(guān)聯(lián)值,例如 radius
case circle(Double)
case rectangle(Int, Int)
}
- 關(guān)聯(lián)值的枚舉值的創(chuàng)建
//創(chuàng)建
var circle = Shape.circle(radius: 10.0)
//重新分配
circle = Shape.rectangle(width: 10, height: 10)
模式匹配
enum中的模式匹配其實(shí)就是匹配case枚舉值
簡單enum的模式匹配
【注】:swift中的enum模式匹配需要將所有情況都列舉届氢,或者使用default
表示默認(rèn)情況欠窒,否則會報錯
enum Weak: String {
case mon, tue, wed, thu, fri, sat, sun
}
var w: Weak?
switch w {
case .mon:
print(Weak.mon.rawValue)
case .tue:
print(Weak.tue.rawValue)
default:
print("unknow day")
}
//---打印結(jié)果 unknow day
查看其SIL文件,其內(nèi)部是將nil放入w全局變量
悼沈,然后匹配case贱迟,做對應(yīng)的代碼跳轉(zhuǎn)
具有關(guān)聯(lián)值enum的模式匹配
關(guān)聯(lián)值的模式匹配主要有兩種:
- 1、通過
switch
匹配所有case
let shape = Shape.circle(radius: 10.0)
switch shape{
//相當(dāng)于將10.0賦值給了聲明的radius常量
case let .circle(radius):
print("circle radius: \(radius)")
case let .rectangle(width, height):
print("rectangle width: \(width) height: \(height)")
}
//---打印結(jié)果: circle radius: 10.0
- 2絮供、也可以這么寫,將關(guān)聯(lián)值的參數(shù)使用
let衣吠、var
修飾
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
let shape = Shape.circle(radius: 10)
switch shape{
//做了Value-Binding,相當(dāng)于將10.0賦值給了聲明的radius常量
case .circle(let radius):
print("circle radius: \(radius)")
case .rectangle(let width, var height):
height += 1
print("rectangle width: \(width) height: \(height)")
}
//---打印結(jié)果: circle radius: 10.0
然后查看SIL中的關(guān)聯(lián)值的模式匹配
壤靶,如下圖所示
- 1缚俏、首先構(gòu)建一個關(guān)聯(lián)值的元組
- 2、根據(jù)當(dāng)前case枚舉值贮乳,匹配對應(yīng)的case忧换,并跳轉(zhuǎn)
- 3、取出元組中的值向拆,將其賦值給匹配case中的參數(shù)
- 通過
if case
匹配單個case亚茬,如下所示
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
let shape = Shape.circle(radius: 10.0)
//匹配單個case
if case let Shape.circle(radius) = shape {
print("circle radius: \(radius)")
}
//---打印結(jié)果: circle radius: 10.0
- 如果我們只關(guān)心不同case的相同關(guān)聯(lián)值(即關(guān)心不同case的某一個值),需要使用同一個參數(shù)浓恳,例如案例中的x刹缝,如果分別使用x碗暗、y, 編譯器會報錯
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, height: Double)
}
let shape = Shape.circle(radius: 10)
switch shape{
case let .circle(x), let .square(20, x):
print(x)
default:
break
}
- 也可以使用
通配符_(表示匹配一切)
的方式,如下圖所示
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, height: Double)
}
let shape = Shape.rectangle(width: 10, height:20)
switch shape{
case let .rectangle(x, _), let .square(_, x):
print("x = \(x)")
default:
break
}
枚舉的嵌套
枚舉的嵌套主要用于以下場景:
- 1梢夯、【枚舉嵌套枚舉】一個復(fù)雜枚舉是由一個或多個枚舉組成
- 2言疗、【結(jié)構(gòu)體嵌套枚舉】enum是不對外公開的,即是私有的
枚舉嵌套枚舉
枚舉嵌套枚舉颂砸,實(shí)現(xiàn)方向的組合噪奄,如下圖所示
enum CombineDirect{
//枚舉中嵌套的枚舉
enum BaseDirect{
case up
case down
case left
case right
}
//通過內(nèi)部枚舉組合的枚舉值
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)
print(leftUp)
結(jié)構(gòu)體嵌套枚舉
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
//計算屬性 - 本質(zhì)是方法(get、set方法)
var with: Double {
get {
return 10.0
}
}
//類型屬性 - 是一個全局變量
static let height = 20.0
}
為什么struct中可以放存儲屬性撒蟀,而enum不可以叙谨?
主要是因為struct
中可以包含存儲屬性
是因為其大小就是存儲屬性的大小
。而對enum來說就是不一樣的保屯,enum枚舉的大小是取決于case的個數(shù)的
手负,如果沒有超過255,enum的大小就是1字節(jié)(8位)
枚舉中包含方法
可以在enum中定義實(shí)例方法姑尺、static修飾的方法
enum Weak: Int {
case mon, tue, wed, thu, fri, sat, sun
mutating func nextDay() {
self = Weak.init(rawValue: self.rawValue + 1) ?? Weak.mon
}
}
//使用
var w = Weak.mon
w.nextDay()
print(w)
indirect關(guān)鍵字
如果我們想要表達(dá)的enum是一個復(fù)雜的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)時竟终,可以通過indirect
關(guān)鍵字來讓當(dāng)前的enum更簡潔
//用枚舉表示鏈表結(jié)構(gòu)
enum List<T>{
case end
//表示case使是引用來存儲
indirect case node(T, next: List<T>)
}
<!--也可以將indirect放在enum前-->
//表示整個enum是用引用來存儲
indirect enum List<T>{
case end
case node(T, next: List<T>)
}
為什么呢?
- 因為
enum
是值類型
切蟋,也就意味著他們的大小在編譯時期就確定了统捶,那么這個過程中對于當(dāng)前的enum的大小是不能確定的,從系統(tǒng)的角度來說柄粹,不知道需要給enum分配多大的空間喘鸟,以下是官方文檔的解釋
You indicate that an enumeration case is recursive by writing indi rect before it, which tells the compiler to insert the necessary l ayer of indirection.
- 打印
enum
的大小
如果傳入的類型是String呢?
從結(jié)果發(fā)現(xiàn)驻右,換成其他類型什黑,其結(jié)果依舊是8,這是為什么呢堪夭?
下面來分析其內(nèi)存結(jié)構(gòu)愕把,首先需要定義一個全局變量
enum List<T>{
case end
indirect case node(T, next: List<T>)
}
var node = List<Int>.node(10, next: List<Int>.end)
print(MemoryLayout.size(ofValue: node))
print(MemoryLayout.stride(ofValue: node))
通過lldb分析其內(nèi)存
所以indirect
關(guān)鍵字其實(shí)就是通知編譯器,我當(dāng)前的enum是遞歸的森爽,大小是不確定的恨豁,需要分配一塊堆區(qū)的內(nèi)存空間
,用來存放enum
- 如果是
end
爬迟,此時存儲的是case值
橘蜜,而case為node
(indirect
修飾)時存儲的是引用地址
- 通過SIL來驗證
swift和OC混編enum
在swift中,enum
非常強(qiáng)大付呕,可以添加方法
扮匠、添加extension
而在OC中捧请,enum僅僅只是一個整數(shù)值
如果想將swift中的enum暴露給OC使用:
- 用
@objc
關(guān)鍵字標(biāo)記enum - 當(dāng)前enum應(yīng)該是Int類型
OC調(diào)用Swift的enum
<!--swift中定義-->
@objc enum Weak: Int {
case mon, tue, wed, thu, fri, sat, sun
}
<!--OC使用-->
- (void)test {
Weak mon = WeakMon;
}
- 枚舉必須用
@objc
修飾凡涩,必須是Int
類型
Swift調(diào)用OC的enum
OC中的枚舉會自動轉(zhuǎn)換成swift中的enum
<!--OC定義-->
//會自動轉(zhuǎn)換成swift的enum
NS_ENUM(NSInteger, OCENUM){
Value1,
Value2
};
<!--swift使用-->
//1棒搜、將OC頭文件導(dǎo)入橋接文件
#import "HTOcTest.h"
//2、使用
let ocEnum = OCENUM.Value1
- 如果OC中是使用
typedef enum
定義的活箕,自動轉(zhuǎn)換成swift就成了下面這樣
typedef enum {
Num1,
Num2
}OCNum;
<!--swift中使用-->
let typeEnum = OCNum.init(rawValue: 1)
print(typeEnum)
//*******打印結(jié)果*******
OCNum(rawValue: 0)
問題:OC如何訪問swift中String類型的enum力麸?
- swift中的enum盡量聲明成Int整型
- 然后OC調(diào)用時,使用的是Int整型的
- enum在聲明一個變量/方法育韩,用于返回固定的字符串克蚂,用于在swift中使用
@objc enum Weak: Int{
case MON, TUE, WED
var val: String?{
switch self {
case .MON:
return "MON"
case .TUE:
return "TUE"
case .WED:
return "WED"
default:
return nil
}
}
}
<!--OC中使用-->
Weak mon = WeakMON;
<!--swift中使用-->
let weak = Weak.MON.val
枚舉的大小
主要分析以下幾種情況的大小:
- 1筋讨、普通enum
- 2埃叭、具有關(guān)聯(lián)值的enum
- 3、enum嵌套enum
- 4悉罕、struct嵌套enum
1赤屋、普通enum大小分析
enum中不能包含存儲屬性,其根本在于enum的大小與Struct的計算方式是不一樣的壁袄,這里我們將展開詳細(xì)的分析
- 首先类早,我們先來看看下面這段代碼的打印結(jié)果是什么?
enum NoMean{
case a
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)
<!--打印結(jié)果-->
0 //size大小是0
1 //表示訪問下一個NoMean的case時嗜逻,需要跨越1字節(jié)的步長
- 如果此時增加一個
case b
涩僻,此時的打印結(jié)果是什么?
enum NoMean{
case a
case b
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)
<!--打印結(jié)果-->
1 //size大小是1
1 //步長是1
- 如果再增加多個呢栈顷?
enum NoMean{
case a
case b
case c
case d
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)
<!--打印結(jié)果-->
1
1
- 如果繼續(xù)增加
case
的個數(shù)超過256
時
從上面結(jié)果分析得出:
- case默認(rèn)是
UInt8
逆日,即1字節(jié)(8位)
,最大可以存儲256個case
- 如果 case個數(shù)超過
256
萄凤,會自動從UInt8 -> UInt16 -> UInt32 -> UInt64 ...
LLDB分析
- 分別定義4個全局變量tmp室抽、tmp1、tmp2蛙卤、tmp3
enum NoMean{
case a
case b
case c
case d
}
var tmp = NoMean.a
var tmp1 = NoMean.b
var tmp2 = NoMean.c
var tmp3 = NoMean.d
通過lldb查看內(nèi)存情況如下狠半,case都是1字節(jié)大小
2、具有關(guān)聯(lián)值enum的大小分析
如果enum中有關(guān)聯(lián)值
颤难,其大小又是多少呢神年?有如下代碼,打印其size和stride
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
<!--打印結(jié)果-->
17 //size的大小是17
24 //stride的步長是24
說明從打印結(jié)果可以說明 enum中有關(guān)聯(lián)值時行嗤,其內(nèi)存大小取決于關(guān)聯(lián)值的大小
-
enum有關(guān)聯(lián)值
時已日,關(guān)聯(lián)值的大小 取 對應(yīng)枚舉關(guān)聯(lián)值 最大的,例如circle中關(guān)聯(lián)值大小是8栅屏,而rectangle中關(guān)聯(lián)值大小是16飘千,所以取16堂鲜。所以enum的size = 最大關(guān)聯(lián)值大小 + case(枚舉值)大小
= 16 + 1 = 17,而stride
由于8字節(jié)對齊护奈,所以自動補(bǔ)齊到24
- 定義一個全局變量缔莲,觀察其內(nèi)存
總結(jié)
- 1、具有
關(guān)聯(lián)值的enum
大小霉旗,取決于最大case的內(nèi)存大小
【枚舉大小的本質(zhì)】 - 2痴奏、關(guān)聯(lián)值枚舉的大小 = 最大case的內(nèi)存大小 + 1(case的大小)
- 3厌秒、size 表示 實(shí)際大小
- 4读拆、stride 表示 對齊后的大小(內(nèi)存空間中真實(shí)占用的大型疑痢)
3檐晕、enum嵌套enum的大小分析
請問下面這段代碼的打印結(jié)果是什么?
enum CombineDirect{
enum BaseDirect{
case up, down, left, 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(MemoryLayout<CombineDirect>.size)
print(MemoryLayout<CombineDirect>.stride)
<!--打印結(jié)果-->
2 //size大小蚌讼,enum有關(guān)聯(lián)值取決于關(guān)聯(lián)值的大小辟灰,每個case都有2個大小為1的enum,加上case枚舉值 應(yīng)該是 3啦逆,這里編譯器做了優(yōu)化伞矩,所以為2
2 //stride大小
總結(jié)
-
enum嵌套enum
同樣取決于最大case的關(guān)聯(lián)值大小
- 當(dāng)嵌套enum的case只有
2
個時,case在內(nèi)存中的存儲是0夏志、8
- 當(dāng)嵌套enum的case大于2乃坤,小于等于4時,case在內(nèi)存中的存儲是
0沟蔑、4湿诊、8、c
- 當(dāng)嵌套enum的case
大于4
時瘦材,case在內(nèi)存中的存儲是從0厅须、1、2...
以此類推
4食棕、結(jié)構(gòu)體嵌套enum的大小分析
請問下面這段代碼的打印結(jié)果是什么朗和?
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")
}
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
1
1
- 如果只嵌套了enum,沒有聲明變量簿晓,結(jié)構(gòu)體的大小是多少呢眶拉?
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
0 //size的大小取決于成員變量,但是struct中目前沒有屬性
1
- 如果不僅有枚舉變量憔儿,還有其他屬性忆植,結(jié)構(gòu)體的大小是多少呢?
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
let key: KeyType //1字節(jié)
var height: UInt8 //1字節(jié)
func launchSkill(){
switch key {
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
2
2
- 如果在增加一個Int類型的屬性呢?
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
var width: Int //8字節(jié)
let key: KeyType //1字節(jié)
var height: UInt8 //1字節(jié)
func launchSkill(){
switch key {
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
10 //size大谐(與OC中的結(jié)構(gòu)體大小計算是一致的耀里,min(m,n),其中m表示存儲的位置,n表示屬性的大小拾氓,要求是:m必須整除n)
16 //stride大小
總結(jié)
- 1冯挎、如果結(jié)構(gòu)體中沒有其他屬性,只有枚舉變量痪枫,那么結(jié)構(gòu)體的大小就是枚舉的大小织堂,即size為1
- 2、如果結(jié)構(gòu)體中嵌套了enum奶陈,但是沒有聲明變量,此時的size是0附较,stride是1
- 3吃粒、如果結(jié)構(gòu)體中還有其他屬性,則按照OC中的
結(jié)構(gòu)體內(nèi)存對齊三原則
進(jìn)行分析
內(nèi)存對齊 & 字節(jié)對齊 區(qū)分
-
內(nèi)存對齊
:iOS中是8字節(jié)
對齊拒课,蘋果實(shí)際分配采用16
字節(jié)對齊徐勃,這種只會在分配對象時出現(xiàn)
-
字節(jié)對齊
:存儲屬性的位置必須是偶地址
,即OC內(nèi)存對齊中的min(m早像,n)
僻肖,其中m表示存儲的位置,n表示屬性的大小卢鹦,需要滿足位置m整除n時臀脏,才能從該位置存放屬性。簡單來說冀自,就是必須在自身的倍數(shù)位置開始
-
外部調(diào)用對象
時揉稚,對象是服從內(nèi)存對齊
。 - 單純從
結(jié)構(gòu)
上說熬粗,結(jié)構(gòu)內(nèi)部服從最大字節(jié)對齊
搀玖。
例如下面這個例子
struct Skill {
var age: Int //8字節(jié)
var height: UInt8 //1字節(jié)
var width: UInt16 //2字節(jié)
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
12
16
-
size為12
的原因是:內(nèi)存從0位置開始Int是占據(jù)0-7,UInt8占據(jù)8驻呐,下一個位置是9灌诅,但是UInt16是2字節(jié)對齊的要在它的倍數(shù)位置開始所以找下一個可以整除它的位置也就是UInt16占據(jù)10-11正好整個size在0-11,所以size為12 -
stride為16
的原因:stride是實(shí)際分配的含末,必須是最大屬性大小的整數(shù)倍猜拾,即8的倍數(shù),所以是16
總結(jié)
- 枚舉說明:
- 1答渔、enum中使用
rawValue
的本質(zhì)是調(diào)用get
方法关带,即在get方法中從Mach-O對應(yīng)地址中取出字符串并返回
的操作 - 2、enum中
init
方法的調(diào)用是通過枚舉.init(rawValue:)
或者枚舉(rawValue:)
觸發(fā)的 - 3、沒有
關(guān)聯(lián)值的enum
宋雏,如果希望獲取所有枚舉值芜飘,需要遵循CaseIterable協(xié)議
,然后通過枚舉名.allCase
的方式獲取 - 4磨总、case枚舉值和rawValue原始值的關(guān)系:
case 枚舉值 = rawValue原始值
- 5嗦明、enum的模式匹配方式,主要有兩種:
switch
/if case
- 6、enum可以嵌套enum毫痕,也可以在結(jié)構(gòu)體中嵌套enum虑瀑,表示該enum是struct私有的
- 7、enum中還可以包含
計算屬性
诗良、類型屬性
,但是不能包含存儲屬性
- 8鲁驶、enum中可以定義
實(shí)例 + static修飾
的方法
- 1答渔、enum中使用
- 枚舉內(nèi)存大小
- 1鉴裹、普通enum的內(nèi)存大小一般是
1字節(jié)
,如果只有一個case钥弯,則為0径荔,表示沒有意義,如果case個數(shù)超過256
脆霎,則枚舉值的類型由UInt8->UInt16->UInt32...
- 2总处、
具有關(guān)聯(lián)值的enum
大小,取決于最大case的內(nèi)存大小
+case的大芯χ搿(1字節(jié)) - 3鹦马、enum嵌套enum同樣取決于最大case的關(guān)聯(lián)值大小
- 4、結(jié)構(gòu)體嵌套enum玖院,如果
沒有屬性菠红,則size為0
,如果只有enum屬性难菌,size為1
试溯,如果還有其他屬性,則按照OC中內(nèi)存對齊原則進(jìn)行計算
- 1鉴裹、普通enum的內(nèi)存大小一般是