Search code examples
objective-cenumsenumeration

Different ways of declaring enum in Objective C


Why are there so many different ways to declare enums in objective c? It's pretty confusing.

Is there any difference between the following or are they all the same?

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;

Solution

  • Is there any difference between the following or are they all the same?

    There are differences, some due to C - which underlies Objective-C - and one if you are thinking about importing your Objective-C code into Swift.

    Your first example:

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

    is an original C enum. This declares a type enum WeekDays, with an underlying type of int, and 5 literal values.

    Now C is not a strongly typed language: the literal values here do not have type enum WeekDays but rather are of type int; and variables of type enum WeekDays can be assigned values other than Monday thru Friday, and be assigned to int variables without casts or conversions. The first literal value (Monday here) will have the int value 0, and following ones will have a value one greater then the preceding literal (so here Friday has the value 4). Examples:

    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 
    

    All the enum variations are weak in this way. However many compilers, including those used by Xcode since at least v8, will issue warnings (not errors) in some cases, e.g. if a switch expression is an enum type and the case labels either omit some of the declared literals or have values outside the declared values.

    Giving the literals explicit values

    Rather than relying on the default values specific values can be given to enum literals. For example to give Monday the value 1 through to Friday having the value 5:

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

    Literals without explicit values are assigned one more than the preceding literal as usual. The values do not need to be contiguous either:

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

    Using a typedef

    Many C type declarations are complex and hard to parse (they are the stuff ion quiz questions), the C typedef allows an alias for a particular type to be declared. A typedef is often used with an enum declaration to avoid having to use enum when using the type. For example:

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

    declares the alias WeekDays to mean enum WeekDays. For example:

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

    Seeing WeekDays twice in the above declaration might be confusing, or simply too much typing for some people, and it can be omitted:

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

    This is slightly different than the previous declaration, WeekDays is now an alias for an anonymous enum. With such a type the first form of declaration is now invalid:

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

    Changing the underlying type

    By default the underlying type of an enum is int. A different integral type can be specified, the type must be large enough to hold all the values the literals represent. The integral types are the integer, unsigned integer and character types. The underlying type is specified by adding : <type> after the enum tag, and the literals can be given specific values of the same type. For example:

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

    as above an anonymous enum may be used:

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

    Using NS_ENUM

    NS_ENUM is an (Apple) Objective-C convenience macro which expands out to a typedef and an enum declaration. For example:

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

    expands to:

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

    which avoiding the forward declaration is equivalent to:

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

    Using Xcode v8, at least, there are no differences in compiler checks and warnings between using NS_ENUM and the above direct typedef.

    When NS_ENUM makes a difference

    If you are writing Objective-C which will be used by Swift then using NS_ENUM does make a difference: an enum declared using NS_ENUM will be imported as a Swift enum; whereas one declared directly will be imported as a Swift struct and a collection of global read-only computed properties, one per literal.

    Why this difference exists is not explained in the current Apple documentation (Using Swift with Cocoa and Objective-C (Swift 4.0.3), available through iBooks)

    Finally, back to your examples

    The above should enable you to figure out the differences:

    1. Default C enum, underlying type is int, type is used as enum WeekDays

    2. Aliased C enum, underlying type is NSUinteger, type is used as WeekDays

    3. NS_ENUM generated enum, underlying type is NSInteger, type can be referred to as either WeekDays or enum WeekDays

    4. This declares two distinct types and will probably produce warnings. The first is an anonymous enum without an alias so no variables can later be introduced with this type. The literals it introduces are of type int. The second is an alias for NSInteger. On 64-bit systems NSInteger is 64-bits, while int is 32-bits, so assignments between the literal type (int) and the "enum" type (WeekDays) may produce warnings. Further switch statements will not be checked as with the previous three versions, as they are simply based on NSInteger. This version looks like an incorrect attempt to emulate NS_ENUM and should not be used (it is invalid as such, just doesn't do what it suggests).

    Hopefully all that illuminates more than it confuses!