用枚舉表示狀態(tài)、選項(xiàng)镣煮、狀態(tài)碼

由于Objective-C是源于C語言姐霍,所以C語言的功能它都有。其中一種就是枚舉類型enum典唇。系統(tǒng)框架中頻繁用到此類型镊折,然而開發(fā)者容易忽視它。在以一系列常量來表示錯誤狀態(tài)碼或可組合的選項(xiàng)時介衔,極易使用枚舉為其命名恨胚。由于C++11標(biāo)準(zhǔn)擴(kuò)充了枚舉的特性,所以最新系統(tǒng)框架用了“強(qiáng)類型”(strong type)的枚舉炎咖。沒錯赃泡,Objective-C也得益于C++11標(biāo)準(zhǔn)。
枚舉只是一種常量命名方式乘盼。某個對象所經(jīng)歷的各種狀態(tài)就可以定義為簡單的一個枚舉集(enumeration set)急迂。比如說可以用下列枚舉來表示“套接字連接”(socket connection)的狀態(tài):

enum EOCConnectionState {
    EOCConnectionStateDisConnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

由于每種狀態(tài)都用一個便于理解的值來表示,所以這樣寫的代碼更易讀懂蹦肴。編譯器會為枚舉分配一個獨(dú)有的編號僚碎,從0開始,每個枚舉遞增1阴幌。實(shí)現(xiàn)枚舉所用的數(shù)據(jù)類型取決于編譯器勺阐,不過其二進(jìn)制位(bit)的個數(shù)必須能完全表示下枚舉編號才行卷中。在前列中,由于最大編號是2渊抽,所以使用1個字節(jié)的char類型即可蟆豫。
然而定義枚舉變量的方式卻不太簡潔,需依賴如下語法

enum EOCConnectionState state = EOCConnectionStateDisConnected;

若是每次不用敲入EOCConnectionState就好了懒闷,要想這樣做十减,則需使用typedef關(guān)鍵字重新定義枚舉類型:

enum EOCConnectionState {
    EOCConnectionStateDisConnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};
typedef enum EOCConnectionState EOCConnectionState;

現(xiàn)在可以用縮寫的 EOCConnectionState 來替代完整的 enum EOCConnectionState 了:

EOCConnectionState state= EOCConnectionStateDisConnected;

C++11標(biāo)準(zhǔn)修訂了枚舉的某些特性愤估,其中一項(xiàng)改動是:可以指明用何種“底層數(shù)據(jù)類型”(underlying type)來保存枚舉類型的變量帮辟。這樣做的好處是,可以向前聲明枚舉變量了玩焰。若不指定底層數(shù)據(jù)類型由驹,則無法向前聲明枚舉類型,因?yàn)榫幾g器不清楚底層數(shù)據(jù)類型的大小昔园,所以在用到此枚舉類型時蔓榄,也就不知道究竟該給變量分配多少內(nèi)存空間。
指定底層數(shù)據(jù)類型所用的語法是:

enum EOCConnectionStateConnectionState : NSInteger { /* ··· */ }

上面這行代碼確保枚舉的底層數(shù)據(jù)類型時NSInteger默刚。也可以在向前聲明時指定數(shù)據(jù)類型

enum EOCConnectionStateConnectionState : NSInteger

還可以不使用編譯器所分配的序號甥郑,而是手動指定某個成員變量所對應(yīng)的值。語法如下:

enum EOCConnectionStateConnectionState {
    EOCConnectionStateDisConnected = 1,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
}

上述代碼把EOCConnectionStateDisConnected的值設(shè)為1荤西,而不使用編譯器所分配的0澜搅。如上所述,那么接下來的枚舉值都會在上一個的基礎(chǔ)上遞增1皂冰,比如說EOCConnectionState-Connected的值就是3店展。
還有一種情況應(yīng)使用枚舉類型,那就是定義選項(xiàng)的時候秃流。若這些選項(xiàng)可以彼此組合赂蕴,那更應(yīng)該如此。只要枚舉定義的對舶胀,各選項(xiàng)之間就可以通過“按位或操作符”(bitwise OR operator)來組合概说。例如,IOS UI框架下有如下枚舉類型嚣伐,用來表示某個視圖應(yīng)該如何在水平或垂直方向上調(diào)整大小糖赔。

enum UIViewAutoresizing {
    UIViewAutoresizingNone                     = 0,
    UIViewAutoresizingFlexibleLeftMargin       = 1 << 0,
    UIViewAutoresizingFlexibleWidth            = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin      = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin        = 1 << 3,
    UIViewAutoresizingFlexibleHeight           = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin     = 1 << 5,
}

每個選項(xiàng)均可啟用或禁用,使用上述方式來定義枚舉值即可保證這一點(diǎn)轩端,因?yàn)樵诿總€枚舉值所對應(yīng)的二進(jìn)制表示中放典,只有一個二進(jìn)制位的值是1。用“按位或操作符”可組合多個選項(xiàng)。例如UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexbleHeight奋构。

系統(tǒng)庫中頻繁使用了這個方法壳影。IOS UI框架中UIKit里還有個例子,用枚舉值來告訴系統(tǒng)視圖所支持的設(shè)備顯示方向弥臼。這個枚舉類型叫做UIInterfaceOrientationMask宴咧,開發(fā)者需要實(shí)現(xiàn)一個名為supportedInterfaceOrientations的方法,將視圖所支持的顯示方向告訴系統(tǒng):

-  (void)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}

Foundation框架定義了一些輔助的宏径缅,用這些宏來定義枚舉時掺栅,可以指定用于保存枚舉的底層數(shù)據(jù)類型,這些宏兼?zhèn)湎蚝蠹嫒荩╞ackward compatibility)的能力纳猪,如果目標(biāo)平臺編譯器支持新標(biāo)準(zhǔn)氧卧,那就使用新式語法,否則改用舊式語法兆旬。這些宏使用#define預(yù)處理指令來定義的假抄。其中一個用于定義像EOCConnectionState普通枚舉類型怎栽,另一個用于定義像UIViewAutoresizing這種包含一系列選項(xiàng)的枚舉類型丽猬。其用法如下:

typedef NS_ENUM (NSUInteger, EOCConnectionState) {
    EOCConnectionStateDisConnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};
typedef NS_OPTIONS (NSUInteger, EOCPermittedDirection) {
    EOCPermittedDirectionUp      =  1 << 0,
    EOCPermittedDirectionDown    =  1 << 1,
    EOCPermittedDirectionRight   =  1 << 2,
    EOCPermittedDirectionLeft    =  1 << 3,
};

這些宏定義如下:

#if ((__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))) && __has_attribute(ns_error_domain)
#define __NS_NAMED_ERROR_ENUM(_domain, _name)     enum _name : NSInteger _name; enum __attribute__((ns_error_domain(_domain))) _name : NSInteger
#define __NS_ANON_ERROR_ENUM(_domain)             enum __attribute__((ns_error_domain(_domain))) : NSInteger
#else
#define __NS_NAMED_ERROR_ENUM(_domain, _name) NS_ENUM(NSInteger, _name)
#define __NS_ANON_ERROR_ENUM(_domain) NS_ENUM(NSInteger)
#endif

由于需要分別處理不同的情況,所以上述代碼用多種方式來定義這兩個宏熏瞄。第一個用#if用于判斷編譯器是否支持新式枚舉脚祟。其中所用的布爾邏輯看上去相當(dāng)復(fù)雜。不過其意思就是想判斷編譯器是否支持新式枚舉强饮。如果不支持由桌,那么就用老式枚舉來定義。如果支持新特性邮丰,那么NS_ENUM宏所定義的枚舉類型展開后就是:

typedef enum EOCConnectionState :NSUInteger EOCConnectionState行您;
enum EOCConnectionState : NSUInteger {
    EOCConnectionStateDisConnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

根據(jù)是否要將代碼按照C++模式編譯,NS_OPTIONS宏的定義方法也有所不同剪廉,如果不按C++編譯娃循,那么其展開方式與NS_ENUM相同。如果按照C++編譯斗蒋,那么展開的代碼略有不同捌斧。原因在于,用按位或運(yùn)算來操作兩個枚舉值時泉沾。C++編譯模式的處理方法與非C++模式不一樣捞蚂。而上面已經(jīng)提到了,作為選項(xiàng)的枚舉值經(jīng)常需要用按位或運(yùn)算來組合跷究。再用或運(yùn)算來操作兩個枚舉時姓迅,C++認(rèn)為運(yùn)算結(jié)果的數(shù)據(jù)類型應(yīng)該是枚舉的底層數(shù)據(jù)類型,也就是NSUInteger。而且C++不允許將這個底層類型“隱式轉(zhuǎn)換”(implicit cast)為枚舉類型本身丁存。我們用EOCPermittedDirection來演示一下:

typedef enum EOCPermittedDirection :int EOCPermittedDirection色冀;
enum  EOCPermittedDirection :int {
    EOCPermittedDirectionUp     = 1 << 0,
    EOCPermittedDirectionDown   = 1 << 1,
    EOCPermittedDirectionLeft   = 1 << 2,
    EOCPermittedDirectionRight  = 1 << 3,
};

然后考慮下列代碼:

EOCPermittedDirection permittedDirections = EOCPermittedDirectionLeft | EOCPermittedDirectionUp

若編譯器按照C++模式編譯(也可能是按照Objective-C++模式編譯),則會給出下列錯誤信息:

error: cannot initialize a variable of type 'EOCPermittedDirection' with an rvalue 
of type 'int'

若想編譯這行代碼柱嫌,就要將按位或操作的結(jié)果“顯示轉(zhuǎn)換”(explicit cast)為EOCPermittedDirection锋恬,所以在C++模式下應(yīng)該用另一種方式定義NS_OPTIONS宏,以便省去類型轉(zhuǎn)換操作编丘。鑒于此与学,凡是需要以按位或操作組合的枚舉都應(yīng)使用NS_OPTIONS來定義。如枚舉不需要互相組合嘉抓,則需要使用NS_ENUM來定義索守。
能用到枚舉的情況還有很多。前面已經(jīng)提到抑片,枚舉可以表示選項(xiàng)與狀態(tài)卵佛,然而還有許多東西能有枚舉來表示。比如狀態(tài)碼就是個好例子敞斋〗赝簦可以把邏輯含義相似的人一組狀態(tài)碼放入同一個枚舉集里,而不要用#define預(yù)處理指令或者常量來定義植捎。以枚舉來表示“樣式”(style)也很適宜衙解。假如創(chuàng)建某個UI元素時可以使用不同樣式,那么在這種情況下最應(yīng)該把樣式聲明為枚舉類型了焰枢。
最后再講一種枚舉用法蚓峦,就是在switch語句里,有時可以這樣定義:

typedef NS_ENUM (NSUInteger, EOCConnectionState) {
    EOCConnectionStateDisConnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
}

switch (_currentState) {
    EOCConnectionStateDisConnected :
       //handle disConnected state
        break;
    EOCConnectionStateConnecting :
       //handle connecting state
        break;
    EOCConnectionStateDisConnected :
       //handle connected state
        break;
}

我們總習(xí)慣在switch語句中加入default分支济锄,然而若使用枚舉來定義狀態(tài)機(jī)(state machine)暑椰,則最好不要有default分支,這樣的話荐绝,如果稍后又加入了一種狀態(tài)一汽,那么編譯器就會發(fā)出警告信息,提示新加入的狀態(tài)并未在switch中處理很泊,加入寫上了default分支角虫,那么他就會處理這個新狀態(tài),從而導(dǎo)致編譯器不會發(fā)出警告信息委造。使用NS_ENUM定義其他枚舉類型時也要注意此問題戳鹅。例如,在定義代表UI元素樣式枚舉時昏兆,通常要確保switch語句能正確處理所有樣式枫虏。

要點(diǎn):

應(yīng)使用枚舉來表示狀態(tài)機(jī)的狀態(tài)淋叶,傳遞給方法的選項(xiàng)因惭,狀態(tài)碼等值,給這些值七個易懂的名字

如果把傳遞給某個方法的選項(xiàng)表示為枚舉類型,而多個選項(xiàng)又可以同時使用报亩,那么將個選值定義為2的冪章蚣,以便通過按位或操作將其組合起來推励。

用NS_ENUM與NS_OPTIONS宏來定義枚舉類型父晶。并指明底層數(shù)據(jù)類型。這樣做可以確保枚舉使用開發(fā)者所選的底層數(shù)據(jù)類型實(shí)現(xiàn)出來的赞警,而不會采用編譯器所選的類型妓忍。

在處理枚舉類型的switch語句中,不要實(shí)現(xiàn)default分支愧旦。這樣的話世剖,加入新枚舉,編譯器就會提示開發(fā)者笤虫,switch語句并未處理所有枚舉

此文章是讀《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》學(xué)習(xí)筆記:
第五條:多用類型常量旁瘫,少用#define預(yù)處理指令

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市琼蚯,隨后出現(xiàn)的幾起案子酬凳,更是在濱河造成了極大的恐慌,老刑警劉巖凌停,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粱年,死亡現(xiàn)場離奇詭異售滤,居然都是意外死亡罚拟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門完箩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赐俗,“玉大人,你說我怎么就攤上這事弊知∽璐” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵秩彤,是天一觀的道長叔扼。 經(jīng)常有香客問我,道長漫雷,這世上最難降的妖魔是什么瓜富? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮降盹,結(jié)果婚禮上与柑,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好价捧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布丑念。 她就那樣靜靜地躺著,像睡著了一般结蟋。 火紅的嫁衣襯著肌膚如雪脯倚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天嵌屎,我揣著相機(jī)與錄音挠将,去河邊找鬼。 笑死编整,一個胖子當(dāng)著我的面吹牛舔稀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掌测,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼内贮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汞斧?” 一聲冷哼從身側(cè)響起夜郁,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粘勒,沒想到半個月后竞端,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庙睡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年事富,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乘陪。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡统台,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啡邑,到底是詐尸還是另有隱情贱勃,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布谤逼,位于F島的核電站贵扰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏流部。R本人自食惡果不足惜戚绕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贵涵。 院中可真熱鬧列肢,春花似錦恰画、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至欧聘,卻和暖如春片林,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怀骤。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工费封, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒋伦。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓弓摘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親痕届。 傳聞我的和親對象是個殘疾皇子韧献,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容