NS_ENUM和NS_OPTIONS

碰到問題

最近在看一本書,書名叫《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》,其中談及了NS_ENUMNS_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_ENUMNS_OPTIONS這兩個(gè)宏的定義分別是什么,

#define NS_ENUM(...) CF_ENUM(__VA_ARGS__)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)

再看看CF_ENUMCF_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兩者在__cplusplusNO的時(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_extensibility is used to distinguish between enum definitions that are extensible and those that are not. The attribute can take either closed or open 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 with flag_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的時(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 黑魔法小記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 顧名思義:NS_OPTIONS是多選項(xiàng),NS_ENUM是單選項(xiàng) 那么問題來了,具體編譯器是如何做到多選的呢? 我們...
    define南拳閱讀 955評(píng)論 0 3
  • Objective-C 里的枚舉有兩種類型:NS_ENUM 和 NS_OPTIONS,本質(zhì)上是一樣的,都是枚舉。 ...
    LeeJay閱讀 734評(píng)論 0 0
  • Enumeration Macros 在Apple的《Adopting Modern Objective-C》一文...
    AprSnow閱讀 680評(píng)論 0 0
  • NS_ENUM NS_ENUM 的第一個(gè)參數(shù)是用于存儲(chǔ)的新類型的類型。在64位環(huán)境下,UITableViewCel...
    SoDoIt閱讀 678評(píng)論 0 0
  • NS_ENUM和NS_OPTIONS區(qū)別 NS_ENUM NS_ENUM的第一個(gè)參數(shù)是用于存儲(chǔ)的新類型的類型。在6...
    著魔的毛豆閱讀 610評(píng)論 0 0

友情鏈接更多精彩內(nèi)容