由于OC基于C語言,所以C語言有的功能它都有.枚舉(enum)就是其中之一.系統(tǒng)框架中頻繁使用枚舉,在用一系列常量來表示錯誤狀態(tài)碼或可組合的選項是,極宜用枚舉為其命名.由于C++11標準擴充了枚舉的特性,所以最新版系統(tǒng)框架使用了"強類型"的枚舉.OC也得益于C++11標準.
枚舉只是一種常量的命名方式,某個對象所經(jīng)歷的各種狀態(tài)就是可以定義為一個簡單的枚舉集合.
enum UIAlertActionStyle {
UIAlertActionStyleDefault,
UIAlertActionStyleCancel,
UIAlertActionStyleDestructive
};
由于每種狀態(tài)都有一個便于理解的值來表示,所以這樣寫出來的代碼更易讀懂.編譯器會為枚舉分配一個獨有的編號,從0開始,每個枚舉遞增1.實現(xiàn)枚舉所用的數(shù)據(jù)類型取決于編譯器,不過其二進制位的個數(shù)必須能完全表示下枚舉編號才行.
然而定義枚舉變量的方式卻不太簡潔:
enum UIAlertActionStyle state = UIAlertActionStyleDefault;
要是每次不用敲enum,只寫UIAlertActionStyle就爽了.于是可以這樣寫:
enum UIAlertActionStyle {
UIAlertActionStyleDefault,
UIAlertActionStyleCancel,
UIAlertActionStyleDestructive
};
typedef enum UIAlertActionStyle UIAlertActionStyle;
現(xiàn)在就可以這樣定義枚舉變量了:
UIAlertActionStyle state = UIAlertActionStyleDefault;
C++11標準修訂了枚舉的某些特性,其中一項改動是:可以指明用何種"底層數(shù)據(jù)類型"來保存枚舉類型的變量.這樣做的好處是,可以向前聲明枚舉變量了.若不指定底層數(shù)據(jù)類型,則無法向前聲明枚舉類型.因為編譯器不知道底層數(shù)據(jù)類型的大小,所以在用到此枚舉類型時,也就不知道該給變量分配多少內(nèi)存空間.
指定底層數(shù)據(jù)類型所有的語法是:
enum CWGConnectionStateConnectionState: NSInteger {/* ... */};
這樣就保證了枚舉底層數(shù)據(jù)類型是NSinteger.也可以在向前聲明時指定底層數(shù)據(jù)類型:
enum CWGConnectionStateConnectionState: NSInteger;
還可以不使用編譯器所分配的序號,而是手工指定某個枚舉成員所對應的值.
enum CWGConnectionStateConnectionState {
CWGConnectionStateDisconnected = 1,
CWGConnectionStateConnecting,
CWGConnectionStateConnected,
};
這樣的話,上面中的每一個枚舉變量的值就是CWGConnectionStateConnecting值為2,CWGConnectionStateConnected值為3.
還有一種情況應該使用枚舉類型,那就是定義選項的時候.如果這些選項可以彼此組合,那么更應該如此.只要枚舉定義的對,那么各選項之間就可以通過"按位或操作符"來組合.例如在iOSUI框架下,有用來表示某個視圖應該如何在水平或垂直方向上調(diào)整大小:
enum UIViewAutoresizing {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
每個選項都可以啟動或禁用.使用上訴方式來定義枚舉值既可以保證這一點.因為在每個枚舉值所對應的二進制表示中,只有1個二進制的值是1,用"按位或操作符"可組合對個選項.例如:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight,用按位與操作符既可以判斷出是否已經(jīng)啟動某個選項:
enum UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if(resizing & UIViewAutoresizingFlexibleWidth) {
// UIViewAutoresizingFlexibleWidth is set
};
系統(tǒng)庫中頻繁使用這個方法,iOS UI框架中的UIKit里還有一個例子, 用枚舉值告訴系統(tǒng)視圖所支持的設備顯示方向,開發(fā)者需要實現(xiàn)一個supportedInterfaceOrientations的方法.
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}
Foundation框架中定義了一些輔助的宏,用這些宏定義枚舉時,可以指定用于保存枚舉值的底層數(shù)據(jù)類型.而且這些宏具備向后兼容能力,如果目標平臺的編譯器支持新標準,那么就使用新式語法,否則改用舊式語法.這些宏是用#define預處理指令來定義的.
- 普通的枚舉類型:
typedef NS_ENUM (NSUInteger, CWGConnectionState) {
CWGConnectionStateDisconnected,
CWGConnectionStateConnecting,
CWGConnectionStateConnected,
};
- 帶有一系列枚舉選項類型:
typedef NS_OPTIONS (NSUInteger, CWGPermittedDirection) {
CWGPermittedDirectionUp = 1 << 0;
CWGPermittedDirectionLeft = 1 << 1;
CWGPermittedDirectionDown = 1 << 2;
CWGPermittedDirectionRight = 1 << 3;
};
用NS_ENUM宏所定義的枚舉類型展開之后就是:
typedef enum CWGConnectionState : NSUInteger CWGConnectionState;
enum CWGConnectionState : NSUInteger {
CWGConnectionStateDisconnected,
CWGConnectionStateConnecting,
CWGConnectionStateConnected,
}
注意:根據(jù)是否要代碼按C++模式編譯,NS_OPTION宏的定義方式也有所不同,如果不按照C++編譯,那么其展開方式和NS_ENUM完全一樣.若按照C++編譯,則展開后的代碼略有不同.原因在于,用按位或運算來操作兩個枚舉值時,C++編譯模式的處理辦法和非C++模式不一樣.在用或運算符操作兩個枚舉值時,C++認為運算結果的數(shù)據(jù)類型應該是枚舉底層數(shù)據(jù)類型.也就是NSUInteger,而且C++不允許將這個底層類型"隱式轉(zhuǎn)換"為枚舉類型本身.所以,在C++模式下應該用另外一種方式定義NS_OPTION宏.以便省去類型轉(zhuǎn)換操作.鑒于此,凡是需要以按位或操作來組合枚舉都應使用NS_OPTION定義,若是枚舉不需要組合,則應該使用NS_ENUM來定義.
能使用到枚舉的情況還有很多,前面已經(jīng)提到了,枚舉可以表示選項和狀態(tài),然而還有許多東西也可以用枚舉來表示.比如狀態(tài)碼就是個很好的例子.可以吧邏輯含義相識的一組狀態(tài)碼放到一個枚舉集合里.而不要用#define預處理命令或常量來定義,以枚舉來表示樣式更適合.假如創(chuàng)建某個UI元素時可以使用不同的樣式,那么在這樣的情況下就最應該把樣式聲明為枚舉類型了.
最后再講一種枚舉的用法,就是在switch語句里.有時可以這樣定義:
typedef NS_ENUM (NSUInteger, CWGConnectionState) {
CWGConnectionStateDisconnected,
CWGConnectionStateConnecting,
CWGConnectionStateConnected,
};
switch (_currentState) {
CWGConnectionStateDisconnected:
// Handle connecting state
break;
CWGConnectionStateConnecting:
// Handle disconnected state
break;
CWGConnectionStateConnected:
// Handle connected state
break;
};
我們總習慣在switch語句中加上defaul分支,然后,若是用枚舉來定義狀態(tài)機,則最好不要有default分支,這樣的話, 如果稍后又加了一種狀態(tài),那么編譯器就會發(fā)出警告信息,提示新加入的狀態(tài)并未在switch分支中處理.假如寫上了default分支,那么它就會處理這個新狀態(tài),從而導致編譯器不發(fā)出警告信息.用NS_ENUM定義其他枚舉類型時也要注意此問題.例如:在定義代表UI元素樣式的枚舉時,通常要確保switch語句能正確處理所有樣式.
總結:
- 應該用枚舉來表示狀態(tài)機的狀態(tài),傳遞給方法的選項以及狀態(tài)碼等值,給這些值起個易懂的名字.
- 如果把傳遞給某個方法的選項表示為枚舉類型,而多個選項又可同時使用,那么就將各選項值定義為2的冪,以便通過按位或操作將其組合起來.
- 用NS_ENUM與NS_OPTIONS宏來定義枚舉類型.并指明其底層數(shù)據(jù)類型.這樣做可以確保枚舉是用開發(fā)者所選的底層數(shù)據(jù)類型實現(xiàn)出來.而不會采用編譯器所選的類型.
- 在處理枚舉類型的switch語句中不要實現(xiàn)default分支,這樣的話,加入枚舉之后,編譯器就會提示開發(fā)者:switch語句并未處理所有枚舉類型.