首页 > ios > 第5条:用枚举表示状态、选项、状态码(2)

第5条:用枚举表示状态、选项、状态码(2)

  系统库中频繁使用这个办法。iOS UI框架中的UIKit里还有个例子,用枚举值告诉系统视图所支持的设备显示方向。这个枚举类型叫做UIInterfaceOrientationMask,开发者需要实现一个名为supportedInterfaceOrientations的方法,将视图所支持的显示方向告诉系统:

- (NSUInteger)supportedInterfaceOrientations {  
  return UIInterfaceOrientationMaskPortrait |  
           UIInterfaceOrientationMaskLandscapeLeft;  
} 

  Foundation框架中定义了一些辅助的宏,用这些宏来定义枚举类型时,也可以指定用于保存枚举值的底层数据类型。这些宏具备向后兼容(backward compatibility)能力,如果目标平台的编译器支持新标准,那就使用新式语法,否则改用旧式语法。这些宏是用#define预处理指令来定义的,其中一个用于定义像EOCConnectionState这种普通的枚举类型,另一个用于定义像UIViewAutoresizing这种包含一系列选项的枚举类型,其用法如下:

typedef NS_ENUM(NSUInteger, EOCConnectionState) {  
    EOCConnectionStateDisconnected,  
    EOCConnectionStateConnecting,  
    EOCConnectionStateConnected,  
};
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {  
    EOCPermittedDirectionUp    = 1 << 0,  
    EOCPermittedDirectionDown  = 1 << 1,  
    EOCPermittedDirectionLeft  = 1 << 2,  
    EOCPermittedDirectionRight = 1 << 3,  
}; 

  这些宏的定义如下:

#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 

  由于需要分别处理不同情况,所以上述代码用多种方式来定义这两个宏。第一个#if用于判断编译器是否支持新式枚举。其中所用的布尔逻辑看上去相当复杂,不过其意思就是想判断编译器是否支持新的枚举特性。如果不支持,那么就用老式语法来定义枚举。
  如果支持新特性,那么用NS_ENUM宏所定义的枚举类型展开之后就是:

typedef enum EOCConnectionState : NSUInteger EOCConnectionState;  
enum EOCConnectionState : NSUInteger {  
    EOCConnectionStateDisconnected,  
    EOCConnectionStateConnecting,  
    EOCConnectionStateConnected,  
}; 

  根据是否要将代码按C++模式编译,NS_OPTIONS宏的定义方式也有所不同。如果不按C++编译,那么其展开方式就和NS_ENUM相同。若按C++编译,则展开后的代码略有不同。原因在于,用按位或运算来操作两个枚举值时,C++编译模式的处理办法与非C++模式不一样。而上面已经提到了,作为选项的枚举值经常需要用按位或运算来组合。在用或运算操作两个枚举值时,C++认为运算结果的数据类型应该是枚举的底层数据类型,也就是NSUInteger。而且C++不允许将这个底层类型“隐式转换”(implicit cast)为枚举类型本身。我们用EOCPermittedDirection来演示一下,假设按NS_ENUM方式将其展开:

typedef enum EOCPermittedDirection : int EOCPermittedDirection;  
enum EOCPermittedDirection : int {  
    EOCPermittedDirectionUp    = 1 << 0,  
    EOCPermittedDirectionDown  = 1 << 1,  
    EOCPermittedDirectionLeft  = 1 << 2,  
    EOCPermittedDirectionRight = 1 << 3,  
}; 

  然后考虑下列代码:

EOCPermittedDirection permittedDirections =  
    EOCPermittedDirectionLeft | EOCPermittedDirectionUp; 

  若编译器按C++模式编译(也可能是按Objective-C++模式编译),则会给出下列错误信息:

error: cannot initialize a variable of type  
'EOCPermittedDirection' with an rvalue of type 'int' 

  如果想编译这行代码,就要将按位或操作的结果显式转换(explicit cast)为EOCPermittedDirection。所以,在C++模式下应该用另一种方式定义NS_OPTIONS宏,以便省去类型转换操作。鉴于此,凡是需要以按位或操作来组合的枚举都应使用NS_OPTIONS定义。若是枚举不需要互相组合,则应使用NS_ENUM来定义。
  能够用到枚举的情况还有很多。前面已经提到,枚举可以表示选项与状态,然而还有许多东西也能用枚举来表示。比如状态码就是个好例子。可以把逻辑含义相似的一组状态码放入同一个枚举集里,而不要用#define预处理指令或常量来定义。以枚举来表示样式(style)也很合宜。假如创建某个UI元素时可以使用不同的样式,那么在这种情况下就最应该把样式声明为枚举类型了。
  最后再讲一种枚举的用法,就是在switch语句里。有时可以这样定义:

typedef NS_ENUM(NSUInteger, EOCConnectionState) {  
    EOCConnectionStateDisconnected,  
    EOCConnectionStateConnecting,  
    EOCConnectionStateConnected,  
};  
 switch (_currentState) {  
    EOCConnectionStateDisconnected:  
        // Handle disconnected state  
        break;  
    EOCConnectionStateConnecting:  
        // Handle connecting state  
        break;  
    EOCConnectionStateConnected:  
        // Handle connected state  
        break;  
} 

  我们总习惯在switch语句中加上default分支。然而,若是用枚举来定义状态机(state machine),则最好不要有default分支。这样的话,如果稍后又加了一种状态,那么编译器就会发出警告信息,提示新加入的状态并未在switch分支中处理。假如写上了default分支,那么它就会处理这个新状态,从而导致编译器不发警告信息。用NS_ENUM定义其他枚举类型时也要注意此问题。例如,在定义代表UI元素样式的枚举时,通常要确保switch语句能正确处理所有样式。

要点

  应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
  如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
  用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
  在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。

  1. 还没有评论
评论提交中, 请稍候...

留言


可以使用的标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Trackbacks & Pingbacks ( 0 )
  1. 还没有 trackbacks