碰到問題
最近在看一本書,書名叫《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》,其中談及了NS_ENUM和NS_OPTIONS的區(qū)別,不是很能理解,直接上手來探究一番。
探究問題
使用場景
NS_ENUM通常用在單一、不可組合的狀態(tài),例如狀態(tài)欄的樣式
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
UIStatusBarStyleDefault = 0, // Dark content, for use on light backgrounds
UIStatusBarStyleLightContent NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark backgrounds
UIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,
UIStatusBarStyleBlackOpaque NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,
} __TVOS_PROHIBITED;
而 NS_OPTIONS則用在非單一、可組合的狀態(tài),例如屏幕方向
typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
} __TVOS_PROHIBITED;
宏定義
在此認(rèn)識(shí)的基礎(chǔ)上,來看一下NS_ENUM和NS_OPTIONS這兩個(gè)宏的定義分別是什么,
#define NS_ENUM(...) CF_ENUM(__VA_ARGS__)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)
再看看CF_ENUM和CF_OPTIONS分別是什么,CF_ENUM中間還多了一層,先來看一下CF_ENUM的定義,
#define CF_ENUM(...) __CF_ENUM_GET_MACRO(__VA_ARGS__, __CF_NAMED_ENUM, __CF_ANON_ENUM, )(__VA_ARGS__)
結(jié)合__CF_ENUM_GET_MACRO的定義來看,
#define __CF_ENUM_GET_MACRO(_1, _2, NAME, ...) NAME
不難理解,當(dāng)CF_ENUM只有一個(gè)入?yún)⒌臅r(shí)候,等同于__CF_ANON_ENUM(__VA_ARGS__),有兩個(gè)入?yún)⒌臅r(shí)候,則等同于__CF_NAMED_ENUM(__VA_ARGS__),很快就能找這兩個(gè)宏的定義,
#if (__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))
#define __CF_NAMED_ENUM(_type, _name) enum __CF_ENUM_ATTRIBUTES _name : _type _name; enum _name : _type
#define __CF_ANON_ENUM(_type) enum __CF_ENUM_ATTRIBUTES : _type
#if (__cplusplus)
#define CF_OPTIONS(_type, _name) _type _name; enum __CF_OPTIONS_ATTRIBUTES : _type
#else
#define CF_OPTIONS(_type, _name) enum __CF_OPTIONS_ATTRIBUTES _name : _type _name; enum _name : _type
#endif
#else
#define __CF_NAMED_ENUM(_type, _name) _type _name; enum
#define __CF_ANON_ENUM(_type) enum
#define CF_OPTIONS(_type, _name) _type _name; enum
#endif
發(fā)現(xiàn)CF_OPTIONS的定義也在這里,其中第一個(gè)#if的內(nèi)容并未深究,按書中所說,是用來判斷編譯器是否支持新的枚舉定義方式。
可以發(fā)現(xiàn),CF_OPTIONS和__CF_NAMED_ENUM兩者在__cplusplus為NO的時(shí)候定義是一樣的,為YES,定義才不一樣,按書上所說原因是:
在用或運(yùn)算操作兩個(gè)枚舉值時(shí),C++認(rèn)為運(yùn)算結(jié)果的數(shù)據(jù)類型應(yīng)該是枚舉的底層數(shù)據(jù)類型,也就是NSUInteger。而且C++不允許將這個(gè)底層類型“隱式轉(zhuǎn)換”為枚舉類型本身。
驗(yàn)證一下
在.m文件中添加如下代碼,
typedef NS_ENUM(NSUInteger, MyType) {
MyType1,
MyType2,
MyType3,
};
typedef NS_OPTIONS(NSUInteger, MyState){
MyState1 = 1 << 0,
MyState2 = 1 << 1,
MyState3 = 1 << 2,
};
任意一處添加如下代碼,并編譯,
MyType type;
MyState state;
type = 1; //no warning
type = MyType1 | MyType2; //no warning
state = 1; //no warning
state = MyState2 | MyState3; //no warning
將.m文件改成.mm文件,再次編譯,
MyType type;
MyState state;
type = 1; //warning
type = MyType1 | MyType2; //warning
state = 1; //no warning
state = MyState2 | MyState3; //no warning
由此可見,用NS_ENUM定義的枚舉,在C++模式下,在碰到需要數(shù)據(jù)類型進(jìn)行隱式轉(zhuǎn)換的時(shí)候,就會(huì)報(bào)錯(cuò),通過Product--Perform Action--Preprocess來看一下兩者在C++編譯模式下的區(qū)別,
typedef enum __attribute__((enum_extensibility(open))) MyType : NSUInteger MyType; enum MyType : NSUInteger {
MyType1,
MyType2,
MyType3,
};
typedef NSUInteger MyState; enum __attribute__((flag_enum,enum_extensibility(open))) : NSUInteger{
MyState1 = 1 << 0,
MyState2 = 1 << 1,
MyState3 = 1 << 2,
};
可以看出,MyState是由一個(gè)NSUInteger數(shù)據(jù)類型typedef來的,而不是一個(gè)枚舉類型,以這種方式,避免了“C++不允許數(shù)據(jù)類型隱式轉(zhuǎn)換”的問題。
enum_extensibility
在preprocess出來的代碼中,發(fā)現(xiàn)了__attribute__((enum_extensibility(open))),這個(gè)是干嘛用的,于是我查了一下資料,關(guān)于__attribute__的黑魔法,可以閱讀一下文章結(jié)尾給出的一些鏈接,此處針對(duì)enum_extensibility做一些研究。
Xcode默認(rèn)的編譯器是Clang,在Clang的文檔中,我查到了enum_extensibility的解釋。
Attribute
enum_extensibilityis used to distinguish between enum definitions that are extensible and those that are not. The attribute can take eitherclosedoropenas an argument.closedindicates a variable of the enum type takes a value that corresponds to one of the enumerators listed in the enum definition or, when the enum is annotated withflag_enum, a value that can be constructed using values corresponding to the enumerators.openindicates a variable of the enum type can take any values allowed by the standard and instructs clang to be more lenient when issuing warnings.
簡而言之,就是open的時(shí)候允許類型隱式轉(zhuǎn)換,closed的時(shí)候不允許類型隱式轉(zhuǎn)換,并給出了例子,如下,
enum __attribute__((enum_extensibility(closed))) ClosedEnum {
A0, A1
};
enum __attribute__((enum_extensibility(open))) OpenEnum {
B0, B1
};
enum __attribute__((enum_extensibility(closed),flag_enum)) ClosedFlagEnum {
C0 = 1 << 0, C1 = 1 << 1
};
enum __attribute__((enum_extensibility(open),flag_enum)) OpenFlagEnum {
D0 = 1 << 0, D1 = 1 << 1
};
void foo1() {
enum ClosedEnum ce;
enum OpenEnum oe;
enum ClosedFlagEnum cfe;
enum OpenFlagEnum ofe;
ce = A1; // no warnings
ce = 100; // warning issued
oe = B1; // no warnings
oe = 100; // no warnings
cfe = C0 | C1; // no warnings
cfe = C0 | C1 | 4; // warning issued
ofe = D0 | D1; // no warnings
ofe = D0 | D1 | 4; // no warnings
}
代碼拷貝進(jìn).m文件,編譯,都不報(bào)錯(cuò);
將.m改成.mm,編譯,除了ce = A1;和oe = B1;,其他都報(bào)錯(cuò);
跟例子里說的情況都不吻合,不知道是我理解錯(cuò)誤,還是哪里不對(duì),希望有看到的大神可以教教我。
總結(jié)
當(dāng)需要定義單一、不可組合的枚舉類型時(shí),使用NS_ENUM,當(dāng)需要定義非單一、可組合的枚舉行勒時(shí),使用NS_OPTIONS,這樣使用,在任何編譯模式下應(yīng)該都不會(huì)出錯(cuò)。
對(duì)碰到的其他問題一知半解,主要原因是對(duì)各方面了解的比較少,還需要繼續(xù)努力學(xué)習(xí)。
相關(guān)文章
黑魔法attribute((cleanup))
attribute 總結(jié)
Clang Attributes 黑魔法小記