本文要点betway体育app:,也就是枚举

原文链接

由于OC基于C语言,所以C语言有的功能它都有.枚举(enum)就是其中之一.系统框架中频繁使用枚举,在用一系列常量来表示错误状态码或可组合的选项是,极宜用枚举为其命名.由于C++11标准扩充了枚举的特性,所以最新版系统框架使用了"强类型"的枚举.OC也得益于C++11标准.

前言

本文要点:

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

Enum,枚举,相信大部分编程语言都有对应的枚举类型,功能可能有多有少,但是枚举最核心的功能是 “规范的定义代码中的<b>状态、状态码、选项</b>”。

枚举只是一种常量的命名方式,某个对象所经历的各种状态就是可以定义为一个简单的枚举集合.

Enum,也就是枚举,从C语言开始就有了,C++、Java、Objective-C、Swift这些语言,当然都有对应的枚举类型,功能可能有多有少,但是最核心的还是一个—-规范的定义代码中的状态、选项等“常量”。

  • 多用类型常量,少用#define预处理指令
  • 用枚举表示状态、选项、状态码

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;

状态、状态码、选项

什么是状态:同时只能出现一个值,比如这个ScrollView里的枚举:

typedef NS_ENUM(NSInteger, UIScrollViewKeyboardDismissMode) { UIScrollViewKeyboardDismissModeNone, UIScrollViewKeyboardDismissModeOnDrag, UIScrollViewKeyboardDismissModeInteractive};

什么是选项:同时可以出现一个或多个值:

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};
  1. 苹果推荐。
  2. 大家都这样写。
  3. 其他的不好学。
  4. <b>以上的都不重要,看我的证明就行了</b>。
enum UIAlertActionStyle {
    UIAlertActionStyleDefault,
    UIAlertActionStyleCancel,
    UIAlertActionStyleDestructive
};

本节的内容就是如何正确的使用枚举。

多用类型常量,少用#define预处理指令

2. 指定枚举的底层数据类型

通过指定枚举的底层数据类型,可以向前声明枚举类型。如果编译器不清楚底层数据类型的大小,则在用到此枚举类型时,就不知道究竟该给变量分配多少空间。

*** 指定底层数据类型的语法 ***

enum EOCConnectionStateConnectionState : NSInteger {}; 

*** 在向前声明时指定底层数据类型的语法 ***

enum EOCConnectionStateConnectionState : NSInteger; 

错误姿势

#define UIScrollViewKeyboardDismissModeNone 0#define UIScrollViewKeyboardDismissModeOnDrag 1 #define UIScrollViewKeyboardDismissModeInteractive 2// 接收值就得这样:int UIScrollViewKeyboardDismissMode = UIScrollViewKeyboardDismissModeNone;

为什么这样做不好:

  1. 宏定义没有类型约束,只是单纯的替换。
  • 可读性较差。
  • 可以用int也可以用UIInteger来接收。

由于每种状态都有一个便于理解的值来表示,所以这样写出来的代码更易读懂.编译器会为枚举分配一个独有的编号,从0开始,每个枚举递增1.实现枚举所用的数据类型取决于编译器,不过其二进制位的个数必须能完全表示下枚举编号才行.

状态与选项的区别(states and options)

使用#define的弊端

场景:编写代码时候我们经常要定义常量,例如我们在编写一段动画的逻辑,我们把一个动画的时常提取为常量:

#define ANIMAITON_DURATION 0.3

上面的编写姿势是不规范的,Why? 如下:
#define预处理指令定义常量有两个弊端:

  • #define定义的常量没有类型信息 上述代码我们采用#define预处理指令,会把源码中ANIMAITON_DURATION字符串全部替换为 0.3,不过我们却无法获知 0.3的类型信息,这会使我们开发中产生迷惑
  • #define定义的常量可能会在无意中被人修改了,如果有人重新定义了常量,原有的值会被覆盖
#define ANIMAITON_DURATION 0.3

@implementation ViewController
- (void)viewDidLoad {
   [super viewDidLoad];
// 重新定义了 ANIMAITON_DURATION
#define ANIMAITON_DURATION 0.4
   float value = 1.0 + ANIMAITON_DURATION;
   NSLog(@"%f",value);
}

如上输出结果是 1.4,原有的值0.3被覆盖了

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分支,并确保有所有可能出现的情况。

正确姿势

typedef enum { UIScrollViewKeyboardDismissModeNone = 0, UIScrollViewKeyboardDismissModeOnDrag, UIScrollViewKeyboardDismissModeInteractive} UIScrollViewKeyboardDismissMode;// 指明枚举类型UIScrollViewKeyboardDismissMode mode = UIScrollViewKeyboardDismissModeNone;

用起来就是这样的:

- dealWithMode:(UIScrollViewKeyboardDismissMode)mode { switch  { case UIScrollViewKeyboardDismissModeNone: //... break; case UIScrollViewKeyboardDismissModeOnDrag: //... break; case UIScrollViewKeyboardDismissModeInteractive: //... break; }}

这样枚举解决了上面1和2的缺点,但是第3点还是没有解决,我依然可以这样搞:

int mode = UIScrollViewKeyboardDismissModeNone;// orUIInteger mode = UIScrollViewKeyboardDismissModeNone;

然而定义枚举变量的方式却不太简洁:

在用 enum 之前,我个人觉得,区分一下状态和选项的概念还是很必要的。

定义常量正确的姿势

定义常量的正确的姿势: 使用类型常量

static const NSTimeInterval KAnimationDuration 0.3

上述定义的类型常量包含了类型信息,清楚的描述了常量的定义
具体使用:

  • 私有常量

如果不打算公开某个常量,比如该变量的使用,应把常量定义在常量的编译单元(tanslation unit,也就是.m文件)内,并建议在前面加上k字母;
变量同时用 static 和const 修饰,const表示该变量的值不可修改,static修饰表示该变量仅在定义该变量的编译单元内可见,

例子:MBProgressHUD
MBProgressHUD.m定义了关于间距,字体大小的私有常量

static const CGFloat kPadding = 4.f;
static const CGFloat kLabelFontSize = 16.f;
static const CGFloat kDetailsLabelFontSize = 12.f;
  • 公开常量

    有时候我们需要对外公开某个常量,比方说,你可能在类代码中调用了NSNotificationCenter派发通知,派发通知的时候,需要使用字符串常量标识通知的名称,而这个名称需要声明为一个外界可见的常量变量,这样注册者可以方便的调用;
    公开常量在头文件中用extern声明,并在相关实现文件中定义其值;
    公开常量放在全局符号表中(global symbol table),命名时候通常以与之相关的类名作为前缀,以避免名称冲突

例子: SVProgressHUD
SVProgressHUD.m文件中定义了与SVProgressHUD状态相关的几个通知

NSString * const SVProgressHUDDidReceiveTouchEventNotification = @"SVProgressHUDDidReceiveTouchEventNotification";
NSString * const SVProgressHUDDidTouchDownInsideNotification = @"SVProgressHUDDidTouchDownInsideNotification";
NSString * const SVProgressHUDWillDisappearNotification = @"SVProgressHUDWillDisappearNotification";
NSString * const SVProgressHUDDidDisappearNotification = @"SVProgressHUDDidDisappearNotification"

SVProgressHUD.h文件对外公开了这些常量

extern NSString * const SVProgressHUDDidReceiveTouchEventNotification;
extern NSString * const SVProgressHUDDidTouchDownInsideNotification;
extern NSString * const SVProgressHUDWillDisappearNotification;
extern NSString * const SVProgressHUDDidDisappearNotification;
extern NSString * const SVProgressHUDWillAppearNotification;
extern NSString * const SVProgressHUDDidAppearNotification;

注意const修饰的字符在常量类型的位置,常量定义应该从右往左边读,所以上述SVProgressHUDDidReceiveTouchEventNotification 是一个常量,而这个常量是一个指针,指向NSString对象,我们需要用const锁定这个指针

要点

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

完美姿势

NS_ENUM是枚举在Objective-C中的"升级版",改进后代码如下:

typedef NS_ENUM(NSInteger, UIScrollViewKeyboardDismissMode) { UIScrollViewKeyboardDismissModeNone, UIScrollViewKeyboardDismissModeOnDrag, UIScrollViewKeyboardDismissModeInteractive};

代码很简单,不理解的话可以评论求解答。

如果有一个"选项变量"要同时表示一个或者多个值肿么办?这时就得上我们的NS_OPTIONS了。比如有这样一个简单的例子:

typedef NS_OPTIONS(NSUInteger, JZZPersonType) { JZZPersonTypeHigh = 0, JZZPersonTypeRich = 1 << 0, JZZPersonTypePretty = 1 << 1};

可以看到,我们这里的选项是用的<b>位运算</b>的方式定义的,这样的好处就是我们的选项变量可以这样来表示:

// 用“或|”运算同时赋值多个选项JZZPersonType personType = JZZPersonTypeHigh | JZZPersonTypeRich | JZZPersonTypePretty;// 用“与&”运算取出对应位if (personType & JZZPersonTypeHigh) { NSLog;}if (personType & JZZPersonTypeRich) { NSLog;}if (personType & JZZPersonTypePretty) { NSLog;}

“|”赋值完后,富帅的内心其实是长这样的:

betway体育app 1富帅的内心

富帅点个赞呗。

作者 @biggergao

2016年05月22日

enum  UIAlertActionStyle  state =  UIAlertActionStyleDefault;

状态,同时只能有一种,如“ OK ”,“ Error ”,不可能同时是OK和Error。

用枚举表示状态、选项、状态码

参考文献

NS_ENUM & NS_OPTIONS
位操作 维基百科

要是每次不用敲enum,只写UIAlertActionStyle就爽了.于是可以这样写:

选项,同时可以有一种或一种以上,如App可以 同时 支持横屏和竖屏,横屏竖屏在这个时候就是“屏幕方向”的两种不同的选项。

C语言枚举语法回顾
enum EOCConnectionState {
 EOCConnectionStateDisconnected,
 EOCConnectionStateConnecting,
 EOCConnectionStateDisconnected,
}

定义枚举变量的方式:

enum EOCConnectionState state = EOCConnectionStateDisconnected;

上述的语法不太简洁,我们可以使用typedef重新定义枚举类型

typedef enum EOCConnectionState = EOCConnectionState
 // 使用EOCConnectionState代替enum EOCConnectionState 
EOCConnectionState state = EOCConnectionStateDisconnected

我们还可以指定枚举使用何种底层数据类型如下,并且手工指定枚举成员的值

enum EOCConnectionState:NSUInteger {
 EOCConnectionStateDisconnected = 1,
 EOCConnectionStateConnecting,
 EOCConnectionStateDisconnected,
}

如上指定了枚举的底层数据类型是NSUInteger,而且手动指定了枚举成员初始值为1
如果枚举成员的值可以组合,我们一般用位移来表示,如下

enum EOCConnectionState:NSUInteger {
 EOCPerimttedDirectionUP = 1<<0, 
 EOCPerimttedDirectionDown = 1<<1, 
 EOCPerimttedDirectionLeft = 1<<2,
 EOCPerimttedDirectionRight= 1<<3,
}
1 << 0 二进制 : 00000001   十进制 1
1 << 1 二进制 : 00000010   十进制 2
1 << 2 二进制 : 00001000   十进制 4
1 << 3 二进制 : 00010000   十进制 8

使用位移指定枚举的值得好处是我们可以方便使用位操作符 | 和 &操作 枚举成员,
|操作:如果我们要组合两个枚举成员,我们可以用 |操作:

enum EOCConnectionState statues = EOCConnectionStateDisconnected | EOCConnectionStateConnecting 

上述代码的执行过程:

二进制       十进制
00000001      1         EOCConnectionStateDisconnected
00000010      2          EOCConnectionStateConnecting
00000011      3          EOCConnect ionStateDisconnected|EOCConnectionStateConnecting

&操作:使用 &位操作符判断是否启用某个选项:

if(status & EOCConnectionStateDisconnected) {   
//EOCConnectionStateDisconnected is set 
}
enum UIAlertActionStyle {
    UIAlertActionStyleDefault,
    UIAlertActionStyleCancel,
    UIAlertActionStyleDestructive
};
typedef enum UIAlertActionStyle  UIAlertActionStyle;

接下来,我们看看如何用枚举定义状态和选项。

Objcet-C中枚举

typedof NS_NUM (NSUInteger,EOCConnectionState) {
 EOCConnectionStateDisconnected ,
 EOCConnectionStateConnecting,
 EOCConnectionStateDisconnected,
};

NS_NUM( _type, _name)是一个辅助宏,上面的语法展开如下,实际上就是C语言的枚举定义方式:

typedf enum EOCConnectionState:NSUInteger EOCConnectionState;
enum EOCConnectionState:NSUInteger { 
EOCConnectionStateDisconnected , 
EOCConnectionStateConnecting, 
EOCConnectionStateDisconnected,
};

这里要注意,当需要枚举成员组合成新值得时候,也就是按位操作枚举成员的时候,我们一般使用NS_OPTIONS来定义枚举成员,与使用NS_NUM相比,NS_OPTIONS会帮我们在进行枚举成员的位运算过程中做隐式转换,保证枚举成员进行位运算的正确性

typedof NS_OPTIONS (NSUInteger,EOCPerimtted) {
 EOCPerimttedDirectionUP = 1<<0, 
 EOCPerimttedDirectionDown = 1<<1, 
 EOCPerimttedDirectionLeft = 1<<2,
 EOCPerimttedDirectionRight= 1<<3,
};

现在就可以这样定义枚举变量了:

enum与状态(states)

使用场景:

在表示一系列逻辑相似的状态,选项,我们应考虑使用枚举的方式,
我们可以在SVProgressHUD这个项目的SVProgressHUD.h中找到如下的代码

typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
   SVProgressHUDMaskTypeNone = 1, // allow user interactions while HUD is displayed
   SVProgressHUDMaskTypeClear,     // don't allow user interactions
   SVProgressHUDMaskTypeBlack,     // don't allow user interactions and dim the UI in the back of the HUD
   SVProgressHUDMaskTypeGradient   // don't allow user interactions and dim the UI with a a-la-alert-view background gradient
};

上面分别代表了SVProgressHUD几种显示样式

 UIAlertActionStyle  state =  UIAlertActionStyleDefault;

不好的做法

使用建议:

我们使用switch语法会习惯加上default分支,但在使用switch处理枚举类型数据相关逻辑时候,建议去掉default分值,这样做的好处是假如某个枚举值没有处理,编译器就会发出警告,提醒我们对所有的枚举选项都进行处理

typedef NS_ENUM (NSUInteger, CWGConnectionState) {
   CWGConnectionStateDisconnected,
   CWGConnectionStateConnecting,
   CWGConnectionStateConnected,
};
switch (_currentState) {
 CWGConnectionStateDisconnected:
 // Handle connecting state
     break;
 CWGConnectionStateConnecting:
 // Handle disconnected state
     break;
 CWGConnectionStateConnected:
 // Handle connected state
     break;
};

C++11标准修订了枚举的某些特性,其中一项改动是:可以指明用何种"底层数据类型"来保存枚举类型的变量.这样做的好处是,可以向前声明枚举变量了.若不指定底层数据类型,则无法向前声明枚举类型.因为编译器不知道底层数据类型的大小,所以在用到此枚举类型时,也就不知道该给变量分配多少内存空间.

经常看到这样的写法:

指定底层数据类型所有的语法是:

#define STATE_OK 0

enum CWGConnectionStateConnectionState: NSInteger {/* ... */};

#define STATE_ERROR 1

这样就保证了枚举底层数据类型是NSinteger.也可以在向前声明时指定底层数据类型:

#define STATE_UNKNOW 2

enum  CWGConnectionStateConnectionState: NSInteger;

//直接用int型变量接收

还可以不使用编译器所分配的序号,而是手工指定某个枚举成员所对应的值.

int STATE = STATE_UNKNOW;

enum CWGConnectionStateConnectionState {
  CWGConnectionStateDisconnected = 1,
  CWGConnectionStateConnecting,
  CWGConnectionStateConnected,
};

这样做有如下“不恰当”:

这样的话,上面中的每一个枚举变量的值就是CWGConnectionStateConnecting值为2,CWGConnectionStateConnected值为3.

宏定义没有类型约束,只是单纯的替换。

本文由必威发布于必威-编程,转载请注明出处:本文要点betway体育app:,也就是枚举

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。