由于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ù)處理指令