Swift中的枚舉有原始值和關聯值,其使用范圍相比OC來說多了很多,因此也更復雜,需要我們花更多的時間來學習它,本文是對學習后和在實際中的運用做的一個總結。
目前來看需要掌握的內容如下:
Swift枚舉內容.jpg
一、Swift枚舉聲明
>>> A. 枚舉的類型及原始值、關聯值
- 聲明枚舉時,類型繼承列表(即冒號后面)寫原始值類型,不寫時默認是
Int
,如果是其它類型則需要寫明。
- 聲明枚舉時,類型繼承列表(即冒號后面)寫原始值類型,不寫時默認是
- 原始值代表的是一個枚舉變量的
rawValue
,rawValue
的本質是枚舉的計算屬性。
- 原始值代表的是一個枚舉變量的
- 在寫枚舉的case時:
如果原始值類型是Int
、String
類型,不寫明原始值會自動生成原始值, 對于Int
類型默認從0開始,下一個是上一個case的原始值+1;對于String
類型,默認原始值是case名;
如果是其它原始值類型的枚舉,可以用"="寫明對應的原始值,見下面的Direction
.
- 在寫枚舉的case時:
- 每個case后面可以追加枚舉關聯值,比如下面的
Score
.
- 每個case后面可以追加枚舉關聯值,比如下面的
-
遞歸枚舉:如果枚舉關聯值中有枚舉本身類型,則這個時候在枚舉定義前或者
case
前需要加indirect
。
-
遞歸枚舉:如果枚舉關聯值中有枚舉本身類型,則這個時候在枚舉定義前或者
- 枚舉變量初始化時使用一般使用
enumName.caseName
。
- 枚舉變量初始化時使用一般使用
- 枚舉默認遵守
RawRepresentable
協議,協議里面有可失敗初始化器init?(rawValue: Self.RawValue)
和計算屬性rawValue
. 所以我們可以通過這個協議的方法來完成初始化和獲取原始值rawValue
。
- 枚舉默認遵守
- 枚舉的原始值類型必須是可以用字面量表示的,比如
Int
、Float
、String
等, 不然的話會報錯Raw value for enum case must be a literal
.
- 枚舉的原始值類型必須是可以用字面量表示的,比如
- 枚舉的case不能即寫明原始值又寫關聯值,寫了報錯
Enum with raw type cannot have cases with arguments
- 枚舉的case不能即寫明原始值又寫關聯值,寫了報錯
// 1.這里的原始值類型是Character,如果不寫明= "d"則會報錯.
// 2.如果是不寫原始值類型Character,默認是Int。
// 3.對于原始值類型是Int或者String,不寫明“=”后面的也不會報錯。
enum Direction: Character {
case north = "d"
case south = "s"
case east = "e"
case west = "w"
}
// 帶枚舉關聯值的枚舉
enum Score {
case point(Int, Int, Int)
case grade(Character)
}
// case中關聯值有自身枚舉類型的遞歸枚舉
indirect enum ArithExpr {
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr, ArithExpr)
}
>>> B. 枚舉的其它性質
Swift中枚舉可以遵守協議,可以添加成員方法、靜態方法、靜態變量、計算屬性。
enum BasketballNum4: Character, CaseIterable {
case AAAA16 = "2"
// 可添加計算屬性
var name: String { "dandy" }
// 可添加靜態變量
static var name2: String { "dandy.static" }
// 可添加成員方法
func testSelf() {
print(self.name)
print(Self.name2)
}
// 可添加靜態方法
static func testSelf2() {
print(self.name2)
}
}
二、枚舉變量的內存
弄清楚枚舉的內存使用情況能讓我們在使用時對性能消耗有所了解。
使用MemoryLayout
可以查看分配內存和使用內存情況,比如下面的方式:
print("MemoryLayout<String>.size", MemoryLayout<String>.size)
print("MemoryLayout<String>.stride=", MemoryLayout<String>.stride)
print("MemoryLayout<String>.alignment=", MemoryLayout<String>.alignment)
2.1 關聯值的存儲
- 如果沒有枚舉關聯值時,枚舉變量只占一個字節,這個字節里面的內容就是
case
的序號。 - 如果有枚舉關聯值,那么枚舉內存分配 = 各個關聯值類型所占的字節總和 + 1(這1個字節是存放枚舉case序號的)+ 根據內存對齊
alignment
補齊字節。 - 內存對齊
alignment
是關聯值類型中最大的那個。
證明過程:
我這里是學習李明杰大師的方法做的,使用他的demo就可以實現:https://github.com/CoderMJLee/Mems
打開demo --》在main文件頂部寫showEnum() --》運行程序 --》打印枚舉變量地址 --》如圖打開內存面板,輸入這個變量的地址值 --》 即可查看該內存里面的值。
枚舉關聯值的存儲.jpg
2.2 原始值的存儲
rawValue
是一個計算屬性,在編譯時就已經確定了每個枚舉值對應的rawValue
值,不需要存儲。證明方法: 可以分析swift
文件編譯過程生成的中間文件sil
。
可以參考文章:Swift進階(六)枚舉和可選類型
2.3 枚舉中方法、計算屬性在內存中什么位置?
- 計算屬性也可以認為是方法,方法的本質就是函數, 方法、函數都存放在代碼段,所以計算屬性、成員方法都存放在代碼段。
- 靜態變量就是全局變量,存放在內存的數據段。
- 靜態方法存放在代碼段。
證明方法見博客Swift 方法及方法在內存中的位置
- 靜態方法存放在代碼段。
三、Swift枚舉應用舉例
- 跟OC語言枚舉一樣的情況,比如訂單狀態枚舉。
- 應用于將多個相似的方法整合成一個,比如:RxSwift中的信號發送。
// 在OC中是分別有onNext、onComplete、onError三個方法,RxSwift的內部中轉方法只使用一個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
}
- 系統的可選類型也是一個枚舉。可選類型可以賦值為nil,相當于賦值了
Optional.none
, 原因是遵守了ExpressibleByNilLiteral
協議。
- 系統的可選類型也是一個枚舉。可選類型可以賦值為nil,相當于賦值了
- 將APP所有的通知名集中到一個枚舉中,枚舉原始值類型聲明為
String
,這樣在使用直接用點語法找case
,而且APP中包含哪些通知也變得一目了然。
- 將APP所有的通知名集中到一個枚舉中,枚舉原始值類型聲明為
- 埋點神策事件時使用枚舉關聯值來做,代碼設計感會很不錯。
- 網絡請求使用
Moya
來實現的話,一個請求對應相關聯參數,這個也可以用枚舉關聯值來做。
- 網絡請求使用
四、OC中使用Swift枚舉
OC文件中導入#import "項目名-Swift.h"
文件后去使用。
'@objc' enum must declare an integer raw type
.
枚舉原始值類型為Int的Swift
枚舉才允許在OC中使用,并且Swift枚舉前需要加@objc
。注意:OC中Integer就是Swift中的Int。
五、Swift中使用OC枚舉
將OC枚舉所在的文件導入到橋接文件中項目名-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新項目
中業務方使用,他們使用的方式是需要取到這個枚舉對應的一個字符串類型作為參數去發送請求,這個參數如果由業務方根據枚舉做一層映射的話,這樣業務方麻煩,也怕他們會寫錯。
這個時候可以在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. 對于我們枚舉中已經確定不會增加新的case了,OC中可以使用NS_CLOSED_ENUM
來定義穩定的枚舉。
typedef NS_CLOSED_ENUM(int, ToastType) {
ToastTypeNormal, // 文字
ToastTypeSucceed, // 成功
ToastTypeWarn, // 警告
ToastTypeError, // 錯誤
ToastTypeLoading // 加載
};