[iOS-Objective-C] 枚举

参考资料

《编写高质量iOS与OS X代码的52个有效方法》中第5条:用枚举表示状态、选项、状态码

枚举类型的基本使用

枚举的作用在于规范并语义化的定义代码中的状态、选项等常量。枚举类型的定义以关键字enum开头,之后是枚举数据类型的名称,最后是在一对花括号内的选项标识符序列。 编译器会为枚举分配一个独有的编号,从0开始,每个枚举递增1。

enum  Direction {up, down, left, right};

实现枚举所用的数据类型取决于编译器。除了使用默认类型,还可以指明用何种“底层数据类型”来保存枚举类型的变量。

enum Direction : UNSInteger {up, down, left, right};

还可以不使用编译器所分配的序号,而是手工指定某个枚举成员所对应的值。如下例中将 up 的值设为1,而不使用编译器所分配的0。接下来的枚举值会在上一个的基础上递增1。

enum Direction {up = 1, down, left, right};

枚举的另一种使用方式是定义为按位掩码,当定义选项的时候,若这些选项可以彼此组合,则在设置特定的枚举值后,各选项间就可通过“按位或”来组合。因为每个枚举值所对应的二进制表示中,只有1个二进制位的值是1,所以多个选项“按位或”后的组合值是唯一的,且将某一选项与组合值做“按位与”操作,即可判断出组合值中是否包含该选项。

enum Direction {
   up = 1 << 0, 
   down = 1 << 1, 
   left = 1 << 2, 
   right = 1 << 3
};

使用关键字 typedef 重新定义枚举

然而声明或定义一个枚举类型变量的方式却不太简洁,仍然需要使用enum关键字,如:

enum  Direction {up, down, left, right};
enum Direction var1;

enum {up, down, left, right} var1;

为了简化枚举的声明,不需要每次都写enum,可以使用关键字typedef重新定义枚举类型

enum {up, down, left, right} Direction;
typedef enum Direction Direction;

之后就可以直接用 Direction 来代替完整的 enum Direction 了:

Direction var1, var2;

使用枚举的正确姿势

苹果对 Objective-C 语言支持了两个有关枚举的辅助弘,分别是NS_ENUMNS_OPTIONSNS_ENUM用来定义普通的枚举类型,NS_OPTIONS用来定义可组合选项的枚举类型。

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
      UIViewAutoresizingNone = 0,
      UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
      UIViewAutoresizingFlexibleWidth = 1 << 1,
      UIViewAutoresizingFlexibleRightMargin = 1 << 2,
      UIViewAutoresizingFlexibleTopMargin = 1 << 3,
      UIViewAutoresizingFlexibleHeight = 1 << 4,
      UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

这些宏是用#define预处理指令来定义的,具备向后兼容能力,如果目标平台的编译器支持新标准,那就使用新式语法,否则改用旧式语法。具体的定义如下:

#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_ENUMNS_OPTIONS都使用相同的老式语法来定义枚举:

typedef NSInteger Direction;
enum {up, down, left, right};

但是,旧式的语法无法告诉编译器重新定义的枚举类型与之后 enum 定义的整形值之间的联系。如果支持新特性,那么NS_ENUM宏所定义的枚举类型展开之后就是:

typedef enum UITableViewCellStyle : NSInteger UITableViewCellStyle;
enum UITableViewCellStyle : NSInteger {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

根据是否要将代码按 C++ 编译,NS_OPTIONS宏的定义方式也有所不同。如果不按 C++ 编译,那么其展开方式就和NS_ENUM相同。若按 C++ 编译,则展开后的代码略有不同。原因在于,用按位或运算来操作两个枚举值时, C++ 编译模式的处理与非 C++ 模式不一样。在用或运算操作两个枚举值时, C++ 认为运算结果的数据类型应该是枚举的底层数据类型,而 C++ 不允许将这个底层类型“隐式转换”为枚举类型本身。如果想编译通过,就要将按位或操作的结果“显式转换”为枚举类型。所以,在 C++ 模式下应该用另一种方式定义NS_OPTIONS宏,以便省去类型转换操作。

鉴于此,凡是需要以按位或操作来组合的枚举都应使用NS_OPTIONS定义。若是枚举不需要互相组合,则应使用NS_ENUM来定义。

枚举在 switch 语句中的应用

当在 switch 语句中使用 enum 值时,不建议添加 default 分支。因为 enum 的选项分支已经包含了所有可能的情况,并不需要额外处理默认情况。而且,当之后添加新的状态时,编译器还会发出警告信息,提示新加入的状态并未在 switch 分支中处理。

推荐阅读更多精彩内容