碰到問題
最近在看一本書奏属,書名叫《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》,其中談及了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;
宏定義
在此認識的基礎(chǔ)上,來看一下NS_ENUM
和NS_OPTIONS
這兩個宏的定義分別是什么毅该,
#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
只有一個入?yún)⒌臅r候,等同于__CF_ANON_ENUM(__VA_ARGS__)
朴爬,有兩個入?yún)⒌臅r候即寒,則等同于__CF_NAMED_ENUM(__VA_ARGS__)
,很快就能找這兩個宏的定義,
#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
的定義也在這里母赵,其中第一個#if的內(nèi)容并未深究逸爵,按書中所說,是用來判斷編譯器是否支持新的枚舉定義方式凹嘲。
可以發(fā)現(xiàn)师倔,CF_OPTIONS
和__CF_NAMED_ENUM
兩者在__cplusplus
為NO
的時候定義是一樣的,為YES
周蹭,定義才不一樣趋艘,按書上所說原因是:
在用或運算操作兩個枚舉值時,C++認為運算結(jié)果的數(shù)據(jù)類型應(yīng)該是枚舉的底層數(shù)據(jù)類型凶朗,也就是NSUInteger瓷胧。而且C++不允許將這個底層類型“隱式轉(zhuǎ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ù)類型進行隱式轉(zhuǎn)換的時候羊精,就會報錯斯够,通過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
是由一個NSUInteger
數(shù)據(jù)類型typedef
來的读规,而不是一個枚舉類型,以這種方式燃少,避免了“C++不允許數(shù)據(jù)類型隱式轉(zhuǎn)換”的問題束亏。
enum_extensibility
在preprocess出來的代碼中,發(fā)現(xiàn)了__attribute__((enum_extensibility(open)))
阵具,這個是干嘛用的碍遍,于是我查了一下資料,關(guān)于__attribute__
的黑魔法阳液,可以閱讀一下文章結(jié)尾給出的一些鏈接怕敬,此處針對enum_extensibility
做一些研究。
Xcode默認的編譯器是Clang帘皿,在Clang的文檔中东跪,我查到了enum_extensibility
的解釋。
Attribute
enum_extensibility
is used to distinguish between enum definitions that are extensible and those that are not. The attribute can take eitherclosed
oropen
as an argument.closed
indicates 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.open
indicates 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
的時候允許類型隱式轉(zhuǎn)換虽填,closed
的時候不允許類型隱式轉(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
}
代碼拷貝進.m文件涩惑,編譯,都不報錯桑驱;
將.m改成.mm竭恬,編譯,除了ce = A1;
和oe = B1;
熬的,其他都報錯痊硕;
跟例子里說的情況都不吻合,不知道是我理解錯誤押框,還是哪里不對岔绸,希望有看到的大神可以教教我。
總結(jié)
當(dāng)需要定義單一橡伞、不可組合的枚舉類型時盒揉,使用NS_ENUM
,當(dāng)需要定義非單一兑徘、可組合的枚舉行勒時刚盈,使用NS_OPTIONS
,這樣使用挂脑,在任何編譯模式下應(yīng)該都不會出錯藕漱。
對碰到的其他問題一知半解,主要原因是對各方面了解的比較少崭闲,還需要繼續(xù)努力學(xué)習(xí)肋联。
相關(guān)文章
黑魔法attribute((cleanup))
attribute 總結(jié)
Clang Attributes 黑魔法小記