Navigation

NS_ENUM and NS_OPTIONS

Tuesday, November 27th, 2012

If you, like most programmers, are curious and detail-oriented you may have noticed that this:

typedef enum {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
} UIViewAnimationTransition;

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

Has, in iOS 6, turned into this:

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

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
};

And maybe you even clicked through to the definition of NS_ENUM and NS_OPTIONS and saw this:

#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

At which point you almost certainly threw up your hands and went back to work.

Recently I had some free time so I broke down this enum situation and also included some of the history and present and also maybe even a little bit of the future of enumerated types in Objective-C.

The Way Things Used To Be

The iOS 5.1 definition of UIViewAnimationTransition is pretty standard and should be comprehensible to any C programmer. It defines an anonymous enum and immediately declares a typedef of that enum to a new type name. This allows the enum to be referenced using only the type name — were the enum to be named in the traditional way (enum UIViewAnimationTransition {) the enum keyword would have to be supplied (enum UIViewAnimationTransition) when declaring values of that type, e.g. in method signatures. The typedef-of-anonymous-enum pattern is standard practice in good, modern C code.

The iOS 5.1 definition of UIViewAutoresizing is a little more interesting but should also be comprehensible even if its motivations are recondite. Like the first example it too defines an anonymous enum. However, it does not typedef the enum itself. Instead it declares the type UIViewAutoresizing as a typedef of NSUInteger. Comprehensible, but recondite.

C Standard Arcanum

From the C Standard, 1999 edition, §6.7.2.2.4:1

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined2, but shall be capable of representing all the values of the members of the enumeration.

If you’re quick-witted you may already have caught the plot: because the standard offers no guarantees about an enum’s signedness and because UIViewAutoresizing declares explicit values for its identifiers which are designed to be combined with the bitwise-or operator and because the results of bitwise operations on signed integers is also implementation-defined (what would -1 | 1 mean anyway) allowing the compiler pick the type of an enum automatically is unsafe.

It turns out this is a fairly common problem so the following workaround was developed: leave the enum anonymous — its identifiers are still valid — and declare the type as a typedef of an integer type of known size and signedness. The advantages of this should be obvious but come with a significant downside: the compiler no longer knows about the connection between the identifiers and the type. This precludes all sorts of lovely warnings (or-ing values from two different enums, missing cases in a switch statement) and also likely, depending on your editor, hinders autocomplete suggestions.

Clearly not an ideal situation.

The Way Things Are

In C++11 and also in more recent versions of Objective-C there is a solution to this: fixed-type enums. The syntax for this looks like this:

enum MyEnum : NSUInteger { ... };

With that in mind, let’s take another look at the macro sludge pasted above.

Base Case

#else
#define NS_ENUM(_type, _name) _type _name; enum
#define NS_OPTIONS(_type, _name) _type _name; enum
#endif

The last four lines, reproduced above, merely define the status quo. If this still looks like gibberish to you, recall how the macro is used on context (e.g. typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {).

Objective-C

Let’s assume that the compiler is operating in Objective-C mode and the fixed enum feature is enabled. We’d see the following definitions of NS_ENUM and NS_OPTIONS:

#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type

These are identical. If we expand one of our previous examples, we get the following:

typedef enum UIViewAnimationTransition : NSInteger UIViewAnimationTransition;
enum UIViewAnimationTransition : NSInteger {

(Line break added by yours truly.) Straightforward enough with the caveat that the type of the enum is required to be repeated in the typedef because the enum is forward declared.

C++

If the compiler is operating in C++11 or Objective-C++11 mode, the definitions are a little bit different:

#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#define NS_OPTIONS(_type, _name) _type _name; enum : _type

Eagle-eyed readers will have already noticed that definition of NS_OPTIONS is the same as the base case above and indeed this is where NS_OPTIONS differs from NS_ENUM. In perusing the headers of UIKit, it seems that NS_OPTIONS is used exclusively when declaring enums with explicit values for their identifiers. I’m not sure what rule of C++ this violates and frankly don’t particularly feel like investigating, but clearly, it’s a problem. (Mark Rowe has chimed in with the answer.)

The Way Things Will Be

In your own code, I would recommend the following:

  • Declare your enums as typedef enum : NSUInteger { ... } MyEnum;. It’s a simple and straightforward extension to the very common typedef-of-anonymous-enum pattern.
  • If your headers need to support compilation in Objective-C++ mode, use the NS_ENUM and NS_OPTIONS macros. You’ll need to experiment to figure out exactly where you need to use NS_OPTIONS as a fallback but NS_ENUM should be preferred wherever possible.

  1. Chapter and verse courtesy of Patrick Thomson. Thanks dude. 

  2. Patrick wrote in to add: “If you would accept suggestions, I would explain how implementation-defined behavior differs from undefined behavior (viz. compilers are required to describe implementation-defined behavior).” 

Comments

  1. Evan replied on November 27th, 2012:

    NSHipster just wrote this up, too. Nice bit of syntactic sugar.

    http://nshipster.com/ns_enum-ns_options/

  2. Mark Rowe replied on November 28th, 2012:

    The special handling NS_OPTIONS for C++ is to avoid requiring explicit casts back to the enum type when the values are or’d with each other. These are required since an expression such as UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight will have a result of type NSUInteger, which cannot be implicitly converted to UIViewAutoresizing.

  3. Colin Barrett replied on November 28th, 2012:

    Thanks a lot, Mark. I’ve updated the post to point to your comment.

  4. Michael Tsai - Blog - NS_ENUM and NS_OPTIONS replied on November 30th, 2012:

    […] Colin Barrett: Recently I had some free time so I broke down this enum situation and also included some of the history and present and also maybe even a little bit of the future of enumerated types in Objective-C. […]