與C
,Objective-C
中的枚舉相比,Swift
中枚舉
功能更強大
。它支持很多只有類
才有的特性,如:Properties
、Methods
、Initialization
、 Extensions
、Protocols
...
-
C語言枚舉的寫法
只支持Int
一種類型:
// WEEK:枚舉名,可省略
// MON:枚舉成員
// week:定義枚舉變量
enum WEEK {
MON, TUE, WED, THU, FRI, STA, SUN
} week;
-
Swift枚舉寫法
支持整型(Integer)
、浮點數(Float Point)
、字符串(String)
、布爾類型(Boolean)
四種基本類型,如果想要支持其它自定義
的類型,需實現 StringLiteralConvertible
協議:
enum WEEK {
case MON, TUE, WED, THU, FRI, STA, SUN
}
// 或者
enum WEEK {
case MON
case TUE
case WED
case THU
case FRI
case STA
case SUN
}
通過查看變量的內存可知:Swift
的枚舉成員
case是一個整型值
即它所在的index
,且只占1
個字節(UInt8
)。
若標明類型String
,則表示rawValue
是String
類型,而不
是case成員
的類型。
enum WEEK : String {
case MON = "MON"
case TUE = "TUE"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case STA = "STA"
case SUN = "SUN"
}
case
后面的"MON"
就是枚舉值
; "="
后面的"MON"
是rawValue
(原始值 )
Swift
中隱式rawValue分配
:不寫"="及后面的字符串,即:
enum WEEK : String {
case MON
case TUE
case WED
case THU
case FRI
case STA
case SUN
}
-
定義枚舉變量
var w = WEEK.MON
一旦w
的類型被聲明為WEEK
,則可以使用一個縮寫語法(.)
將其設置為WEEK
的值,即:
var w : WEEK = .TUE
標明類型
后,可以通過w.MON.rawValue
獲取原生值rawValue
,本質是調用rawValue
的get()
;未標明類型則不能獲取rawValue。
// 前提是標明了rawValue的類型,才能獲取rawValue
var w = WEEK.MON.rawValue
通過SIL看一下區別:這里標明
的類型是Int
未標明
類型的SIL
:
可知標明類型能調用rawValue
的本質是:rawValue
是一個計算屬性
,通過調用其get()
獲取該屬性的值
;而在rawValue
的get()
中,rawValue
是Int
類型時默認從0
開始,是String
類型時默認是枚舉成員本身
的字符串:
那么當類型為String
時,get()
中構建的字符串存放
在哪里呢?
可知:隱式rawValue字符串
在編譯過程
中就已經存放在MachOView
文件中的__Text,__cstring
中,且通過字符串的地址可知占用的是一段連續的內存空間
。
-
枚舉
變量
和rawValue
的區別
print(WEEK.THU) //THU
print(WEEK.THU.rawValue) //THU
在這里,雖然打印出來的都是THU
,但需要區分的是WEEK.THU
是WEEK
類型的,而WEEK.THU.rawValue
是String
類型的。
-
枚舉中
init?( )
- 先來看看什么情況下才會
調用
枚舉的初始化
方法?
可知只有通過WEEK(rawValue:)
這種方式才會調用到init()
方法。
- 分析枚舉的初始化方法:
_allocateUninitializedArray
:創建一個元組
第一個元素:與枚舉個數大小一樣的數組
,用于存儲枚舉中的rawValue
,在本例中是StaticString
--->Array<StaticString>
第二個元素:數組的首地址
--->BuildIn.RawPointer
_findStringSwitchCase
:查找指定枚舉值
在數組中的位置
,返回index
。
從0
到count-1
依次與index
作比較:
- 如果
相等
則構建
對應的枚舉
- 如果
不相等
則構建一個Optional.none!enumelt
的枚舉
-
關聯值
enum Shape {
case circle(radius:Double)
case rectangle(length:Int, width:Double, height:Int)
}
// 創建枚舉的關聯值
var shape = Shape.circle(radius: 3.00)
// 重新分配關聯值
shape = Shape.circle(radius: 2.0)
此時的枚舉值
不再有rawValue
,也沒必要(rawValue是單個值),而關聯值
可以是一組值
。
通過SIL
也可以看出:enum
中不
再有初始化
方法以及計算屬性rawValue
。
關聯值的標簽可省略
,只寫類型(不推薦,可讀性差
),如:
enum Shape {
case circle(Double)
case rectangle(Int, Double, Int)
}
-
枚舉的用法:模式匹配
- 簡單用法
enum WEEK : String {
case MON
case TUE
case WED
case THU
case FRI
case STA
case SUN
}
var currentDay : WEEK = WEEK.SUN
var str : String
switch currentDay {
case .STA, .SUN:
str = "Happy Day"
default:
str = "Sad Day"
}
SIL
的實現:通過匹配case
來做相應的代碼跳轉。
2.復雜用法:匹配關聯值
可以通過SIL文件知道:
-
case let .circle(radius):
中的let
表示關聯值
是一個常量
,不可被修改;也可換一種寫法case .circle(let radius):
。 -
case .rectangle(let length, var width) :
中var width
表示width
是一個變量
,可以被修改;如果關聯值width
用不到則可以用_
代替,即case .rectangle(let length, _) :
。 - 若只想匹配單個
case
可以:
if case let Shape.circle(radius) = shape {
tempValue = radius
}
- 如果只關心
不同的case
的相同關聯值
,可以這樣寫:
enum Shape {
case circle(radius:Double)
case rectangle(length:Double, width:Double)
case square(length:Double, width:Double)
}
var rectangle = Shape.rectangle(length:5, width: 3)
var square = Shape.square(length: 9, width: 5)
var tempValue : Double
switch square {
case let .rectangle (5, x), let .square(x, 5):
tempValue = x
default :
tempValue = 0.0
}
// 若switch rectangle {..}則tempValue=3
// 若switch square {..}則tempValue=9
還可以使用通配符
的方式:
case let .rectangle (_, x), let .square(x, _):
print(x)
// 若switch rectangle {..}則 3
// 若switch square {..}則 9
//或者
case let .rectangle (x, y), let .square(y, x):
print(x,y)
// 若switch rectangle {..}則 (5,3)
// 若switch square {..}則 (5,9)
底層實現還是通過匹配case
,匹配成功
后取出元組中x
所在的元素,將該元素
賦值給x
,再將元素值
賦值給tempValue
:
-
枚舉的嵌套
- 枚舉中嵌套枚舉
enum CombineDirect {
enum BaseDirect {
case up
case down
case left
case right
}
case leftUp(direct1:BaseDirect, direct2:BaseDirect)
case rightUp(direct1:BaseDirect, direct2:BaseDirect)
case leftDown(direct1:BaseDirect, direct2:BaseDirect)
case rightDown(direct1:BaseDirect, direct2:BaseDirect)
}
var direct = CombineDirect.leftDown(direct1: .left, direct2: .right)
- 結構體中嵌套枚舉
struct Skill {
enum Direct {
case up
case down
case left
case right
}
var direct : Direct
func launchSkill() {
switch direct {
case .left, .right:
print("控制方向")
case .up, .down:
print("移動距離")
}
}
}
-
枚舉中包含屬性
枚舉本身是值
類型,只能包含計算
屬性(只有方法,不存儲在enum中)和類型
屬性(也不存儲在enum中),不
能包含存儲
屬性。
注意:結構體
可以有存儲
屬性,結構體大小就是存儲屬性
的大小。
-
枚舉中包含方法
enum WEEK : Int {
case MON
case TUE
case WED
case THU
case FRI
case STA
case SUN
func nextDay()-> WEEK{
switch self {
case .SUN:
return .MON
default:
return WEEK(rawValue: self.rawValue + 1) ?? self
}
}
}
-
迭代枚舉
如果需要迭代枚舉中的所有值時,需要自定義的枚舉遵守CaseIterable
協議,通過獲取enum的allCases
屬性獲取所有值:
enum FlowerType: CaseIterable {
case Rose
case Orchid
case Peony
}
let numOfFlowerType = FlowerType.allCases.count
for flower in FlowerType.allCases {
print(flower)
}
//Rose
//Orchid
//Peony
-
枚舉中可使用協議
protocol CustomDesc {
var description : String { get }
}
enum FlowerType: CustomDesc {
case Rose
case Orchid
case Peony
var description: String {
switch self {
case .Rose:
return "Rose"
case .Orchid:
return "Orchid"
case .Peony:
return "Peony"
}
}
}
-
枚舉可擴展
枚舉通過擴展
將case
(放在枚舉中)和方法
(放在枚舉的擴展中)分離
:
enum FlowerType {
case Rose
case Orchid
case Peony
}
extension FlowerType {
func introduced() -> String {
switch self {
case .Rose:
return "Rose"
case .Orchid:
return "Orchid"
case .Peony:
return "Peony"
}
}
}
-
枚舉可使用泛型
枚舉通過泛型參數
定義以適應枚舉中的關聯值
(可有多個泛型參數),拿Swift標準庫中的Optional
類型為例:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
let aValue = Optional<Int>.some(5)
let noValue = Optional<Int>.none
if noValue == Optional.none { print("No value")
-
枚舉的大小
1.只有一個case的簡單枚舉(其實無意義):
enum test {
case a
}
print(MemoryLayout<test>.size) //0
print(MemoryLayout<test>.stride) // 1
- 有多個case的
enum test {
case a
case b
case c
case d
}
print(MemoryLayout<test>.size) // 1
print(MemoryLayout<test>.stride) // 1
enum
其實就是以1字節
的長度存儲在內存
中即UInt8
,當case個數
超過UInt8的最大值255
個時,編譯器自動將enum的內存大小擴
為UInt16
。也可在匯編模式下看出:每次移動1
個字節。
總結:原始值的enum大小
取決于case的數量
,與rawValue
的類型
無關,case超不過255個就是 UInt8
--->1字節,如果超過則自動升級成UInt16
。
- 有
關聯值
的枚舉:大小取決于case中最大內存
的大小以及字節對齊
PS:是否+1還沒弄透徹,下次弄明白了再補上...
,這里先記錄一下自測總結出來的結果,可能有誤...(可略過
)
猜想1: enum
中除
去占最大
內存的case
外:
有占大于等于最大關聯值類型a(>8視為8,<8就是a本身)
的case的話,size就需要在對齊基礎上+1
(case的大?。?br>
小于最大關聯值類型a
字節則不
需要+1
。
如
enum Shape {
case circle(radius:UInt8) //1
case rectangle(length:UInt8, width:Int, height:UInt16) // 1 8 2
case square(width:Int32, height:Int32) // 4 4
} // size : 19
enum Shape {
case circle(radius:UInt8)
case rectangle(length:UInt8, width:Int, height:UInt16)
case square(width:UInt8, height:UInt16)
} // size : 18
size:除去最大的rectangle
(字節對齊的原則上其大小為8 + 8 + 2 = 18),circle
和square
中只要任一個所占內存大于等于enum中的最大類型(這里所有的case中最大的是Int為8字節),該enum的size
大小都需要在對齊基礎
上+ 1
(case的大小);
這里circle只占了1個字節,而在上面的enum中square占4 + 4 等于8字節,所以需要+1 = 19,下面的enum中square占1 + 2 = 3小于8字節,不需要+1。
猜想2: case數量>1的前提下, 最大case只有一個關聯值時始終+1。
stride
:為了字節對齊
(空間換取時間,提高效率),需要將實際大小
補齊到最大
所占內存大小(這里是Int-->8)的倍數
,所以19補齊成8的倍數即3 * 8 = 24
- 枚舉嵌套枚舉的大小
比較特殊
:取決于內層枚舉的大小
及字節對齊
5.嵌套枚舉的結構體
的大小 :取決于存儲屬性
的內存大?。ㄓ袥]有方法
都一樣,因為方法不
存在結構體
中)
有存儲屬性時size為1、stride為1
無存儲屬性時size為0、stride為1 (空結構體也是size為0、stride為1 )
- 區分
內存對齊
和字節對齊
:
內存對齊
:64位下8字節對齊,分配對象
時用到內存對齊。
字節對齊
:對齊的目的就是地址要從偶地址
開始,成員變量的起始位
要從該變量內存大小的倍數
開始。
下面兩個結構體
,不同類型的成員變量交換位置
都會導致結構體的內存
大小不一致
。
struct A {
var height : Double //8
var count : UInt16 //2
var age : UInt8 //1
}
print(MemoryLayout<A>.size) //11
print(MemoryLayout<A>.stride) //16
和
struct B {
var height : Double //8
var age : UInt8 //1
var count : UInt16 //2
}
print(MemoryLayout<B>.size) //12
print(MemoryLayout<B>.stride) //16
OC的結構體內存對齊規則如下:
struct的第一個數據成員要從偏移量offset為0的位置開始,后面的其他成員的起始的位置要從該成員類型的字節大小(Swift中>8的視為8)的整數倍開始
步長是最大成員類型(Swift中>8的視為8)的整數倍
若一個結構體包含另一個結構體的成員,結構體成員要從自身結構體中最大類型(Swift中>8的視為8)的整數倍開始
-
indirect關鍵字
- 使用場景:用
遞歸
的方式表達
想要的數據結構
上面圖片中是一個遞歸枚舉
,若不用indirect
關鍵字聲明編譯器會報錯
,所以下面case
需要用indirect
關鍵字聲明:
原因:普通枚舉
的大小
在編譯時期就確定
好的,而這里的遞歸枚舉
的大小
在編譯時是未知
的,所以需要用indirect
關鍵字來說明需要在堆上
分配一塊內存空間來存放,當前case
會使用引用類型
存儲。
- 本質:在
堆
上分配一塊內存
,存儲一個指向case的值
的地址
定義一個簡單的遞歸枚舉變量,分析SIL
中indirect
關鍵字到底做了些什么事情?
var list = List<Int>.node(4, List<Int>.end)
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @main.list : main.List<Swift.Int> // id: %2
%3 = global_addr @main.list : main.List<Swift.Int> : $*List<Int> // user: %16
%4 = metatype $@thin List<Int>.Type
// 構建Int類型的值4
%5 = integer_literal $Builtin.Int64, 4 // user: %6
%6 = struct $Int (%5 : $Builtin.Int64) // user: %13
%7 = metatype $@thin List<Int>.Type
%8 = enum $List<Int>, #List.end!enumelt // user: %14
// 分配堆空間存儲metadata、refCount、value(List<Int>.node(T, List<Int>)這個case的值(是一個元組))
%9 = alloc_box $<τ_0_0> { var (τ_0_0, List<τ_0_0>) } <Int> // users: %15, %10
// 取出%9(類似對象)里面value的地址 *(Int, List<Int>)
%10 = project_box %9 : $<τ_0_0> { var (τ_0_0, List<τ_0_0>) } <Int>, 0 // users: %12, %11
// 第一個元素的地址 *Int
%11 = tuple_element_addr %10 : $*(Int, List<Int>), 0 // user: %13
// 第二個元素的地址 *List<Int>
%12 = tuple_element_addr %10 : $*(Int, List<Int>), 1 // user: %14
// 將4存入元組中第一個元素的地址里
store %6 to %11 : $*Int // id: %13
// 將List<Int>.end存入元組中第二個元素的地址里
store %8 to %12 : $*List<Int> // id: %14
// 構建一個枚舉 List<Int>.node %9--->List<Int>.node(4, List<Int>.end)
%15 = enum $List<Int>, #List.node!enumelt, %9 : $<τ_0_0> { var (τ_0_0, List<τ_0_0>) } <Int> // user: %16
store %15 to %3 : $*List<Int> // id: %16
%17 = integer_literal $Builtin.Int32, 0 // user: %18
%18 = struct $Int32 (%17 : $Builtin.Int32) // user: %19
return %18 : $Int32 // id: %19
} // end sil function 'main'
alloc_box
:在堆
空間上分配
了內存
區域存放
T類型的value
,box相當于在value外面包裹了一層;project_box
:取出來的是value的地址
;可通過斷點看到調用了swift_allocObject
:
通過上面SIL分析可知:indirect
關鍵字的本質
就是在堆
上分配一塊內存
來存儲一個引用地址
,該地址中存放的是被indirect修飾的case
的值
。
-
indirect
關鍵字修飾enum
:表明整個enum
類型都是以引用類型
來存儲。
先看看未
加indirect
修飾enum
時,其case
的內存結構:case
里面直接存放
的是關聯值
9;
下面是加indirect
修飾enum
時,其case
的內存結構:case
里面存放
的是一個引用地址
。
-
Swift和OC枚舉混編
- OC訪問Swift中的枚舉
- enum要用
@objc
修飾 - 必須將
rawValue
的類型聲明成Int
類型(因為OC
中的枚舉就是的整型
值)
@objc enum Week : Int {
case MON
case TUE
case WED
case THU
case FRI
case STA
case SUN
}
然后在OC-Swift.h橋接文件
中,該enum
就已存在:
typedef SWIFT_ENUM(NSInteger, Week, closed) {
WeekMON = 0,
WeekTUE = 1,
WeekWED = 2,
WeekTHU = 3,
WeekFRI = 4,
WeekSTA = 5,
WeekSUN = 6,
};
在OC
文件中就可以直接使用
:
- OC訪問String類型的enum
// .swift文件中封裝String類型的enum
@objc class Week : NSObject {
@objc enum WeekInt : Int {
case MON, TUE, WED, THU, FRI, STA ,SUN
var string : String {
return Week.getName(weekValue: self)
}
}
@objc class func getName(weekValue:WeekInt)->String {
switch weekValue {
case .MON: return "MON"
case .TUE: return "TUE"
case .WED: return "WED"
case .THU: return "THU"
case .FRI: return "FRI"
case .STA: return "STA"
case .SUN: return "SUN"
}
}
}
// OC-Swift.h橋接文件
enum WeekInt : NSInteger;
@class NSString;
SWIFT_CLASS("_TtC8YYOCTest4Week")
@interface Week : NSObject
+ (NSString * _Nonnull)getNameWithWeekValue:(enum WeekInt)weekValue SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
typedef SWIFT_ENUM(NSInteger, WeekInt, closed) {
WeekIntMON = 0,
WeekIntTUE = 1,
WeekIntWED = 2,
WeekIntTHU = 3,
WeekIntFRI = 4,
WeekIntSTA = 5,
WeekIntSUN = 6,
};
// OC中調用
NSString *weekStr = [Week getNameWithWeekValue:WeekIntFRI];
NSLog(@"%@", weekStr); // FRI
// Swift中調用
var weekStr = Week.WeekInt.STA.string
print(weekStr) //STA
- Swift訪問OC中的枚舉
OC中的枚舉會被自動轉換
成Swift的enum。
1.typedef NS_ENUM
方式聲明的枚舉
//OC .h文件中定義的枚舉
typedef NS_ENUM(NSInteger, YYSTATE){
Invalid = -1,
Failed,
Success
};
// 自動轉換成Swift的枚舉:(在系統自動生成的Swift文件中)
public enum YYSTATE : Int {
case Invalid = -1
case Failed = 0
case Success = 1
}
// .swift文件中使用enum:
var state = YYSTATE.Success
print(state.rawValue) // 1
//轉換后的enum的大小與步長:
print(MemoryLayout<YYSTATE>.size) //8
print(MemoryLayout<YYSTATE>.stride) //8
-
NS_ENUM
方式聲明的枚舉
// OC .h文件中定義枚舉
NS_ENUM(NSInteger, YYSTATE){
Invalid = -1,
Failed,
Success
};
//自動轉換后的枚舉:
public var YYSTATE: YYSTATE
public enum YYSTATE : Int {
case Invalid = -1
case Failed = 0
case Success = 1
}
// .swift中使用枚舉:
var state = YYSTATE.Success
print(state.rawValue) // 1
//轉換后的enum的大小與步長:
print(MemoryLayout<YYSTATE>.size) //8
print(MemoryLayout<YYSTATE>.stride) //8
-
typedef enum
方式聲明的枚舉
// .h中定義枚舉
typedef enum {
YYSTATEInvalid = -1,
YYSTATEFailed,
YYSTATESuccess
}YYSTATE;
//自動轉換后的枚舉:
public struct YYSTATE : Equatable, RawRepresentable {
public init(_ rawValue: Int32)
public init(rawValue: Int32)
public var rawValue: Int32
}
//.swift中使用枚舉:
var state = YYSTATESuccess
print(state.rawValue) //1
//轉換后的enum的大小與步長:
print(MemoryLayout<YYSTATE>.size) //4
print(MemoryLayout<YYSTATE>.stride) //4