C enum
C語言枚舉格式
enum 枚舉名 {
枚舉值 1,
枚舉值 2,
……
};
比如要表示星期幾的枚舉:
enum week {
MON,
TUE,
WED,
THU,
FRI,
SAT,
SUN
};
第一個枚舉成員的默認值為0,依次加1。要改變枚舉值直接賦值就可以了。
聲明一個枚舉變量如下:
enum week {//這里枚舉名可以省略
MON,
TUE,
WED,
THU,
FRI,
SAT,
SUN
} week;
Swift 枚舉
同樣的一個week枚舉,在swift中定義如下:
enum week {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
var w: week = week.MONDAY
swift枚舉不需要,
和;
,不過需要case
。可以簡寫只有一個case
:
enum week {
case MONDAY, TUEDAY, WEDDAY, THUDAY, FRIDAY, SATDAY, SUNDAY
}
Swift中枚舉默認也是整形,區別是根據枚舉的數量編譯器會處理整形的類型比如:Int8
、Int16
、Int32
、Int64
。
遍歷
枚舉可以像集合那樣遍歷,需要遵循CaseIterable
協議。
enum week {
case MONDAY, TUEDAY, WEDDAY, THUDAY, FRIDAY, SATDAY, SUNDAY
}
extension week : CaseIterable {}
for enumCase in week.allCases {
print(enumCase)
}
MONDAY
TUEDAY
WEDDAY
THUDAY
FRIDAY
SATDAY
SUNDAY
rawValue(原始值)
如果想用Sring表達Enum,在Swift中可以如下:
enum week: String { //這里必須注明類型
case MONDAY = "MONDAY"
case TUEDAY = "TUEDAY"
case WEDDAY = "WEDDAY"
case THUDAY //不賦值 “THUDAY”
case FRIDAY
case SATDAY
case SUNDAY
}
在這里=
左邊的值我們叫做rawValue
。想要訪問rawValue
必須設置類型(Int
也需要)。
那么這里Swift是怎么處理rawValue
的呢?
var w = week.MONDAY.rawValue
查看sil
enum week : String {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
typealias RawValue = String
init?(rawValue: String)
var rawValue: String { get }
}
可以看到String
有一個別名RawValue
。有一個可選的初始化方法,有一個計算屬性rawValue
。所以本質上我們訪問rawValue
是在訪問計算屬性。
變量w
的生成過程如下:
// 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
//全局變量w
%3 = global_addr @main.w : Swift.String : $*String // user: %8
%4 = metatype $@thin week.Type
//拿到MONDAY枚舉
%5 = enum $week, #week.MONDAY!enumelt // user: %7
// function_ref week.rawValue.getter
//獲取枚舉計算屬性rawValue的get方法
%6 = function_ref @main.week.rawValue.getter : Swift.String : $@convention(method) (week) -> @owned String // user: %7
//枚舉值傳給rawValue的get方法,返回一個String
%7 = apply %6(%5) : $@convention(method) (week) -> @owned String // user: %8
//把String給比變量w
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.week.rawValue.getter
方法比較長,截取一部分如下:
// week.rawValue.getter
sil hidden @main.week.rawValue.getter : Swift.String : $@convention(method) (week) -> @owned String {
// %0 "self" // users: %2, %1
//接收一個枚舉值參數week
bb0(%0 : $week):
//默認有個self=枚舉值
debug_value %0 : $week, let, name "self", argno 1 // id: %1
//匹配枚舉跳轉對應分支
switch_enum %0 : $week, case #week.MONDAY!enumelt: bb1, case #week.TUEDAY!enumelt: bb2, case #week.WEDDAY!enumelt: bb3, case #week.THUDAY!enumelt: bb4, case #week.FRIDAY!enumelt: bb5, case #week.SATDAY!enumelt: bb6, case #week.SUNDAY!enumelt: bb7 // id: %2
bb1: // Preds: bb0
//構建字符串MONDAY
%3 = string_literal utf8 "MONDAY" // user: %8
%4 = integer_literal $Builtin.Word, 6 // 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
//跳轉bb8
br bb8(%8 : $String)
// %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'
所以在這個過程中訪問rawValue
的getter
方法,根據枚舉值找到對應分支構建字符串然后返回。
那么MONDAY
存在哪里呢?
可以看到是連續的內存空間來存儲字符串。在程序運行的時候會通過地址+偏移量取找到這個字符串。
case和rawValue
print(week.MONDAY)
print(week.MONDAY.rawValue)
MONDAY
MONDAY
case
和rawValue
輸出一樣,那么這兩個相同么?
顯然不是一個東西,不是同一個類型。這里肯定也不能互相賦值,并且也不能對
rawValue
賦值(只有getter
方法)。
init?
上面分析的sil里面有一個init
方法
print(week.init(rawValue:"MONDAY")!)//MONDAY
print(week.init(rawValue:"123"))//nil
MONDAY
nil
可以打個符號斷點具體看一下,
//走不到斷點
print(week.MONDAY.rawValue)//MONDAY
//可以斷點
print(week(rawValue:"MONDAY")!)//MONDAY
print(week.init(rawValue:"123"))//nil
發現訪問rawValue
不走init方法,通過rawValue
初始化才走init
方法。
分析下SIL
文件
// week.init(rawValue:)
sil hidden @main.week.init(rawValue: Swift.String) -> main.week? : $@convention(method) (@owned String, @thin week.Type) -> Optional<week> {
// %0 "rawValue" // users: %164, %158, %79, %3
// %1 "$metatype"
bb0(%0 : $String, %1 : $@thin week.Type):
%2 = alloc_stack $week, var, name "self" // users: %162, %154, %143, %132, %121, %110, %99, %88, %165, %159
debug_value %0 : $String, let, name "rawValue", argno 1 // id: %3
%4 = integer_literal $Builtin.Word, 7 // user: %6
// function_ref _allocateUninitializedArray<A>(_:)
//調用方法創建一個數組
%5 = function_ref @Swift._allocateUninitializedArray<A>(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
//返回一個元組
%6 = apply %5<StaticString>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
//返回值是一個元組類型(Array,RawPointer當前指針)
%7 = tuple_extract %6 : $(Array<StaticString>, Builtin.RawPointer), 0 // users: %80, %79
%8 = tuple_extract %6 : $(Array<StaticString>, Builtin.RawPointer), 1 // user: %9
//訪問數組首地址
%9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*StaticString // users: %17, %69, %59, %49, %39, %29, %19
//構建第一個字符串,存儲在 array index 0
%10 = string_literal utf8 "MONDAY" // user: %12
%11 = integer_literal $Builtin.Word, 6 // user: %16
%12 = builtin "ptrtoint_Word"(%10 : $Builtin.RawPointer) : $Builtin.Word // user: %16
br bb1 // id: %13
bb1: // Preds: bb0
%14 = integer_literal $Builtin.Int8, 2 // user: %16
br bb2 // id: %15
bb2: // Preds: bb1
//構建StaticString
%16 = struct $StaticString (%12 : $Builtin.Word, %11 : $Builtin.Word, %14 : $Builtin.Int8) // user: %17
store %16 to %9 : $*StaticString // id: %17
// 1
%18 = integer_literal $Builtin.Word, 1 // user: %19
//%9數組首地址,19%存儲當前數組index 1的地址
%19 = index_addr %9 : $*StaticString, %18 : $Builtin.Word // user: %27
//構建第二個字符串,存儲在array index 1的位置
%20 = string_literal utf8 "TUEDAY" // user: %22
%21 = integer_literal $Builtin.Word, 6 // user: %26
%22 = builtin "ptrtoint_Word"(%20 : $Builtin.RawPointer) : $Builtin.Word // user: %26
br bb3 // id: %23
bb14: // Preds: bb13
%76 = struct $StaticString (%72 : $Builtin.Word, %71 : $Builtin.Word, %74 : $Builtin.Int8) // user: %77
store %76 to %69 : $*StaticString // id: %77
// function_ref _findStringSwitchCase(cases:string:)
//拿到函數
%78 = function_ref @Swift._findStringSwitchCase(cases: [Swift.StaticString], string: Swift.String) -> Swift.Int : $@convention(thin) (@guaranteed Array<StaticString>, @guaranteed String) -> Int // user: %79
//拿到數組中元素的index
%79 = apply %78(%7, %0) : $@convention(thin) (@guaranteed Array<StaticString>, @guaranteed String) -> Int // users: %149, %138, %127, %116, %105, %94, %83, %147, %136, %125, %114, %103, %92, %81
release_value %7 : $Array<StaticString> // id: %80
debug_value %79 : $Int, let, name "$match" // id: %81
//0
%82 = integer_literal $Builtin.Int64, 0 // user: %84
//拿到結構體的index
%83 = struct_extract %79 : $Int, #Int._value // user: %84
//比較兩個index
%84 = builtin "cmp_eq_Int64"(%82 : $Builtin.Int64, %83 : $Builtin.Int64) : $Builtin.Int1 // user: %85
//cond_br 成功跳轉bb15,否則跳轉bb16
cond_br %84, bb15, bb16 // id: %85
bb15: // Preds: bb14
%86 = metatype $@thin week.Type
//返回枚舉值
%87 = enum $week, #week.MONDAY!enumelt // user: %89
%88 = begin_access [modify] [static] %2 : $*week // users: %89, %90
store %87 to %88 : $*week // id: %89
end_access %88 : $*week // id: %90
br bb29 // id: %91
bb16: // Preds: bb14
debug_value %79 : $Int, let, name "$match" // id: %92
%93 = integer_literal $Builtin.Int64, 1 // user: %95
%94 = struct_extract %79 : $Int, #Int._value // user: %95
//把1和index比較,一直循環比較
%95 = builtin "cmp_eq_Int64"(%93 : $Builtin.Int64, %94 : $Builtin.Int64) : $Builtin.Int1 // user: %96
cond_br %95, bb17, bb18 // id: %96
bb28: // Preds: bb26
release_value %0 : $String // id: %158
dealloc_stack %2 : $*week // id: %159
//沒有匹配到返回none,也就是nil
%160 = enum $Optional<week>, #Optional.none!enumelt // user: %161
br bb30(%160 : $Optional<week>) // id: %161
bb29: // Preds: bb27 bb25 bb23 bb21 bb19 bb17 bb15
%162 = load %2 : $*week // user: %163
//匹配到了
%163 = enum $Optional<week>, #Optional.some!enumelt, %162 : $week // user: %166
release_value %0 : $String // id: %164
dealloc_stack %2 : $*week // id: %165
br bb30(%163 : $Optional<week>) // id: %166
// %167 // user: %168
bb30(%167 : $Optional<week>): // Preds: bb29 bb28
//返回可選值
return %167 : $Optional<week> // id: %168
} // end sil function 'main.week.init(rawValue: Swift.String) -> main.week?'
首先構造數組,從macho文件里面逐一讀取cstring存放到數組中。
這里可以看到有一個_findStringSwitchCase
方法,在swift
源碼中這個方法:
func _findStringSwitchCase(
cases: [StaticString],
string: String) -> Int {
for (idx, s) in cases.enumerated() {
if String(_builtinStringLiteral: s.utf8Start._rawValue,
utf8CodeUnitCount: s._utf8CodeUnitCount,
isASCII: s.isASCII._value) == string {
return idx
}
}
return -1
}
接收一個數組和目標字符串。返回index
。如果找不到返回-1
。所以枚舉在底層就相當于Int
。查找枚舉也就是查找index。
- 創建一個Array,把所有的macho中的字符串取出來依次存儲到數組中。
- 通過init參數中的值去數組中找index。然后依次與0、1、2比較。
- 通過比較index找到相等則返回對應枚舉,否則返回
none
。是一個可選值。
Associated Value (關聯值)
從上面看到原始值只能對應一個值。如果想用枚舉表達更復雜的信息時rawValue
就不夠用了。這個時候就可以使用Associated Value
比如:
enum Shape{
case circle(radious: Double) //這里標簽可以省略,不推薦
case rectangle(width: Int, height: Int)
}
var circle = Shape.circle(radious: 10)
- 添加關聯值后就沒有原始值了。(因為已經允許使用一組值了,通過SIL查看也沒有對應的rawValue和init方法了)
- 標簽可以省略
enum模式匹配
對于一般的枚舉我們直接switch case匹配就可以了如:
enum week: String {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
var w: week?
switch w {
case .MONDAY: print("MONDAY")
case .SUNDAY: print("SUNDAY")
default: print("default")
}
- 不匹配所有
case
需要default,不然會報錯。 - 對于多個
case
和oc
不同的是要用,
隔開匹配。
原理是把w
存儲在全局變量中,匹配對應的case
做代碼跳轉。
對于關聯值想要匹配
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
var circle = Shape.circle(radious: 10)
switch circle {
case let .circle(radious) : print("\(radious)")//這里相當于把常量10給了radious。
case let .rectangle(width, height): print("\(width) \(height)")
}
- 進行了
value - binding
,如果case
匹配上把值給了常量/變量。
也可以把修飾符放在變量前。
switch rectangle {
case .circle(var radious):
radious += 1
print("\(radious)")
case .rectangle(let width, var height):
height += 10
print("\(width) \(height)")
}
為了方便查看SIL
代碼,簡化下代碼
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
var circle = Shape.circle(radious: 10)
var temp: Double
var w: Int
var h: Int
switch circle {
case .circle(let radious):
temp = radious
case .rectangle(let width, var height):
w = width
h = height
}
對應sil
如下:
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @main.circle : main.Shape // id: %2
%3 = global_addr @main.circle : main.Shape : $*Shape // users: %9, %16
%4 = metatype $@thin Shape.Type
%5 = float_literal $Builtin.FPIEEE64, 0x4024000000000000 // 10 // user: %6
%6 = struct $Double (%5 : $Builtin.FPIEEE64) // user: %7
%7 = tuple $(radious: Double) (%6) // user: %8
//創建枚舉值,枚舉值關聯值是元組(%6),也就是10
%8 = enum $Shape, #Shape.circle!enumelt, %7 : $(radious: Double) // user: %9
//枚舉值連同關聯值給到%3
store %8 to %3 : $*Shape // id: %9
alloc_global @main.temp : Swift.Double // id: %10
%11 = global_addr @main.temp : Swift.Double : $*Double // user: %23
alloc_global @main.w : Swift.Int // id: %12
%13 = global_addr @main.w : Swift.Int : $*Int // user: %33
alloc_global @main.h : Swift.Int // id: %14
%15 = global_addr @main.h : Swift.Int : $*Int // user: %36
%16 = begin_access [read] [dynamic] %3 : $*Shape // users: %17, %18
//讀取%3
%17 = load %16 : $*Shape // user: %19
end_access %16 : $*Shape // id: %18
//跳轉bb1
switch_enum %17 : $Shape, case #Shape.circle!enumelt: bb1, case #Shape.rectangle!enumelt: bb2 // id: %19
// %20 // user: %21
bb1(%20 : $(radious: Double)): // Preds: bb0
//取出關聯值
%21 = tuple_extract %20 : $(radious: Double), 0 // users: %24, %22
//關聯值給到radious
debug_value %21 : $Double, let, name "radious" // id: %22
%23 = begin_access [modify] [dynamic] %11 : $*Double // users: %24, %25
store %21 to %23 : $*Double // id: %24
end_access %23 : $*Double // id: %25
br bb3 // id: %26
這也就解釋了為什么switch中能訪問到關聯值。
1.構建一個元組
2.根據當前枚舉值匹配case
3.取出元組值,聲明常量使用這個值初始化
只匹配一個case的情況,除了寫default的情況還可以通過if case
處理
if case let Shape.circle(radious) = circle {
print("\(radious)")
}
這里SIL
代碼和switch
差不多,原理相同。
多個case匹配
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
case square(width: Int, height: Int)
}
var rectangle = Shape.rectangle(width: 10, height: 10)
var square = Shape.square(width: 10, height: 20)
var temp: Int
switch rectangle {
case let .rectangle(10, x), let .square(10, x):
temp = x
default:
print("not found case")
}
對應的sil
(省略了main中的switch_enum相關邏輯,和上面的一樣):
// %28 // users: %30, %29
bb1(%28 : $(width: Int, height: Int)): // Preds: bb0
//取出關聯值 width
%29 = tuple_extract %28 : $(width: Int, height: Int), 0 // users: %31, %33
//取出關聯值 height
%30 = tuple_extract %28 : $(width: Int, height: Int), 1 // users: %36, %37
debug_value %29 : $Int, let, name "$match" // id: %31
//常量 10
%32 = integer_literal $Builtin.Int64, 10 // user: %34
//從結構體取 width
%33 = struct_extract %29 : $Int, #Int._value // user: %34
//比較 10 和 width
%34 = builtin "cmp_eq_Int64"(%32 : $Builtin.Int64, %33 : $Builtin.Int64) : $Builtin.Int1 // user: %35
//相等走bb2,否則走bb3
cond_br %34, bb2, bb3 // id: %35
bb2: // Preds: bb1
//將 height 給到 x
debug_value %30 : $Int, let, name "x" // id: %36
//bb6是將x給到temp
br bb6(%30 : $Int) // id: %37
取出case元組值,比較第一個元素,賦值第二個元素。
如果匹配的第一個元素是通配符呢?
switch rectangle {
case let .rectangle(_, x), let .square(_, x):
temp = x
default:
print("not found case")
}
可以看到直接取了height
賦值給x
了,這里就不需要檢查第一個元素了。
// %28 // user: %29
bb1(%28 : $(width: Int, height: Int)): // Preds: bb0
%29 = tuple_extract %28 : $(width: Int, height: Int), 1 // users: %30, %31
debug_value %29 : $Int, let, name "x" // id: %30
br bb3(%29 : $Int) // id: %31
對于一個case多個匹配的情況,常量/變量要匹配,如下:
對于.rectangle
有兩個參數,.square
有3個參數。
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
case square(width: Int, height: Int, length: Int)
}
var rectangle = Shape.rectangle(width: 10, height: 10)
var square = Shape.square(width: 10, height: 20, length: 30)
var temp: Int
switch square {
case let .rectangle(20, x), let .square(10, x, 30):
temp = x
print("\(temp)")
default:
print("not found case")
}
在上面.square
的第三個參數必須是一個確定的值,當然也可以第二個參數是確定值,第三個參數是x
switch square {
case let .rectangle(20, x), let .square(10, 10, x):
temp = x
print("\(temp)")
default:
print("not found case")
}
也就是說case里面的常量/變量每個case必須都有。
嵌套
如果一個復雜枚舉是由單個枚舉構成的,那么我們可以用嵌套來解決。
enum CombineDirect {
enum BaseDirect {
case up
case down
case left
case right
}
case leftUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
case rightUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
case leftDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
case rightDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
}
let combine = CombineDirect.leftUp(combineElement1: .left, combineElement2: .up)
當然在結構體中也可以嵌套enum:
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 .down,.up:
print("up, down")
}
}
}
實際上枚舉只是在不同作用于內,結構上沒有影響
enum屬性
enum
中能包含計算屬性(本身是方法)、類型屬性(本身是全局變量)。不能包含存儲屬性。(結構體能包含存儲屬性是因為結構體大小就是存儲屬性大小)
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
static var height = 20
// var width: Double
var width: Int {
get {
return 10
}
}
}
計算屬性要通過case
調用,類型屬性通過枚舉調用:
print(Shape.rectangle(width: 10, height: 10).width)
print(Shape.height)
enum方法
如果想知道星期枚舉的下一天:
enum WEEK: Int {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
func nextDay() -> WEEK {
if self == .SUNDAY {
return WEEK(rawValue: 0)!
} else {
return WEEK(rawValue: self.rawValue + 1)!
}
}
}
print(WEEK.SUNDAY.nextDay())
這樣做的好處是我們不需要在外面再專門定義一個方法了,直接在枚舉中就可以定義方法處理了。
- 可以定義實例方法和類型方法(static)
enum大小
rawValue枚舉大小
enum Hotpot {
case cat
}
var hotpot = Hotpot.cat
print(MemoryLayout.size(ofValue: hotpot))
print(MemoryLayout<Hotpot>.size)
print(MemoryLayout<Hotpot>.stride)//步長為1
0
0
1
可以看到size
為0
,stride
為1
。那么增加一個case
看下:
enum Hotpot {
case cat
case dog
}
print(MemoryLayout<Hotpot>.size)
print(MemoryLayout<Hotpot>.stride)
1
1
size
和stride
都為1
,那也就意味著enum
在內存存儲過程當中就是以1
個字節長度存儲的。
聲明一個變量打個斷點看一下:
var hotpot = Hotpot.cat
這里把
0x0
給到了[rip + 0x49c8]
,那么修改一下:
var hotpot = Hotpot.dog
變成了把
0x1
給到[rip + 0x49c8]
。這里對于默認的
enum
,case
是UInt8
也就是1
字節。最大255
,超過會自動變成UInt16
。驗證下:
enum Hotpot {
case cat1
……
case cat257
}
print(MemoryLayout<Hotpot>.size)
print(MemoryLayout<Hotpot>.stride)
2
2
斷點驗證:
var hotpot = Hotpot.cat257
0x100
也就是256
。從0
開始對應Hotpot.cat257
。
讀取一下內存地址:
var hotpot = Hotpot.cat1
var hotpot1 = Hotpot.cat2
var hotpot2 = Hotpot.cat3
var hotpot3 = Hotpot.cat4
枚舉數量小于256
:
(lldb) po withUnsafePointer(to: &hotpot){print($0)}
0x000000010000805c
0 elements
(lldb) memory read 0x000000010000805c
0x10000805c: 00 01 02 03 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x10000806c: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
枚舉數量大于256
:
(lldb) po withUnsafePointer(to: &hotpot){print($0)}
0x000000010000c05c
0 elements
(lldb) memory read 0x000000010000c05c
0x10000c05c: 00 00 01 00 02 00 03 00 00 00 00 00 00 00 00 00 ................
0x10000c06c: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
那么在內存中通過分配對應字節大小(這里分別是1和2),0x1、0x2……
都存儲在內存中。
- 如果枚舉有原始值,大小取決于case的多少。
- 枚舉類型其實標識的是rawValue的值,不是case的值。
- 對于只有一個case的情況是0,因為這個enum沒有意義,多于一個則有意義。
關聯值枚舉大小
只有一個case的情況:
enum Shape {
case circle(radious: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
8
8
多個case的情況:
enum Shape {
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
17
24
這里size
以rectangle
的大小為準8 (width)+ 8(height) + 1(case)
的大小為17
。
內存驗證下:
var rectangle = Shape.rectangle(width: 10, height: 20)
把rectangle
修改下:
enum Shape {
case circle(radious: Double)
case rectangle(width: Int)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
var rectangle = Shape.rectangle(width: 10)
size
為9
,補齊8 stride
變為16
。
- 有關聯值的枚舉大小取決于最大的那個case+1(這里1為case本身,如果只有一個case則不加1)。
嵌套枚舉大小
enum CombineDirect {
enum BaseDirect {
case up //00
case down //01
case left //02
case right //03
}
//0
case leftUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
//4
case rightUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
//8
case leftDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
//12
case rightDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
}
var combine = CombineDirect.leftDown(combineElement1: .left, combineElement2: .down)
print(MemoryLayout<CombineDirect>.size)
print(MemoryLayout<CombineDirect>.stride)
這里
02
和BaseDirect相關,例子中是.left
,81
中的1
(因為BaseDirect
占一字節)和第二個枚舉值相關.down 01
,系統進行了優化。8
代表case leftDown
。這里并不是遞增,如果
case
很多的話會從0、1、2、3、4開始,編譯器做了優化
- 本質上取決于
BaseDirect
枚舉的大小。
結構體中枚舉大小
struct Skill {
enum KeyType {
case up
case down
case left
case right
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
0
1
- 結構體中枚舉大小與只有一個case的情況相同(有關聯值的枚舉也一樣)
這里相當于結構體中沒有定義枚舉,相當于沒有。其實也就是結構體的大小。
內存對齊&字節對齊
內存對齊
8字節對齊
字節對齊
倍數對齊
struct Hotpot {
var age: Int //8
var height: UInt8 //1
var width: UInt16 //2
}
12
16
按理說應該是11
啊,調整下順序:
struct Hotpot {
var age: Int //8
var width: UInt16 //2
var height: UInt8 //1
}
print(MemoryLayout<Hotpot>.size)
print(MemoryLayout<Hotpot>.stride)
11
16
這里是因為編譯器做了優化,這里UInt8
編譯器做了優化,為了偶地址。
上面說的結構體,枚舉也一樣:
enum Hotpot {
//1
case cat
// 8 + 2 + 1 + 1(case)
case cat1(Int,UInt16,UInt8)
//8 + 2(偶地址) + 2 + 1(case)
case cat2(Int,UInt8,UInt16)
}
print(MemoryLayout<Hotpot>.size)
print(MemoryLayout<Hotpot>.stride)
13
16
- 計算
size
的時候要考慮進去偶地址 - 計算
size
的時候要考慮進去字節對齊
枚舉大小案例
enum Shape{
case circle(radious: UInt8)
//2 + 8 + 2 + 1 = 13
case rectangle(width: UInt8, height: Int, length: UInt16)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
18
24
按照前面所說應該是2 + 8 + 2 + 1 = 13
,這里由于需要8
字節對齊,所以應該是8 + 8 + 2 + 1 = 19
為什么是18
呢?
編譯器在這里做了優化
var shape = Shape.rectangle(width: 1, height: 2, length: 3)
memory read這里是小端,驗證了
width
占了8字節,case1
字節放在了0x80
,這個是枚舉值的標志位。相當于對case做了優化。rectangle
換個順序看下:
case rectangle(width: UInt8,length: UInt16, height: Int)
應該是(2 + 2)(這里補齊到8) + 8 + 1(優化到前面) = 16
16
16
可以看到
width
、height
和case
存在了前8字節上。01
存儲的就是width
偶地址補位為0001
,0002
存儲的就是length
補3字節000000 0002
,剩余1字節存放case``ox80
。
枚舉大小總結
-
rawValue
的枚舉大小默認是1
字節(UInt8
),case超過256
個會UInt8->UInt16->UInt32……
。 - 關聯值枚舉大小與最大
case
關聯值相關,需要加1
(case
)和考慮偶地址
以及字節對齊。
indirect
如果enum
是一個復雜的數據結構,可以通過indirect
讓enum
簡潔,比如要用結構體寫一個鏈表:
enum List<Element> {
case end
case node(Element,next:List<Element>)
}
這樣寫直接報錯:
系統要求增加
indirect
關鍵字。由于
enum
是值類型,大小在編譯期就確定了,在上面的例子中List
大小并不能確定(由于case中有List)。官方原文:
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 BinaryTree<T> {
case empty
indirect case node(left: BinaryTree,value:T, right:BinaryTree)
}
var node = BinaryTree<Int>.node(left: BinaryTree<Int>.empty, value: 10, right: BinaryTree<Int>.node(left: BinaryTree<Int>.empty, value: 20, right: BinaryTree<Int>.empty))
print(MemoryLayout.size(ofValue: node))
print(MemoryLayout.stride(ofValue: node))
indirect
也可以放在枚舉之前:
indirect enum BinaryTree<T> {
case empty
case node(left: BinaryTree,value:T, right:BinaryTree)
}
8
8
可以看到大小都是8
,讀取下內存:
看內存結構像是
HeapObject
,分析下SIL
:alloc_box
sil-instruction ::= 'alloc_box' sil-type (',' debug-var-attr)* %1 = alloc_box $T // %1 has type $@box T
Allocates a reference-counted @box on the heap large enough to hold a value of type T, along with a retain count and any other metadata required by the runtime. The result of the instruction is the reference-counted @box reference that owns the box. The project_box instruction is used to retrieve the address of the value inside the box.
The box will be initialized with a retain count of 1; the storage will be uninitialized. The box owns the contained value, and releasing it to a retain count of zero destroys the contained value as if by destroy_addr. Releasing a box is undefined behavior if the box's value is uninitialized. To deallocate a box whose value has not been initialized, dealloc_box should be used.
大概意思是在堆空間上分配了一塊內存區域,內存區域用來存放value
,在例子中也就是Int
值。在取值的時候取box
,box
是把value
包裹了一層,也就是取的是地址。
alloc_box
本質是swift_allocObject
,所以indirect
就是通知編譯器當前的枚舉是遞歸的,大小自然無法確定,所以分配堆空間存放。
兩種方式的比較:
indirect enum BinaryTree<T> {
case empty
case node(left: BinaryTree,value:T, right:BinaryTree)
}
enum BinaryTree1<T> {
case empty
indirect case node(left: BinaryTree1,value:T, right:BinaryTree1)
}
var node = BinaryTree<Int>.node(left: BinaryTree<Int>.empty, value: 10, right: BinaryTree<Int>.empty)
var node1 = BinaryTree<Int>.empty
var node2 = BinaryTree1<Int>.node(left: BinaryTree1<Int>.empty, value: 10, right: BinaryTree1<Int>.empty)
var node3 = BinaryTree1<Int>.empty
node
node1
node2
node3
indrect
關鍵字的本質把對應case的值放在了堆空間上。- 通知編譯器當前的枚舉是遞歸的,分配堆空間存放。
- 放在
case
前這個case
是引用類型,在enum
前整個枚舉類型都用引用類型存儲。
Swift與OC枚舉混編
-
Swift
枚舉非常強大,可以添加方法、extension、計算屬性、類型屬性。 -
OC
枚舉僅僅是一個整形值。
Swift枚舉暴露給OC
- @objc標記
- 枚舉應該是Int類型
@objc enum WEEK: Int {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
暴露給OC的是:
typedef SWIFT_ENUM(NSInteger, WEEK, closed) {
WEEKMONDAY = 0,
WEEKTUEDAY = 1,
WEEKWEDDAY = 2,
WEEKTHUDAY = 3,
WEEKFRIDAY = 4,
WEEKSATDAY = 5,
WEEKSUNDAY = 6,
};
oc中調用
#import "SwiftEnum-Swift.h"
WEEK week = WEEKMONDAY;
那么對于非Int類型的枚舉呢?
1.通過swift中包裝一層,也就是Int枚舉,只不過Swift中封裝個方法調用。對OC來說轉換的只是case,沒有方法。
@objc public enum WEEK: Int {
case MONDAY
case TUEDAY
case WEDDAY
func weekName() -> String {
switch self {
case .MONDAY: return "MONDAY"
case .TUEDAY: return "TUEDAY"
case .WEDDAY: return "WEDDAY"
}
}
}
swift中調用
var week = WEEK.MONDAY.weekName()
oc中調用
WEEK week = WEEKMONDAY;
2.通過class封裝,這里相當于把class共享給OC了,自然能訪問到enum。這個時候通過class getName
方法 OC
也能讀取rawValue
class WeekName: NSObject {
@objc enum WEEK : Int {
case MONDAY
case TUEDAY
case WEDDAY
var weekName: String {
return WeekName.getName(self)
}
}
@objc class func getName(_ fieldName:WEEK) -> String {
switch fieldName {
case .MONDAY: return "MONDAY"
case .TUEDAY: return "TUEDAY"
case .WEDDAY: return "WEDDAY"
}
}
}
轉換如下:
enum WEEK : NSInteger;
SWIFT_CLASS("_TtC9SwiftEnum8WeekName")
@interface WeekName : NSObject
+ (NSString * _Nonnull)getName:(enum WEEK)fieldName SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
typedef SWIFT_ENUM(NSInteger, WEEK, closed) {
WEEKMONDAY = 0,
WEEKTUEDAY = 1,
WEEKWEDDAY = 2,
};
swift中調用
var week = WeekName.WEEK.MONDAY. weekName
oc中調用
WEEK week = WEEKMONDAY;
NSString *str = [WeekName getName:week];
在這里如果不想swift中使用Int枚舉,可以重寫rawValue
,這里需要注意不能private
,可以直接assert
讓debug
下crash
Property 'rawValue' must be declared public because it matches a requirement in public protocol 'RawRepresentable'
public var rawValue: Int {
assert(false, "This is a string enum,please use weekName()")
return 0
}
swift調用OC枚舉
NS_ENUM枚舉
在oc中使用NS_ENUM
定義的枚舉都會自動轉化為swift
中的枚舉
typedef NS_ENUM(NSUInteger, OCEnum) {
value1,
value2
};
自動轉換代碼:
public enum OCEnum : UInt {
case value1 = 0
case value2 = 1
}
在swift中調用需要在bridging中導入頭文件:
//SwiftEnum-Bridging-Header.h中
#import "OCTest.h"
//調用:
var ocEnum = OCEnum.value1
非NS_ENUM枚舉
不是NS_ENUM
宏定義的枚舉,或者用NS_OPTIONS定義的枚舉:
typedef enum {
num1,
num2
} OCNum;
可以看到轉換成了結構體:
public struct OCNum : Equatable, RawRepresentable {
public init(_ rawValue: UInt32)
public init(rawValue: UInt32)
public var rawValue: UInt32
}
open class OCTest : NSObject {
}
swift調用:
var ocNumEnum = OCNum.init(rawValue: 1)
print(ocNumEnum)
輸出
OCNum(rawValue: 1)
c的enum
typedef NS_ENUM(NSInteger, CEnum) {
CEnumInValid = 0,
CEnumA = 1,
CEnumB = 2,
CEnumC = 3,
};
轉換后代碼
public enum CEnum : Int {
case inValid = 0
case A = 1
case B = 2
case C = 3
}
調用
var cEnum = CEnum.A
cEnum = CEnum.init(rawValue: 10)!
print(cEnum.rawValue)
10
對比swift
的enum
var swiftEnum = WEEK.init(rawValue: 10)
print(swiftEnum?.rawValue)
nil
對比可知,在swift
中調用oc/c
的枚舉,對于不存在的case
會返回rawValue
,對于純swift
枚舉返回nil
。