typedef NS_ENUM vs typedef enum - objective-c

On the Adopting Modern Objective-C guide, Apple recommends using the NS_ENUM macro instead of enum. I've also read an explanation from NSHipster about NS_ENUM and NS_OPTIONS.
Maybe I've missed something but I don't quite understand what is the difference between the following two snippets and if any why is NS_ENUM the recommended way to go (except maybe, for backwards compatibility with older compilers)
// typedef enum
typedef enum {
SizeWidth,
SizeHeight
}Size;
// typedef NS_ENUM
typedef NS_ENUM(NSInteger, Size) {
SizeWidth,
SizeHeight
};

First, NS_ENUM uses a new feature of the C language where you can specify the underlying type for an enum. In this case, the underlying type for the enum is NSInteger (in plain C it would be whatever the compiler decides, char, short, or even a 24 bit integer if the compiler feels like it).
Second, the compiler specifically recognises the NS_ENUM macro, so it knows that you have an enum with values that shouldn't be combined like flags, the debugger knows what's going on, and the enum can be translated to Swift automatically.

NS_ENUM allows you to define a type. This means that the compiler can check if you're assigning the enum to a different variable like so:
//OK in both cases
NSInteger integer = SizeWidth;
//OK only with typedef
BOOL value = SizeHeight;
NS_ENUM also provides checks in switch statements that you've covered all possible values:
//Will generate warning if using NS_ENUM
switch(sizeVariable) {
case SizeWidth:
//Do something
}

Related

Typedef enum declaration: which is correct

So I been used to use this format to declare a enum types:
typedef enum SortType {
SORT_BY_NAME,
SORT_BY_COMPANY,
SORT_BY_NONE
} SortType;
But I saw some people declare it this way
typedef enum {
SORT_BY_NAME,
SORT_BY_COMPANY,
SORT_BY_NONE
} SortType;
Both seems to work and no error. But I want to know which is correct.
I would recommend:
typedef NS_ENUM(NSInteger, SortType) {
SortTypeName,
SortTypeCompany,
SortTypeNone
};
as per the Apple Developer Guides and Sample Code: Adopting Modern Objective-C > Enumeration Macros
Between those two, there isn't a wrong form per se. That said, the current recommended way to declare enums in Objective-C is using the NS_ENUM macro:
typedef NS_ENUM(NSInteger, SortType) {
SORT_BY_NAME,
SORT_BY_COMPANY,
SORT_BY_NONE
};
From Apple's Adopting Modern Objective-C guide:
The NS_ENUM and NS_OPTIONS macros provide a concise, simple way of defining enumerations and options in C-based languages. These macros improve code completion in Xcode and explicitly specify the type and size of your enumerations and options. Additionally, this syntax declares enums in a way that is evaluated correctly by older compilers, and by newer ones that can interpret the underlying type information.
Use the NS_ENUM macro to define enumerations, a set of values that are mutually exclusive:
The NS_ENUM macro helps define both the name and type of the enumeration, in this case named UITableViewCellStyle of type NSInteger. The type for enumerations should be NSInteger.

NS_ENUM vs enum

Objective C provides several ways to declare an enumeration. It could be declared via typedef enum or NS_ENUM. NS_ENUM macro takes type name as a parameter, and I do not completely understand its meaning. I didn't find description of NS_ENUM macro in official Apple documentation. What's a difference between using enum and NS_ENUM? And an other question if it's possible to use any other type in NS_ENUM instead NSInteger and its relative integer types?
NSHipster provided a very nice post that explains this thoroughly:
http://nshipster.com/ns_enum-ns_options/
To quote the bottom line:
This approach combines the best of all of the aforementioned approaches (enum, typedef enum), and even provides hints to the compiler for type-checking and switch statement completeness.
The main difference is that typedef NS_ENUM translates to a Swift enum properly, whereas typedef enum doesn't.
Here is the link to the official Apple's documentation:
https://developer.apple.com/library/content/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html#//apple_ref/doc/uid/TP40014150-CH1-SW6
From the subsection titled "Enumeration Macros":
The NS_ENUM and NS_OPTIONS macros provide a concise, simple way of defining enumerations and options in C-based languages. These macros improve code completion in Xcode and explicitly specify the type and size of your enumerations and options. Additionally, this syntax declares enums in a way that is evaluated correctly by older compilers, and by newer ones that can interpret the underlying type information.
Use the NS_ENUM macro to define enumerations, a set of values that are mutually exclusive:
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
The NS_ENUM macro helps define both the name and type of the enumeration, in this case named UITableViewCellStyle of type NSInteger. The type for enumerations should be NSInteger.

Can the underlying type of an Objective-C enum be specified?

I was surprised to discover that Xcode does not issue an error for the following code, which is invalid in C:
typedef enum : NSUInteger {
FLAG_A = 1,
FLAG_B = 2,
FLAG_C = 4
} MyFlags;
This appears to make NSUInteger the underlying type of the MyFlags enum, very similar to C#'s syntax for setting the underlying type of an enum.
Does this syntax do what it appears, or does it mean something else?
It is a feature borrowed from C++11 used to ensure a specific size of the enum. It allows to use enums in the public API of frameworks without having to fear that the ABI will eventually break.
Clang implements this feature and allows its use in Objective-C as a language extension.
This will work fine in objective c. you can set any value for these params in an enum.

The new enum in Objective-C

In the latest tools, a new kind of enums are now allowed:
typedef enum CarType : NSUInteger {
FourDoorCarType,
TwoDoorCarType
} CarType;
My question comes in parts:
Why should I use this instead of the old way?
Why does CarType appear twice? I tried skipping the first CarType and just leaving the first line as "typedef enum : NSUInteger {", and it seems to work fine. What are the drawbacks, if any?
Can some types other than NSUInteger be used?
Because this new way helps you with autocompletion, switch statement, better, respectively more precise warnings, ...
Stick with macro ...
typedef NS_ENUM( NSUInteger, CarType ) {
FourDoorCarType,
TwoDoorCarType
};
... read this for example https://stackoverflow.com/a/3190470/581190
NSInteger, ... what types do you want?
This explicitly defines the numeric type that the enum uses. This helps with portability concerns. It also helps if you need control over signed vs. unsigned
Two types are actually defined here. The first is enum CarType and the second is CarType, which is an alias of enum CarType. You can omit the first CarType if you want. This just prevents enum CarType from being a defined type, but CarType is still valid. Another common thing people do is something like
typedef enum _EnumName {
values
} EnumName;
What you decide to do here is something of a matter of personal preference.
Yes. You can use any numeric type, although the enum values must be able to fit in the chosen type.
One big advantage is that you can forward-declare enums with this syntax:
enum CarType : NSUInteger;
That helps avoiding including large headers in other headers just because of enum definitions.
For point 2: it's the "same old syntax" from C: typedef <something> <alias>. Here, something is enum <enumIdent> { <a, b, c, d, ...> }.
You're allowed to use the same name for enumIdent and alias, simply.
the answers would be here for you.
typedef enum MYCARTYPE { // you could drop the ": NSInteger" part to you could drop the the MYCARTYPE name as well, if you like.
FourDoorCarType,
TwoDoorCarType
} CarType;
Why should I use this instead of the old way?
you can use the old way at the current stage.
Why does CarType appear twice? I tried skipping the first CarType and just leaving the first line as typedef enum : NSUInteger {, and it seems to work fine. What are the drawbacks, if any?
because you named the enum as CarType and then you named the new type in typedef definition as CarType as well. you don't need to name the enum because you cannot use its name anywhere. the name of the new type should be enough.
Can some types other than NSUInteger be used?
yep, you can, the types are always NSInteger, you don't need to limit them unsigned integer.
New NS_ENUM also enables you to forward declare as following:
// Forward declaration for XYZCharacterType in other header say XYZCharacter.h
typedef NS_ENUM(NSUInteger, XYZCharacterType);
// Enum declaration header: "XYZEnumType.h"
#ifndef XYZCharacterType_h
#define XYZCharacterType_h
typedef NS_ENUM(NSUInteger, XYZEnumType) {
XYZCharacterTypeNotSet,
XYZCharacterTypeAgent,
XYZCharacterTypeKiller,
};
#endif /* XYZCharacterType_h */`
Forward-declare enum in Objective-C

Is it required to typedef a primitive type to an enum?

I was looking through the NSString header file to see how Apple writes their enumerations and came across this piece of code:
enum {
NSStringEncodingConversionAllowLossy = 1,
NSStringEncodingConversionExternalRepresentation = 2
};
typedef NSUInteger NSStringEncodingConversionOptions;
This leaves me with a couple questions.
Why have they used anonymous enumerations? Is this method advantageous?
Is the typedef NSUInteger NSStringEncodingConversionOptions; line a good idea to include normally, or is it only used here because they have declared an anonymous enumeration?
That strange-looking definition is there to clearly define the bit-width and the signed-ness of an enum in a code both in 64-bit and 32-bit environment. It's detailed in this Apple document, but let me write down the summary here.
Apple in the past used the standard typedef enums before, as in
typedef enum { .... } NSEnumTypeName;
before 64bit-32bit universal binaries were (re)introduced. (I used "re" because the FAT binaries have been there since the NeXTStep days. Anyway.)
However, this makes the bit-width and the signed-ness of the typedef'd type NSEnumTypeName to be implementation-defined as specified in the Official Standard, see 6.7.2.2.4.
That makes it even trickier to write a code which can be compiled with various compilers and with various bit-width.
So Apple switched from the standard enum to the anonymous enum with a corresponding typedef to a specific signed/unsigned integer type.