第5条:用枚举表示状态、选项、状态码
1. 枚举类型的定义
枚举只是一种常量命名方式。某个对象所经历的各种状态就可以定义为一个简单的枚举集(编译器会为枚举集里的枚举各自分配一个独有的编号,从0开始,每个枚举递增1)。
然而,定义枚举变量的方式并不简洁:
// 声明一个名为EOCConnectionState的枚举
enum EOCConnectionState{
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
// 定义一个名为state的枚举变量
enum EOCConnectionState state = EOCConnectionStatedDisconnected;
可以通过typedef关键字重新定义枚举类型:
// 声明一个名为EOCConnectionState的枚举
enum EOCConnectionState{
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
// 通过typedef给enum EOCConnectionState起别名EOCConnectionState
typedef enum EOCConnectionState EOCConnectionState;
// 定义一个名为state的枚举变量
EOCConnectionState state = EOCConnectionStateDisconnected;
2. 指定枚举的底层数据类型
通过指定枚举的底层数据类型,可以向前声明枚举类型。如果编译器不清楚底层数据类型的大小,则在用到此枚举类型时,就不知道究竟该给变量分配多少空间。
*** 指定底层数据类型的语法 ***
enum EOCConnectionStateConnectionState : NSInteger {};
3. 枚举类型的其他用法
*** 3.1 手动指定某个枚举成员所对应的值 ***
enum EOCConnectionStateConnectionState {
EOCConnectionStateDisconnected = 1,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
EOCConnectionStatedDisconnected的值设为1,此后每个枚举的值递增1。
*** 3.2 选项 ***
/* UI框架中的UIViewAutoresizing枚举类型 */
enum {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
typedef NSUInteger UIViewAutoresizing;
/* UI框架中的UIInterfaceOrientationMask枚举类型 */
typedef enum : NSUInteger {
UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait ),
UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft ),
UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight ),
UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown ),
UIInterfaceOrientationMaskLandscape =
(UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight ),
UIInterfaceOrientationMaskAll =
(UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft |
UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown ),
UIInterfaceOrientationMaskAllButUpsideDown =
(UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft |
UIInterfaceOrientationMaskLandscapeRight ),
} UIInterfaceOrientationMask;
每个选项均可启用或禁用。用“按位或操作符”(“|”)可组合多个选项,而用“按位与操作符”(“&”)可判断出是否已启用某个选项。
*** 3.3 Foundation框架中辅助的宏——NS_ENUM和NS_OPTIONS ***
Foundation框架中定义了一些辅助的宏,用这些宏来定义枚举类型时,也可以指定用于保存枚举值的底层数据类型。这些宏是用#define预处理指令来定义的,其中,NS_ENUM用于定义像EOCConnectionState这种普通的枚举类型,而NS_OPTIONS用于定义像UIViewAutoresizing这种可以通过“按位或操作符”来组合选项的枚举类型。如:
/* NS_ENUM (所指定的底层数据类型, 枚举类型名称) */
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
/* NS_OPTIONS (所指定的底层数据类型, 枚举类型名称) */
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {
EOCPermittedDirectionUp = 1 << 0,
EOCPermittedDirectionDown = 1 << 1,
EOCPermittedDirectionLeft = 1 << 2,
EOCPermittedDirectionRight = 1 << 3,
};
NS_OPTIONS的定义若按C++模式编译,由于作为选项的枚举值经常需要用按位或运算来组合,所以想编译代码,需要将按位或操作的结果显示转换为枚举类型本身(EOCPermittedDirection)。
如:
EOCPermittedDirection permittedDirections = (EOCPermittedDirection)EOCPermittedDirectionUp | EOCPermittedDirectionLeft;
*** 3.4 状态码 ***
可以把逻辑含义相似的一组状态码放入同一个枚举集里,而不用#define预处理指令或常量来定义。
*** 3.5 样式 ***
创建某个UI元素时可以使用不同的样式,在这种情况下,最应该把样式声明为枚举类型。
*** 3.6 在switch语句里 ***
typedef NS_ENUM(NSUInteger, EOCConnectionState){
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
switch (_currentState){
EOCConnectionStateDisconnected:
// 实现代码
break;
EOCConnectionStateConnecting:
// 实现代码
break;
EOCConnectionStateConnected:
// 实现代码
break;
}
若是用枚举来定义状态机(state machine),则最好不要有default分支,并确保有所有可能出现的情况。
要点
- 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
- 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么久将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
- 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
- 在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。