參考資料
《編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》中第5條:用枚舉表示狀態(tài)、選項(xiàng)、狀態(tài)碼
枚舉類型的基本使用
枚舉的作用在于規(guī)范并語義化的定義代碼中的狀態(tài)、選項(xiàng)等常量。枚舉類型的定義以關(guān)鍵字enum開頭,之后是枚舉數(shù)據(jù)類型的名稱,最后是在一對(duì)花括號(hào)內(nèi)的選項(xiàng)標(biāo)識(shí)符序列。 編譯器會(huì)為枚舉分配一個(gè)獨(dú)有的編號(hào),從0開始,每個(gè)枚舉遞增1。
enum Direction {up, down, left, right};
實(shí)現(xiàn)枚舉所用的數(shù)據(jù)類型取決于編譯器。除了使用默認(rèn)類型,還可以指明用何種“底層數(shù)據(jù)類型”來保存枚舉類型的變量。
enum Direction : UNSInteger {up, down, left, right};
還可以不使用編譯器所分配的序號(hào),而是手工指定某個(gè)枚舉成員所對(duì)應(yīng)的值。如下例中將 up 的值設(shè)為1,而不使用編譯器所分配的0。接下來的枚舉值會(huì)在上一個(gè)的基礎(chǔ)上遞增1。
enum Direction {up = 1, down, left, right};
枚舉的另一種使用方式是定義為按位掩碼,當(dāng)定義選項(xiàng)的時(shí)候,若這些選項(xiàng)可以彼此組合,則在設(shè)置特定的枚舉值后,各選項(xiàng)間就可通過“按位或”來組合。因?yàn)槊總€(gè)枚舉值所對(duì)應(yīng)的二進(jìn)制表示中,只有1個(gè)二進(jìn)制位的值是1,所以多個(gè)選項(xiàng)“按位或”后的組合值是唯一的,且將某一選項(xiàng)與組合值做“按位與”操作,即可判斷出組合值中是否包含該選項(xiàng)。
enum Direction {
up = 1 << 0,
down = 1 << 1,
left = 1 << 2,
right = 1 << 3
};
使用關(guān)鍵字 typedef 重新定義枚舉
然而聲明或定義一個(gè)枚舉類型變量的方式卻不太簡潔,仍然需要使用enum關(guān)鍵字,如:
enum Direction {up, down, left, right};
enum Direction var1;
或
enum {up, down, left, right} var1;
為了簡化枚舉的聲明,不需要每次都寫enum,可以使用關(guān)鍵字typedef重新定義枚舉類型
enum {up, down, left, right} Direction;
typedef enum Direction Direction;
之后就可以直接用 Direction 來代替完整的 enum Direction 了:
Direction var1, var2;
使用枚舉的正確姿勢(shì)
蘋果對(duì) Objective-C 語言支持了兩個(gè)有關(guān)枚舉的輔助弘,分別是NS_ENUM和NS_OPTIONS。NS_ENUM用來定義普通的枚舉類型,NS_OPTIONS用來定義可組合選項(xiàng)的枚舉類型。
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
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
};
這些宏是用#define預(yù)處理指令來定義的,具備向后兼容能力,如果目標(biāo)平臺(tái)的編譯器支持新標(biāo)準(zhǔn),那就使用新式語法,否則改用舊式語法。具體的定義如下:
#if (__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#if (__cplusplus)
#define NS_OPTIONS(_type, _name) _type _name; enum : _type
#else
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#else
#define NS_ENUM(_type, _name) _type _name; enum
#define NS_OPTIONS(_type, _name) _type _name; enum
#endif
上述代碼首先判斷編譯器是否支持新式枚舉,如果不支持,那么NS_ENUM和NS_OPTIONS都使用相同的老式語法來定義枚舉:
typedef NSInteger Direction;
enum {up, down, left, right};
但是,舊式的語法無法告訴編譯器重新定義的枚舉類型與之后 enum 定義的整形值之間的聯(lián)系。如果支持新特性,那么NS_ENUM宏所定義的枚舉類型展開之后就是:
typedef enum UITableViewCellStyle : NSInteger UITableViewCellStyle;
enum UITableViewCellStyle : NSInteger {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
根據(jù)是否要將代碼按 C++ 編譯,NS_OPTIONS宏的定義方式也有所不同。如果不按 C++ 編譯,那么其展開方式就和NS_ENUM相同。若按 C++ 編譯,則展開后的代碼略有不同。原因在于,用按位或運(yùn)算來操作兩個(gè)枚舉值時(shí), C++ 編譯模式的處理與非 C++ 模式不一樣。在用或運(yùn)算操作兩個(gè)枚舉值時(shí), C++ 認(rèn)為運(yùn)算結(jié)果的數(shù)據(jù)類型應(yīng)該是枚舉的底層數(shù)據(jù)類型,而 C++ 不允許將這個(gè)底層類型“隱式轉(zhuǎn)換”為枚舉類型本身。如果想編譯通過,就要將按位或操作的結(jié)果“顯式轉(zhuǎn)換”為枚舉類型。所以,在 C++ 模式下應(yīng)該用另一種方式定義NS_OPTIONS宏,以便省去類型轉(zhuǎn)換操作。
鑒于此,凡是需要以按位或操作來組合的枚舉都應(yīng)使用NS_OPTIONS定義。若是枚舉不需要互相組合,則應(yīng)使用NS_ENUM來定義。
枚舉在 switch 語句中的應(yīng)用
當(dāng)在 switch 語句中使用 enum 值時(shí),不建議添加 default 分支。因?yàn)?enum 的選項(xiàng)分支已經(jīng)包含了所有可能的情況,并不需要額外處理默認(rèn)情況。而且,當(dāng)之后添加新的狀態(tài)時(shí),編譯器還會(huì)發(fā)出警告信息,提示新加入的狀態(tài)并未在 switch 分支中處理。