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
Related
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.
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
}
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.
In Objective-C id is a typedef:
typedef struct objc_object {
Class isa;
} *id;
So I can declare (and initialize) a variable e.g. like this:
// using id
id po_one = #"one";
Compiles fine.
Since I name my types in an own scheme and since I dislike the implied pointer in the id typedef I want to add an own typedef (with O for object) like this:
typedef struct objc_object O;
So a variable declaration (with initialization) could look like:
// using O
O * po_two = #"two";
This compiles with a warning:
Initialization from incompatible pointer
type
As far as I understand typedefs I thought that the latter should be equivalent to:
// using struct objc_object
struct objc_object * po_three = #"three";
Which again compiles fine.
It's astonishing that:
po_two = po_one;
po_two = po_three;
both compile without warnings.
So I tried:
typedef struct objc_object * PO;
to see whether it works without warning if I include the pointer (being exactly the thing I want to avoid - so just for test reasons) to see whether a typedef outside of the structure definition works differently to a typedef in the structure definition (which is the definition of id in this case).
// using PO
PO po_four = #"four";
That also works fine.
If I use:
#define O struct objc_object
the construct using O again compiles without warning.
But I prefer to use typedefs instead of a defines whenever possible.
I'm puzzled. What is my misunderstanding with typedefs?
I'm not an expert of compilers, so probably a real expert can give you a better answer. In any case, based on my knowledge, when a compiler does type checking, it bases this check on the "parse tree", which generates a sort of table for all possible data type structures, and check if two definitions point to the same row in the table. So it works by fetching symbols and type definitions from this table and comparing them.
Now "struct objc_object" is one of these data structures. Then, when id is defined, it generates another entry: "id --> struct objc_object *".
The po_three definition leads to the same reference: "struct objc_object *" (infact "=" has lowest precedence).
The same happens for po_four, because PO leads by definition to the same reference, again (PO --> struct objc_object *).
When you use the definition with #define, thanks to preprocessing you're still referencing "struct objc_object *".
But when you use O * po_two = #"two" you're in fact trying to match one type which is "id" (#"two"), that is "struct objc_object *" and another one which is a pointer to "struct objc_object" but even if logically they are the same they don't lead to the same reference in the table. The reason is because "O" type has been defined yet, so po_two is simply stored in the symbol table as a pointer to "O" and "O --> struct objc_object". And that's the reason of the warning: po_two is a pointer to something different than #"two".
I have a curiosity: have you tried to plot the symbol table using "nm"?
In Objective-C, are there any ways to indicate that an NSNumber* should actually be a BOOL? Right now my code looks like:
NSNumber *audio; // BOOL wrapper
Without the comment, it is not immediately obvious that *audio is a boolean value.
My first thought was to try
typedef NSNumber* BOOL;
but this gave a compiler error apparently because typedef doesn't understand Objective-C.
Without changing the variable names (which is difficult when using existing APIs), how should I indicate that an NSNumber* holds a boolean value?
The code:
typedef NSNumber* BOOL;
doesn't compile because BOOL is already a typedef, and it's not allowed to redefine a typedef.
So you could use another name for that type, e.g.:
typedef NSNumber NSNumberBool;
NSNumberBool *audio;
Or, probably better, name the variable so that you know it is an NSNumber and contains a bool, this way you don't even need to go look for the variable type:
NSNumber *audioNumberBool;
...
[audioNumberBool boolValue];
Clearly, the best solution is to rename the property to be something like isAudio or hasAudio. But if you can't do it, then a mediocre solution is the typedef like you describe. The typedef you describe fails because BOOL is already defined in Objective C in objc.h:
typedef signed char BOOL;
beside which, that would be confusing since it doesn't indicate its actually an NSNumber and not just a bool/int. I would suggest something like:
typedef NSNumber NSNumberBool;
or perhaps in this case, better would be:
#define NSNumberBool NSNumber
and then use:
NSNumberBool *audio;
Or just use a comment as you have done.
The best way would be to call the variable audioBool or something that signifies a boolean value. Comments are good and all, but sometimes they can be missed or just not read.
If you have a problem with renaming many variables, you can use Xcode's refactor tool. Simply right click (or cmd+click) on the variable name, choose the Refactor... option and then rename the variable. Hit Preview, and Xcode will show you what it's about to change. If you like what you see, go ahead and apply, if not, cancel.
Xcode will also give you the option to make a snapshot before committing to a refactor operation. This is on by default. So if anything goes wrong, you can just roll back.