Swift枚舉熟練運(yùn)用必備知識

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

Swift枚舉內(nèi)容.jpg

一、Swift枚舉聲明
>>> A. 枚舉的類型及原始值、關(guān)聯(lián)值
    1. 聲明枚舉時,類型繼承列表(即冒號后面)寫原始值類型,不寫時默認(rèn)是Int,如果是其它類型則需要寫明。
    1. 原始值代表的是一個枚舉變量的rawValue,rawValue的本質(zhì)是枚舉的計算屬性。
    1. 在寫枚舉的case時:
      如果原始值類型是Int、String類型,不寫明原始值會自動生成原始值, 對于Int類型默認(rèn)從0開始,下一個是上一個case的原始值+1;對于String類型,默認(rèn)原始值是case名;
      如果是其它原始值類型的枚舉,可以用"="寫明對應(yīng)的原始值,見下面的Direction.
    1. 每個case后面可以追加枚舉關(guān)聯(lián)值,比如下面的Score.
    1. 遞歸枚舉:如果枚舉關(guān)聯(lián)值中有枚舉本身類型,則這個時候在枚舉定義前或者case前需要加indirect。
    1. 枚舉變量初始化時使用一般使用enumName.caseName
    1. 枚舉默認(rèn)遵守RawRepresentable協(xié)議,協(xié)議里面有可失敗初始化器init?(rawValue: Self.RawValue)和計算屬性rawValue. 所以我們可以通過這個協(xié)議的方法來完成初始化和獲取原始值rawValue。
    1. 枚舉的原始值類型必須是可以用字面量表示的,比如IntFloat、String等, 不然的話會報錯Raw value for enum case must be a literal.
    1. 枚舉的case不能即寫明原始值又寫關(guān)聯(lián)值,寫了報錯Enum with raw type cannot have cases with arguments
// 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)存中什么位置?
    1. 計算屬性也可以認(rèn)為是方法,方法的本質(zhì)就是函數(shù), 方法、函數(shù)都存放在代碼段,所以計算屬性、成員方法都存放在代碼段。
    1. 靜態(tài)變量就是全局變量,存放在內(nèi)存的數(shù)據(jù)段。
    1. 靜態(tài)方法存放在代碼段。
      證明方法見博客Swift 方法及方法在內(nèi)存中的位置
三、Swift枚舉應(yīng)用舉例
    1. 跟OC語言枚舉一樣的情況,比如訂單狀態(tài)枚舉。
    1. 應(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
}
    1. 系統(tǒng)的可選類型也是一個枚舉??蛇x類型可以賦值為nil,相當(dāng)于賦值了Optional.none, 原因是遵守了ExpressibleByNilLiteral協(xié)議。
    1. 將APP所有的通知名集中到一個枚舉中,枚舉原始值類型聲明為String,這樣在使用直接用點語法找case,而且APP中包含哪些通知也變得一目了然。
    1. 埋點神策事件時使用枚舉關(guān)聯(lián)值來做,代碼設(shè)計感會很不錯。
    1. 網(wǎng)絡(luò)請求使用Moya來實現(xiàn)的話,一個請求對應(yīng)相關(guān)聯(lián)參數(shù),這個也可以用枚舉關(guān)聯(lián)值來做。
四、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       // 加載
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容