在Objective C中声明枚举的不同方式

8

为什么在Objective C中声明枚举有这么多不同的方式?这相当令人困惑。

以下几种声明方式是否有区别,还是它们都是相同的?

enum WeekDays{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday
};

typedef enum : NSUInteger {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday
} WeekDays;

typedef NS_ENUM(NSInteger, WeekDays){
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday
};

enum {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday
};
typedef NSInteger WeekDays;

1
这个问题不仅限于Objective-C,而是C语言。只有NS_ENUM是Obj-C中特别可用的方便宏。也许你应该研究一下typedef的语义,而不仅仅关注于enum - cwschmidt
1
可能是typedef NS_ENUM vs typedef enum的重复问题。 - Willeke
@Willeke 谢谢分享链接。我现在明白了 NS_ENUM 和 enum 的区别,但是我仍然不确定在 enum 声明方面的差异。 - SilentK
你点击了 NSHipster 的链接吗? - Willeke
1个回答

23
以下是否有差异?还是它们都一样?
有区别,其中一些是由C导致的,C是Objective-C的基础,还有一个区别是如果您考虑将您的Objective-C代码导入Swift。
您的第一个例子:
enum WeekDays { Monday, ..., Friday };

这是一个原始的C语言enum。它声明了一个名为enum WeekDays类型,底层类型为int,并包含5个字面值

C语言不是强类型语言:这里的字面值并不是enum WeekDays类型的,而是int类型的;而且enum WeekDays类型的变量可以被赋予不是MondayFriday的值,并且可以被赋值给int类型的变量而无需进行强制转换。第一个字面值(这里是Monday)将具有int0,后续的字面值将比前一个字面值多1(因此Friday的值为4)。例如:

enum WeekDays one;
int two;

one = Monday;
two = Tuesday; // assigning enum literal to int is OK
two *= 42;     // will produce an int value outside of Monday thru Friday
one = two;     // which can be assigned to an enum WeekDays
one = 34;      // int values can also be directly assigned 

所有的enum变量都存在这种弱点。然而,包括自Xcode至少v8以来使用的编译器在内的许多编译器,在某些情况下会发出警告(而不是错误),例如如果switch表达式是一个enum类型,并且case标签省略了一些声明的字面值或者具有声明值之外的值。

给字面值赋予显式的值

与其依靠默认值,可以为enum字面值指定特定的值。例如,将Monday赋值为1,将Friday赋值为5

enum WeekDays { Monday = 1, ..., Friday };

通常情况下,没有显式值的文本将被分配比先前的文本字面量多一的值。这些值也不需要连续:

enum WeekDays { Monday = 1, Tuesday = 42, Wednesday = 3, Thursday, Friday };

使用 typedef

很多C语言类型声明很复杂,难以解析(它们是小测验问题的内容),C语言typedef允许声明一个特定类型的别名。 在使用类型时,typedef通常与enum声明一起使用,以避免使用enum. 例如:

typedef enum WeekDays { Monday, ..., Friday } WeekDays;

声明别名WeekDays代表enum WeekDays。例如:

WeekDays one;      // means exactly the same as the previous
                   // declaration for one
enum WeekDays one; // also valid and means the same thing

在上述声明中两次看到WeekDays可能会让人感到困惑,或者对某些人来说输入太多了,可以省略:


typedef enum { Monday, ..., Friday } WeekDays;

这与前面的声明稍有不同,WeekDays现在是一个 匿名enum别名。使用这种类型,第一种声明形式现在已经无效:

enum Weekdays one; // invalid, no such enum
WeekDays one;      // valid

更改底层类型

默认情况下,enum 的底层类型为 int。可以指定不同的整数类型,该类型必须足够大以容纳所有字面值表示的值。整数类型包括整数、无符号整数和字符类型。底层类型是通过在enum 标签后添加 :<type> 来指定的,并且可以为字面值指定相同类型的特定值。例如:

typedef enum Vowels : char { Letter_A = 'a', ..., Letter_U = 'u' } Vowels;

如上所述,可以使用匿名枚举:

typedef enum : char { Letter_A = 'a', ..., Letter_U = 'u' } Vowels;

使用 NS_ENUM

NS_ENUM是一个(苹果公司)Objective-C方便的宏,会扩展成一个typedefenum声明。例如:

typedef NS_ENUM(NSInteger, WeekDays) { Monday, ..., Friday };

扩展为:

typedef enum WeekDays : NSInteger WeekDays;
enum WeekDays : NSInteger  { Monday, ..., Friday };

避免前置声明等同于:

typedef enum WeekDays : NSInteger { Monday, ..., Friday } WeekDays;

使用Xcode v8或更高版本,使用NS_ENUM和直接使用typedef声明枚举类型时,在编译器检查和警告方面没有任何区别。

NS_ENUM的作用

如果您编写的Objective-C代码将被Swift使用,则使用NS_ENUM确实会有所不同:使用NS_ENUM 声明的枚举将被导入为Swift enum;而直接声明的枚举将被导入为Swift struct及其包含的全局只读计算属性集合。为每个枚举值按一个属性生成一个结构体。

苹果目前的文档(使用Swift与Cocoa和Objective-C(Swift 4.0.3),通过)中未解释这种差异的原因。

最后,回到你的例子

上述内容应该可以让你理解它们之间的差异:

  1. 默认的C枚举,底层类型是int,作为enum WeekDays类型使用。

  2. 别名C枚举,底层类型为NSUinteger,作为WeekDays类型使用。

  3. 使用NS_ENUM生成的枚举,底层类型为NSInteger,可以使用WeekDaysenum WeekDays引用此类型。

  4. 这声明了两个不同的类型,可能会产生警告。第一个是没有别名的匿名enum,因此稍后不能引入带有此类型的变量。它引入的值是int类型。第二个是NSInteger的别名。在64位系统上,NSInteger是64位的,而int是32位的,因此在字面值类型(int)和"枚举"类型(WeekDays)之间进行赋值可能会产生警告。进一步的switch语句将不像前三个版本一样被检查,因为它们仅基于NSInteger。该版本看起来像是错误地模仿NS_ENUM,不应该使用它(尽管合法,但并不实现它所暗示的功能)。

希望以上内容比混淆更加清楚明白!


哇,这是非常深入和完整的解释。非常感谢。 - SilentK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接