Swift中的枚舉有原始值和關(guān)聯(lián)值,其使用范圍相比OC來說多了很多,因此也更復(fù)雜,需要我們花更多的時間來學(xué)習(xí)它,本文是對學(xué)習(xí)后和在實際中的運(yùn)用做的一個總結(jié)。
目前來看需要掌握的內(nèi)容如下:

一、Swift枚舉聲明
>>> A. 枚舉的類型及原始值、關(guān)聯(lián)值
- 聲明枚舉時,類型繼承列表(即冒號后面)寫原始值類型,不寫時默認(rèn)是
Int,如果是其它類型則需要寫明。
- 聲明枚舉時,類型繼承列表(即冒號后面)寫原始值類型,不寫時默認(rèn)是
- 原始值代表的是一個枚舉變量的
rawValue,rawValue的本質(zhì)是枚舉的計算屬性。
- 原始值代表的是一個枚舉變量的
- 在寫枚舉的case時:
如果原始值類型是Int、String類型,不寫明原始值會自動生成原始值, 對于Int類型默認(rèn)從0開始,下一個是上一個case的原始值+1;對于String類型,默認(rèn)原始值是case名;
如果是其它原始值類型的枚舉,可以用"="寫明對應(yīng)的原始值,見下面的Direction.
- 在寫枚舉的case時:
- 每個case后面可以追加枚舉關(guān)聯(lián)值,比如下面的
Score.
- 每個case后面可以追加枚舉關(guān)聯(lián)值,比如下面的
-
遞歸枚舉:如果枚舉關(guān)聯(lián)值中有枚舉本身類型,則這個時候在枚舉定義前或者
case前需要加indirect。
-
遞歸枚舉:如果枚舉關(guān)聯(lián)值中有枚舉本身類型,則這個時候在枚舉定義前或者
- 枚舉變量初始化時使用一般使用
enumName.caseName。
- 枚舉變量初始化時使用一般使用
- 枚舉默認(rèn)遵守
RawRepresentable協(xié)議,協(xié)議里面有可失敗初始化器init?(rawValue: Self.RawValue)和計算屬性rawValue. 所以我們可以通過這個協(xié)議的方法來完成初始化和獲取原始值rawValue。
- 枚舉默認(rèn)遵守
- 枚舉的原始值類型必須是可以用字面量表示的,比如
Int、Float、String等, 不然的話會報錯Raw value for enum case must be a literal.
- 枚舉的原始值類型必須是可以用字面量表示的,比如
- 枚舉的case不能即寫明原始值又寫關(guān)聯(lián)值,寫了報錯
Enum with raw type cannot have cases with arguments
- 枚舉的case不能即寫明原始值又寫關(guān)聯(lián)值,寫了報錯
// 1.這里的原始值類型是Character,如果不寫明= "d"則會報錯.
// 2.如果是不寫原始值類型Character,默認(rèn)是Int。
// 3.對于原始值類型是Int或者String,不寫明“=”后面的也不會報錯。
enum Direction: Character {
case north = "d"
case south = "s"
case east = "e"
case west = "w"
}
// 帶枚舉關(guān)聯(lián)值的枚舉
enum Score {
case point(Int, Int, Int)
case grade(Character)
}
// case中關(guān)聯(lián)值有自身枚舉類型的遞歸枚舉
indirect enum ArithExpr {
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr, ArithExpr)
}
>>> B. 枚舉的其它性質(zhì)
Swift中枚舉可以遵守協(xié)議,可以添加成員方法、靜態(tài)方法、靜態(tài)變量、計算屬性。
enum BasketballNum4: Character, CaseIterable {
case AAAA16 = "2"
// 可添加計算屬性
var name: String { "dandy" }
// 可添加靜態(tài)變量
static var name2: String { "dandy.static" }
// 可添加成員方法
func testSelf() {
print(self.name)
print(Self.name2)
}
// 可添加靜態(tài)方法
static func testSelf2() {
print(self.name2)
}
}
二、枚舉變量的內(nèi)存
弄清楚枚舉的內(nèi)存使用情況能讓我們在使用時對性能消耗有所了解。
使用MemoryLayout可以查看分配內(nèi)存和使用內(nèi)存情況,比如下面的方式:
print("MemoryLayout<String>.size", MemoryLayout<String>.size)
print("MemoryLayout<String>.stride=", MemoryLayout<String>.stride)
print("MemoryLayout<String>.alignment=", MemoryLayout<String>.alignment)
2.1 關(guān)聯(lián)值的存儲
- 如果沒有枚舉關(guān)聯(lián)值時,枚舉變量只占一個字節(jié),這個字節(jié)里面的內(nèi)容就是
case的序號。 - 如果有枚舉關(guān)聯(lián)值,那么枚舉內(nèi)存分配 = 各個關(guān)聯(lián)值類型所占的字節(jié)總和 + 1(這1個字節(jié)是存放枚舉case序號的)+ 根據(jù)內(nèi)存對齊
alignment補(bǔ)齊字節(jié)。 - 內(nèi)存對齊
alignment是關(guān)聯(lián)值類型中最大的那個。
證明過程:
我這里是學(xué)習(xí)李明杰大師的方法做的,使用他的demo就可以實現(xiàn):https://github.com/CoderMJLee/Mems
打開demo --》在main文件頂部寫showEnum() --》運(yùn)行程序 --》打印枚舉變量地址 --》如圖打開內(nèi)存面板,輸入這個變量的地址值 --》 即可查看該內(nèi)存里面的值。
枚舉關(guān)聯(lián)值的存儲.jpg
2.2 原始值的存儲
rawValue是一個計算屬性,在編譯時就已經(jīng)確定了每個枚舉值對應(yīng)的rawValue值,不需要存儲。證明方法: 可以分析swift文件編譯過程生成的中間文件sil。
可以參考文章:Swift進(jìn)階(六)枚舉和可選類型
2.3 枚舉中方法、計算屬性在內(nèi)存中什么位置?
- 計算屬性也可以認(rèn)為是方法,方法的本質(zhì)就是函數(shù), 方法、函數(shù)都存放在代碼段,所以計算屬性、成員方法都存放在代碼段。
- 靜態(tài)變量就是全局變量,存放在內(nèi)存的數(shù)據(jù)段。
- 靜態(tài)方法存放在代碼段。
證明方法見博客Swift 方法及方法在內(nèi)存中的位置
- 靜態(tài)方法存放在代碼段。
三、Swift枚舉應(yīng)用舉例
- 跟OC語言枚舉一樣的情況,比如訂單狀態(tài)枚舉。
- 應(yīng)用于將多個相似的方法整合成一個,比如:RxSwift中的信號發(fā)送。
// 在OC中是分別有onNext、onComplete、onError三個方法,RxSwift的內(nèi)部中轉(zhuǎn)方法只使用一個on
func on(_ event: Event<Element>)
public enum Event<Element> {
/// Next element is produced.
case next(Element)
/// Sequence terminated with an error.
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
- 系統(tǒng)的可選類型也是一個枚舉??蛇x類型可以賦值為nil,相當(dāng)于賦值了
Optional.none, 原因是遵守了ExpressibleByNilLiteral協(xié)議。
- 系統(tǒng)的可選類型也是一個枚舉??蛇x類型可以賦值為nil,相當(dāng)于賦值了
- 將APP所有的通知名集中到一個枚舉中,枚舉原始值類型聲明為
String,這樣在使用直接用點語法找case,而且APP中包含哪些通知也變得一目了然。
- 將APP所有的通知名集中到一個枚舉中,枚舉原始值類型聲明為
- 埋點神策事件時使用枚舉關(guān)聯(lián)值來做,代碼設(shè)計感會很不錯。
- 網(wǎng)絡(luò)請求使用
Moya來實現(xiàn)的話,一個請求對應(yīng)相關(guān)聯(lián)參數(shù),這個也可以用枚舉關(guān)聯(lián)值來做。
- 網(wǎng)絡(luò)請求使用
四、OC中使用Swift枚舉
OC文件中導(dǎo)入#import "項目名-Swift.h"文件后去使用。
'@objc' enum must declare an integer raw type.
枚舉原始值類型為Int的Swift枚舉才允許在OC中使用,并且Swift枚舉前需要加@objc。注意:OC中Integer就是Swift中的Int。
五、Swift中使用OC枚舉
將OC枚舉所在的文件導(dǎo)入到橋接文件中項目名-Bridging-Header.h。
Swift中用枚舉類型名 + 點語法就可以了。
OC中定義字符串枚舉:
Apple官方的做法
.h 文件中
typedef NSString *AddressRecType NS_STRING_ENUM;
FOUNDATION_EXPORT AddressRecType const AddressRecTypeHistory;
FOUNDATION_EXPORT AddressRecType const AddressRecTypeCurrentLocation;
FOUNDATION_EXPORT AddressRecType const AddressRecTypeRGeo;
.m 文件中
AddressRecType const AddressRecTypeHistory = @"history";
AddressRecType const AddressRecTypeCurrentLocation = @"curLocation";
AddressRecType const AddressRecTypeRGeo = @"RGeo";
.swift 文件中 --------------
let enum5 = AddressRecType.currentLocation
有些情況使用字符串枚舉更合適,比如:你寫一個Pod私有庫需要提供給Swift新項目中業(yè)務(wù)方使用,他們使用的方式是需要取到這個枚舉對應(yīng)的一個字符串類型作為參數(shù)去發(fā)送請求,這個參數(shù)如果由業(yè)務(wù)方根據(jù)枚舉做一層映射的話,這樣業(yè)務(wù)方麻煩,也怕他們會寫錯。
這個時候可以在OC中定義字符串枚舉后,在Swift中使用該枚舉的rawValue來取得這個字符串就能避免這個問題。
-
Handle unknown values using "@unknown default"的問題
在Swift中對OC枚舉所有case都處理后,仍然會有警告:
Switch covers known cases, but 'ToastType' may have additional unknown values. Handle unknown values using "@unknown default"
就是提示還需要處理未來可能新增加的case. 對于我們枚舉中已經(jīng)確定不會增加新的case了,OC中可以使用NS_CLOSED_ENUM來定義穩(wěn)定的枚舉。
typedef NS_CLOSED_ENUM(int, ToastType) {
ToastTypeNormal, // 文字
ToastTypeSucceed, // 成功
ToastTypeWarn, // 警告
ToastTypeError, // 錯誤
ToastTypeLoading // 加載
};
