谈谈位枚举NS_OPTIONS

Author Avatar
纸简书生 4月 22, 2017

给别人讲问题的时候遇到类似的,顺便就复习下。

谈谈位枚举NS_OPTIONS

在iOS开发中枚举大家用得最多的应该是NS_ENUM。NS_ENUM也没什么好讲的。主要来讲讲位枚举NS_OPTIONS。

下面是他们在Foundation.framework的NSObjCRuntime.h的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#if (__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#if (__cplusplus)
#define NS_OPTIONS(_type, _name) _type _name; enum : _type
#else
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#else
#define NS_ENUM(_type, _name) _type _name; enum
#define NS_OPTIONS(_type, _name) _type _name; enum
#endif

其实从枚举定义来看,NS_ENUM和NS_OPTIONS本质是一样的,仅仅从字面上来区分其用途。NS_ENUM是通用情况,NS_OPTIONS一般用来定义具有位移操作或特点的情况(bitmask掩码)。

与、或操作

先讲讲关于二进制的位于操作。可能好多的同学都有些忘了。这里复习下。

位操作 解释 例子 应用
按位与(&) 有0则0,当俩位同时为1时返回1. 4=0000 0000 0000 0100 &7 =0000 0000 0000 0111= 0000 0000 0000 0100 1.清零
2.获取一个数据的指定位
3.保留数据区的特定位
按位或(` `) 有1则1,只要有一位为1则即可返回1. 5 = 0000 0000 0000 0101` ` 7= 0000 0000 0000 0111=0000 0000 0000 0111 设定一个数据的指定位

<<:左移运算符。左移k位 相当于 *2^k
>>:右移运算符。右移k位 相当于/2^k

实例

NS_OPTIONS位枚举的特点是可以使用位运算来处理枚举值,实际使用中可以用一个变量存储多个枚举值,表示互不影响的多个设置。比如系统中的UIViewAutoresizing定义如下。

1
2
3
4
5
6
7
8
9
enum UIViewAutoresizing {
UIViewAutoresizingNone = 0, // 000000
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 000001
UIViewAutoresizingFlexibleWidth = 1 << 1,// 000010
UIViewAutoresizingFlexibleRightMargin = 1 << 2,// 000100
UIViewAutoresizingFlexibleTopMargin = 1 << 3,// 001000
UIViewAutoresizingFlexibleHeight = 1 << 4,// 010000
UIViewAutoresizingFlexibleBottomMargin = 1 << 5,// 100000
}

为了说明问题这里就用UIViewAutoresizing举个例子。上面把对应的二进制写在了后面。现在要实现视图的宽和高自适应,UIViewAutoResizingFlexibleWidth| UIViewAutoresizingFlexibleHeight。

一般会这样写:

1
UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

转换为二进制计算一下。

1
UIViewAutoresizing resizing = 000010 |010000 = 010010

这样就实现了变量保存多个枚举值。那么如何判断变量是否包含某个枚举值了,这的通过与操作。比如我要判断是否包含了UIViewAutoresizingFlexibleWidth

1
2
3
if (resizing & UIViewAutoresizingFlexibleWidth) {
// UIViewAutoresizingFlexibleWidth is set
}

转换为二进制:

1
2
3
if (010010 & 000010) {
// UIViewAutoresizingFlexibleWidth is set
}

010010 & 000010 = 000010 很明显不等于0。所以为YES。那么用一个不包含的试一下。

1
2
3
if (resizing & UIViewAutoresizingFlexibleTopMargin) {
// UIViewAutoresizingFlexibleTopMargin is set
}

转为二进制

1
2
3
if (010010 & 001000) {
// UIViewAutoresizingFlexibleTopMargin is set
}

很明显010010 & 001000 = 0,所以能够知道resizing不包含UIViewAutoresizingFlexibleTopMargin。

通过以上的这种方式实现了一个变量保存多个枚举值。也就是NS_OPTIONS的原理。

需要注意的地方

  1. 枚举命名方式尽量用系统的风格,比如枚举名为UIViewAutoresizing,具体的值为UIViewAutoresizingNone。简单来讲就是(枚举名+状态)
  2. 如果要实现一个变量保存多个枚举值就用NS_OPTIONS。个人觉得用NS_OPTIONS完全可以替代NS_ENUM。只需要在使用的时候不用与、或操作就可以了
  3. 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
  4. 在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。
  5. 预留一个枚举值,用于扩展或者用于表示没有的情况