《Effective Objective-C 2.0》- 5:用枚舉來(lái)表示狀態(tài)物邑、選項(xiàng)溜哮、狀態(tài)碼

由于 Objective-C 基于 C 語(yǔ)言,所以 C 語(yǔ)言有的功能它都有色解。其中之一就是枚舉類型:enum茂嗓。系統(tǒng)框架中頻繁使用此類型,然而開(kāi)發(fā)者容易忽視它科阎。在以一系列常量來(lái)表示錯(cuò)誤狀態(tài)碼或可組合的選項(xiàng)時(shí)述吸,極易使用枚舉為其命名。

枚舉只是一種常量命名方式锣笨。某個(gè)對(duì)象所經(jīng)歷的各種狀態(tài)就可以定義一個(gè)簡(jiǎn)單的枚舉集(enumeration set)蝌矛。比如說(shuō),可以用下列枚舉表示“套接字連接”(socket connection)的狀態(tài):

enum EOCConnectionState {
    EOCConnectionStateDisconnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

由于每種狀態(tài)都用一個(gè)便于理解的值來(lái)表示票唆,所以這樣寫(xiě)出來(lái)的代碼更易讀懂。編譯器會(huì)為枚舉分配一個(gè)獨(dú)有的編號(hào)屹徘,從 0 開(kāi)始走趋,每個(gè)枚舉值遞增 1 。實(shí)現(xiàn)枚舉所用的數(shù)據(jù)類型取決于編譯器噪伊,不過(guò)其二進(jìn)制位(bit)的個(gè)數(shù)必須能完全表示下枚舉編號(hào)才行簿煌。在前例中氮唯,由于最大編號(hào)是 2,所以使用 1 個(gè)字節(jié)的 char 類型即可姨伟。

然而定義枚舉變量的方式卻不太簡(jiǎn)潔惩琉,要依如下語(yǔ)法編寫(xiě):

enum EOCConnectionState state = EOCConnectionStateDisconnected;

若是每次不用敲入 enum 而只需要寫(xiě) EOCConnectionState 就好了。要想這樣夺荒,則需要使用 typedef 關(guān)鍵字重新定義枚舉類型:

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

現(xiàn)在可以用簡(jiǎn)寫(xiě)的 EOCConnectionState 來(lái)代替完整的 enum EOCConnectionState 了:

EOCConnectionState state = EOCConnectionStateDisconnected;

C++11 標(biāo)準(zhǔn)修訂了枚舉的某些特性瞒渠。其中一項(xiàng)改動(dòng)是:可以指明用何種“底層數(shù)據(jù)類型”(underlying type)來(lái)保存枚舉類型的變量。這樣的好處是技扼,可以向前聲明枚舉變量了伍玖。若不指定底層數(shù)據(jù)類型,則無(wú)法向前聲明枚舉類型剿吻,因?yàn)榫幾g器不清楚底層數(shù)據(jù)類型的大小窍箍,所以在用到此枚舉類型時(shí),也就不知道究竟該給變量分配多少空間丽旅。

指定底層數(shù)據(jù)類型所用的語(yǔ)法是:

enum EOCConnectionStateConnectionState : NSInteger { /* ... */ };

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

enum EOCConnectionStateConnectionState: NSInteger;

還可以不使用編譯器所分配的序號(hào),而是手工指定某個(gè)枚舉成員所對(duì)應(yīng)的值榄笙。語(yǔ)法如下:

enum EOCConnectionState {
    EOCConnectionStateDisconnected = 1,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

上述代碼把 EOCConnectionStateDisconnected 的值設(shè)為 1 邪狞,而不使用編譯器所分配的 0 。如前所述办斑,接下來(lái)幾個(gè)枚舉的值都會(huì)在上一個(gè)的基礎(chǔ)上遞增 1 外恕。比如說(shuō),EOCConnectionStateConnected 的值就是 3乡翅。

還有一種情況應(yīng)該使用枚舉類型鳞疲,那就是定義選項(xiàng)的時(shí)候。若這些選項(xiàng)可以彼此組合蠕蚜,則更應(yīng)如此尚洽。只要枚舉定義得對(duì),各選項(xiàng)之間就可以通過(guò)“按位或操作符”(bitwise OR operator)來(lái)組合靶累。例如腺毫,iOS UI 框架中有如下枚舉類型,用來(lái)表示某個(gè)視圖應(yīng)該如何在水平或垂直方向上調(diào)整大小:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

每個(gè)選項(xiàng)均可啟用或禁用挣柬,使用上述方式來(lái)定義枚舉值即可保證這一點(diǎn)潮酒,因?yàn)樵诿總€(gè)枚舉值(UIViewAutoresizingNone 除外,它點(diǎn)值是 0邪蛔,對(duì)應(yīng)的二進(jìn)制值是 0急黎,其中沒(méi)有值為 1 的二進(jìn)制位)所對(duì)應(yīng)的二進(jìn)制表示中,只有一個(gè)二進(jìn)制位的值是 1。用“按位或操作符”可組合多個(gè)選項(xiàng)勃教,例如: UIViewAutoResizingFlexibleWidth | UIViewAutoresizingFlexibleHeight淤击。圖列出了每個(gè)枚舉成員的二進(jìn)制值,并演示了剛才那兩個(gè)枚舉組合之后的值故源。用“按位與操作符”(bitwise AND operator)即可判斷出是否已啟用某個(gè)選項(xiàng):

enum UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    if (resizing & UIViewAutoresizingFlexibleWidth) {
        // UIViewAutoresizingFlexibleWidth is set
    }
UIViewAutoresizingFlexibleLeftMargin 000001
    UIViewAutoresizingFlexibleWidth 000010
    UIViewAutoresizingFlexibleRightMargin 000100
    UIViewAutoresizingFlexibleTopMargin 001000
    UIViewAutoresizingFlexibleHeight 010000
    UIViewAutoresizingFlexibleBottomMargin 100000    

    UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight 010010

每個(gè)枚舉值的二進(jìn)制表示污抬,以及對(duì)其中兩個(gè)枚舉值執(zhí)行按位或操作之后對(duì)二進(jìn)制值。

系統(tǒng)庫(kù)中頻繁使用這個(gè)方法绳军。iOS UI 框架中的 UIKit 里面還有個(gè)例子印机,用枚舉值告訴系統(tǒng)視圖所支持的設(shè)備顯示方向。這個(gè)枚舉類型叫做 UIInterfaceOrientationMask删铃,開(kāi)發(fā)者需要實(shí)現(xiàn)一個(gè)名為 supportedInterfaceOrientations 的方法耳贬,將視圖所支持的顯示方向高速系統(tǒng):

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

Foundation 框架中定義了一些輔助的宏,用這些宏來(lái)定義枚舉類型時(shí)猎唁,也可以指定用于保存枚舉值的底層數(shù)據(jù)類型咒劲。這些宏具備向后兼容(backward compatibility)能力,如果目標(biāo)平臺(tái)的編譯器支持新標(biāo)準(zhǔn)诫隅,那就使用新式語(yǔ)法腐魂,否則改用舊式語(yǔ)法。這些宏是用 #define 預(yù)處理指令來(lái)定義的逐纬,其中一個(gè)用于定義像 EOCConnectionState 這種普通的枚舉類型蛔屹,另一個(gè)用于定義像 UIViewAutoresizing 這種包含一系列選項(xiàng)的枚舉類型,其用法如下:

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

這些宏的定義如下:

#define NS_ENUM(...) CF_ENUM(__VA_ARGS__)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)

由于需要分別處理不同的情況豁生,所以上述代碼用很多種方式來(lái)定義這兩個(gè)宏兔毒。第一個(gè) #if 用于判斷編譯器是否支持新式枚舉。其中所用的布爾邏輯看上去相當(dāng)復(fù)雜甸箱,不過(guò)其意思就是想判斷編譯器是否支持新的枚舉特性育叁。如果不支持,那么就用老式語(yǔ)法來(lái)定義枚舉芍殖。

如果支持新特性豪嗽,那么用 NS_ENUM 宏所定義的枚舉類型展開(kāi)之后就是:

typedef enum EOCConnectionState : NSUInteger EOCConnectionState;
enum EOCConnectionState : NSUInteger {
    EOCConnectionStateDisconnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

根據(jù)是否要將代碼按 C++ 模式編譯,NS_OPTIONS 宏的定義方式有所不同豌骏。如果不按 C++ 編譯龟梦,那么其展開(kāi)方式就和 NS_ENUM 相同。若按 C++ 編譯窃躲,則展開(kāi)后的代碼略有不同计贰。原因在于,用按位或運(yùn)算來(lái)操作兩個(gè)枚舉值時(shí)蒂窒,C++ 編譯模式的處理辦法與非 C++ 模式不一樣躁倒。而上面已經(jīng)提到了赎婚,作為選項(xiàng)的枚舉值經(jīng)常需要用按位或運(yùn)算來(lái)組合。在用或運(yùn)算操作兩個(gè)枚舉值時(shí)樱溉,C++ 認(rèn)為運(yùn)算結(jié)果的數(shù)據(jù)類型應(yīng)該是枚舉的底層數(shù)據(jù)類型,也就是NSUInteger纬凤。而且 C++ 不允許將這個(gè)底層類型“隱式轉(zhuǎn)換”(implicit cast)為枚舉類型本身福贞。我們用 EOCPermittedDirection 來(lái)演示一下,假設(shè)按 NS_ENUM 方式將其展開(kāi):

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 模式編譯)停士,則會(huì)給出下列錯(cuò)誤信息:

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)換操作。鑒于此蜻底,凡是需要以按位或操作來(lái)組合的枚舉都應(yīng)該使用 NS_OPTIONS 定義骄崩。若是枚舉不需要互相組合,則應(yīng)使用 NS_ENUM 來(lái)定義薄辅。

能夠用到枚舉的情況還有很多要拂。前面已經(jīng)提到,枚舉可以表示選項(xiàng)與狀態(tài)站楚,然而還有許多東西也能用枚舉表示脱惰。比如狀態(tài)碼就是個(gè)好例子×海可以把邏輯含義相似的一組狀態(tài)碼放入同一個(gè)枚舉集里拉一,而不要用 #define 預(yù)處理指令或常量來(lái)定義。以枚舉來(lái)表示樣式(style)也很合宜旧乞。假設(shè)創(chuàng)建某個(gè) UI 元素時(shí)可以使用不同的樣式蔚润,那么在這種情況下就最應(yīng)該把樣式聲明為枚舉類型了。

最后再講一種枚舉的用法良蛮,就是在 switch 語(yǔ)句里抽碌,有時(shí)可以這樣定義:

typedef NS_ENUM(NSUInteger, EOCConnectionState) {
    EOCConnectionStateDisconnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};
  
    switch (_currentState) {
        case EOCConnectionStateDisconnected:
        {
             // Handle disconnected state
        }
            break;
            case EOCConnectionStateConnecting:
        {
            // handle connecting state
        }
            break;
            case EOCConnectionStateConnected:
        {
            // handle connected state
        }
            break;
    }

我們總是習(xí)慣在 switch 語(yǔ)句中加上 default 分支。然而决瞳,若是用枚舉來(lái)定義狀態(tài)機(jī)(state machine),則最好不要有 default 分支货徙。這樣的話,如果稍后又加了一種狀態(tài)皮胡,那么編譯器就會(huì)發(fā)出警告信息痴颊,提示新加入的狀態(tài)并未在 switch 分支中處理。假如寫(xiě)上了 default 分支屡贺,那么它就會(huì)處理這個(gè)新?tīng)顟B(tài)蠢棱,從而導(dǎo)致編譯器不發(fā)出警告信息锌杀。用 NS_ENUM 定義其他枚舉類型時(shí)也要注意此問(wèn)題。例如泻仙,在定義代表 UI 元素的枚舉時(shí)糕再,通常要確保 switch 語(yǔ)句能正確處理所有樣式。

總結(jié):

  1. 應(yīng)該用枚舉來(lái)表示狀態(tài)機(jī)的狀態(tài)玉转、傳遞給方法的選項(xiàng)以及狀態(tài)碼等值突想,給這些值起個(gè)易懂的名字。
  2. 如果把傳遞給某個(gè)方法的選項(xiàng)表示為枚舉類型究抓,而多個(gè)選項(xiàng)又可同時(shí)使用猾担,那么就將各選項(xiàng)定義為 2 的冪,以便通過(guò)按位或操作將其組合起來(lái)刺下。
  3. 用 NS_ENUM 與 NS_OPTIONS 宏來(lái)定義枚舉類型绑嘹,并指明其底層數(shù)據(jù)類型。這樣做可以確保枚舉是用開(kāi)發(fā)者所選的底層數(shù)據(jù)類型實(shí)現(xiàn)出來(lái)的橘茉,而不會(huì)采用編譯器所選的類型工腋。
  4. 在處理枚舉類型的 switch 語(yǔ)句中不要實(shí)現(xiàn) default 分支。這樣的話畅卓,加入新枚舉之后夷蚊,編譯器就會(huì)提示開(kāi)發(fā)者:switch 語(yǔ)句并未處理所有枚舉。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末髓介,一起剝皮案震驚了整個(gè)濱河市惕鼓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唐础,老刑警劉巖箱歧,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異一膨,居然都是意外死亡呀邢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)豹绪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)价淌,“玉大人,你說(shuō)我怎么就攤上這事瞒津〔跻拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵巷蚪,是天一觀的道長(zhǎng)病毡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)屁柏,這世上最難降的妖魔是什么啦膜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任有送,我火速辦了婚禮,結(jié)果婚禮上僧家,老公的妹妹穿的比我還像新娘雀摘。我一直安慰自己,他們只是感情好八拱,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布届宠。 她就那樣靜靜地躺著,像睡著了一般乘粒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上伤塌,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天灯萍,我揣著相機(jī)與錄音,去河邊找鬼每聪。 笑死旦棉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的药薯。 我是一名探鬼主播绑洛,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼童本!你這毒婦竟也來(lái)了真屯?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤穷娱,失蹤者是張志新(化名)和其女友劉穎绑蔫,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泵额,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡配深,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫁盲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篓叶。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖羞秤,靈堂內(nèi)的尸體忽然破棺而出缸托,到底是詐尸還是另有隱情,我是刑警寧澤瘾蛋,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布嗦董,位于F島的核電站,受9級(jí)特大地震影響瘦黑,放射性物質(zhì)發(fā)生泄漏京革。R本人自食惡果不足惜奇唤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匹摇。 院中可真熱鬧咬扇,春花似錦、人聲如沸廊勃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坡垫。三九已至梭灿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冰悠,已是汗流浹背堡妒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溉卓,地道東北人皮迟。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像桑寨,于是被迫代替她去往敵國(guó)和親伏尼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

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