本節,分析枚舉
enum
- 各語言枚舉區別
- swift枚舉的使用
- swift枚舉大小
- 枚舉的嵌套
- 枚舉的遞歸(indirect)
- OC橋接
- SIL分析
1. 各語言枚舉區別
1.1 C語言
枚舉
- 僅支持
Int
類型,默認首元素
值為0,后續元素值
依次+1
。
如果中間
有元素賦值
,以賦值
為準,后續沒賦值
的元素值
依舊依次+1
enum WEEK {
Mon, Tue = 10, Wed, Thu, Fri, Sat, Sun
};
enum WEEK a = Mon;
enum WEEK b = Wed;
printf("%d",a); // 打印0
printf("%d",b); // 打印11
OC語言
的枚舉類型
與C語言
一致
1.2 Swift
枚舉
十分強大
- 格式:
不用逗號
分隔,類型需使用case聲明
- 內容:
- 支持
Int
、Double
、String
等基礎類型
,有默認枚舉值
(String
類型默認枚舉值
為key名
,Int、Double
數值型默認枚舉值
為0
開始+1
遞增 )- 支持
自定義選項
不指定
支持類型
,沒有rawValue
。但同樣支持case枚舉
,可自定義關聯內容
。
- 指定類型:
沒指定
枚舉值時,各類型都有默認枚舉值
。
// Double類型
// CaseIterable協議,有allCases屬性,支持遍歷所有case
enum Week1: Double, CaseIterable {
case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}
Week1.allCases.forEach { print($0.rawValue)}
// String類型
enum Week2: String, CaseIterable {
case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}
Week2.allCases.forEach { print($0.rawValue)}
- 自定義類型(
強大
)
不指定
枚舉類型,可給枚舉項
添加拓展內容
。switch
讀取時,可提取出拓展類型
,進行相應操作。
// 自定義類型的使用
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
func printValue(_ v: Shape) {
// switch區分case(不想每個case處理,可使用default)
switch v {
case .square(let width):
print(width)
case .circle(let radius, _):
print(radius)
}
}
let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
printValue(s)
printValue(c)
2. swift枚舉的使用
- swift枚舉的讀取,有兩種方式:
1.
統一
使用switch
區分case
- 判斷
單類case
,直接使用if語句
2.1 switch方式
- 靈活的
屬性讀取
:
let
聲明整個case
分開
聲明case
中的每個關聯內容
合并case
,使用同一個變量x
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
func printValue1(_ v: Shape) {
switch v {
// 1. let聲明整個case
case let .square(x):
print(x)
// 2. 分開聲明case中的每個關聯內容
case .circle(let x, var y):
y += 100 // var聲明的變量,可修改和賦值
print("radius: \(x), borderWidth: \(y)")
}
}
func printValue2(_ v: Shape) {
switch v {
// 3. 合并case,使用同一個變量x
case let .square(x), let .circle(x, _):
print(x)
}
}
let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
print("------")
printValue1(s)
printValue1(c)
print("------")
printValue2(s)
printValue2(c)
2.2 if 方式
- 判斷是否為
指定case項
,并獲取關聯內容
。
(同樣支持整體
case關聯內容聲明
和分開聲明
)
// 自定義類型的使用
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
// 判斷s是否是square類型。并獲取`關聯內容`
// 1. 內部聲明關聯內容類型(如: let)
if case .square(let width) = s {
print(width)
}
// 2. 聲明case所有關聯內容類型(如: var)
if case var .circle(radius, borderWidth) = c {
radius += 200
borderWidth += 100
print(radius, borderWidth)
}
2.3 計算型屬性 & 函數
-
enum枚舉
支持計算型屬性
和函數
enum Direct: Int {
case up
case down
case left
case right
// 計算型屬性
var description: String{
switch self {
case .up:
return "這是上面"
default:
return "這是\(self)"
}
}
// 函數
func printSelf() {
print(description)
}
}
Direct.down.printSelf() // 打?。?這是down
3. swift枚舉大小
size
:實際占用
內存大小
stride
:系統分配
的內存大小
指定類型:
- 僅
一個case
項:size
為0
(高版本xcode
可能為1
),stride
為1
)多個case
項:
case小于255
個:size
為1
,stride
為1
超過255
個會自動擴容
,size
和stride
都會增加
。
(原因,1字節
(8bit)可區分255種
情況。所以默認size
為1
,當只有一個case
時,0x0
即可表示
。所以size
為0
和1
都可理解)image.png
自定義:
占用內存空間最大
的case
大小 +enum
自身大小:
image.png
如果不清楚Foo2
的case
大小size
為何為18
,可查看內存對齊
順帶提供一個
struct(5個屬性值)
大小計算方式
:
MyStruct1 內存計算
4. 枚舉的嵌套
-
枚舉的嵌套
,本質上
只是在不同作用域
內創建
,并沒有造成結構上
的嵌套
。
4.1 enum嵌套enum
enum Foo {
enum Direct: Int {
case up
case down
case left
case right
}
case leftUp(element1: Direct, element2: Direct)
case rightDown(element1: Direct, element2: Direct)
}
var f = Foo.leftUp(element1: .left, element2: .up)
4.2 struct嵌套enum
struct Foo {
enum Direct: Int {
case up
case down
case left
case right
}
let key: Direct
func printKey() {
switch key {
case .up: print("上")
case .down: print("下")
default:
print("其他")
}
}
}
var f = Foo(key: .down)
f.printKey() // 打?。?下
5. 枚舉的遞歸(indirect)
- 枚舉中
case關聯內容
使用自己枚舉類型
,是否會造成遞歸
?枚舉
的大小
如何確定?
案例:
樹節點
,需要重復使用:
image.png直接使用
,XCode
會報錯
。
(因為直接使用
,enum
的大小
需要case
來確定
,而case
的大小
又需要使用到enum大小
。所以無法計算大小
,報錯)
-
swift
提供了indirect
關鍵字,可以標記遞歸枚舉
,也可以標記單個case
,被標記后,case項
直接去堆中申請內存
,變為引用類型
,大小為指針8字節
。
image.png
- 輸出
SIL中間代碼
,可以看到是使用alloc_box
創建枚舉值,內部調用了swift_allocObject
,去堆
中申請空間
:
image.png在SIL官方文檔中,有介紹
alloc_box
:
image.png從
匯編層
也可佐證
:
image.png
6.OC橋接
-
OC
枚舉僅支持Int
類型,而swift
支持多種類型
。
6.1 OC
使用swift
枚舉:
swift
中創建Int
類型枚舉值
,使用@objc
聲明
image.png
@objc
聲明后,橋接文件
中,自動生成了OC
的SWIFT_ENUM
:
image.png
OC
文件中,導入swift
橋接頭文件,直接調用SwiftEnum
image.png
6.2 swift
使用OC
枚舉:
OC
的.h頭文件
聲明枚舉類型:
typedef NS_ENUM(NSUInteger, OCEnum)
:自動轉換成swift枚舉
typedef enum
:轉換成結構體
image.png
橋接文件
中,添加OC
頭文件:
image.png
自動生成
的swift
文件中,可以看到轉換的類型:
image.png
image.png
swift
文件中使用:
NS_ENUM
生成:可正常使用
typedef enum
生成:只能通過通過值初始化
,再使用
image.png
6.3 OC
使用swift
枚舉:
- 如果
swift
中不是Int
類型,而又希望OC能用
,只能自己做個橋接
。
(例如: 原本
swift
中枚舉類型
為String
,可直接通過rawValue
讀取值。
為了兼容OC
,把類型改成Int
,自定義計算型屬性
,禁止使用默認的rawValue
讀?。?/p>
swift
中創建int類型
的枚舉
,自定義string
計算屬性,并禁止rawValue
的使用。
image.png
OC
中直接使用
:
image.png
- 如果還希望
OC
能訪問到swift
對應的String
值:
使用
class
的類方法兼容
:
class
需要繼承NSObject
,類函數
完成枚舉
與String
的映射。enum
禁止rawValue
,改用string
計算屬性獲取
:
image.png
橋接文件
中可以看到生成了OC
的類方法
和枚舉
:
image.png
image.png
OC文件
中使用:
image.png
7. SIL分析
7.1 enum格式
- 案例代碼:
enum Week: String {
case Mon
case Tue
case Wed
case Thu
case Fri
case Sat
case Sun
}
- 打開終端,
cd
到當前文件夾
,swiftc -emit-sil main.swift > ./main.sil
命令輸出SIL
文件:
取消
swift函數名的混淆
輸出:swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil && open main.sil
7.2 rawValue的讀取
- 在
SIL文件
中,搜索rawValue
的getter
方法:
(switch
跳轉指定case
,執行函數
,得到case內容
,返回case內容
)
image.png
有2個疑問:
-
默認屬性
(字符串)是什么時候創建
的? - 如何記錄
case名
和對應值
的?
默認屬性
(字符串)是什么時候創建
的?
編譯期
就會生成所有符號
image.png
- 所以上面
rawValue
讀取時,可直接通過string_literal
從MachO
中讀取字符
。
- 如何記錄
case名
和對應值
的?
String類型
的枚舉
,case
與rawValue值
(打印結果一樣,但類型是對應枚舉類型
和String
)- 通過
rawValue
初始化case
時,類型為Option
(找不到
對應case
時,為nil
)enum Week: String { case Mon case Tue case Wed case Thu case Fri case Sat case Sun } // case與rawValue值(打印結果一樣,但類型不同) print("類型:\(type(of: Week.Mon)) 值:\(Week.Mon)") // 打印: 類型:Week 值:Mon print("類型:\(type(of: Week.Mon.rawValue)) 值:\(Week.Mon.rawValue)") // 打?。?類型:String 值:Mon // 通過rawValue來生成對應的case(可選類型,找不到rawValue對應的case,就是nil) print(Week.init(rawValue: "Mon")) // 打印: Optional(Demo.Week.Mon) print(Week.init(rawValue: "Hello")) // 打印: nil
通過
SIL
分析init(rawValue:)
:
image.png
完整流程1.創建:
創建枚舉
(格式:元組(Array<T>,Pointer)
,此例中T
為String
) ,再遍歷創建
所有枚舉
項。2.查詢:
通過_findStringSwitchCase
獲取入參值
的index
,使用switch
通過index
(int類型)匹配到case
,匹配成功
:返回optiona
l的some值
,匹配失敗
,直接返回nil
在
swift
源碼中搜索findStringSwitchCase
,可以看到是通過for遍歷
匹配index
:
image.png
enum枚舉
較為簡單
,我們了解了
與其他語言
的差異
、用法
,順帶探索大小
和源碼實現
。
- 下一節,介紹swift
閉包
。