The Long Weekend Website

Written by Paul

Not So Nasty Enums in Objective-C

By Paul on December 1st, 2010 in Technobabble
Tags: , ,

The syntax for Enums in C are sooo cool! A lot of people must agree with this statement because they keep wanting to use them in Objective C code. Of course there are other benefits: code readability, they are defined in one block, lighter than passing around string constants, etc. However, being C syntax, they are not well supported inside the gates of Objective C land.

I decided that there must be some better solutions out there, and here are two approaches I have found. One is a good approach, and the other is even better and the one we use (from now on).

Typedef Enum With Matched C Array of NSStrings

// Place this in your .h file, outside the @interface block
typedef enum {
    JPG,
    PNG,
    GIF,
    PVR
} kImageType;
NSString * const kImageTypeArray[];

...

// Place this in your .m file, outside the @implementation block
NSString * const kImageTypeArray[] = {
    @"JPEG",
    @"PNG",
    @"GIF",
    @"PowerVR"
};

...

// A method to convert an enum to string
-(NSString*) imageTypeEnumToString:(kImageType)enumVal
{
  return kImageTypeArray[enumVal];
}

// A method to retrieve the int value from the C array of NSStrings
-(kImageType) imageTypeStringToEnum:(NSString*)strVal
{
  int retVal;
  for(int i=0; i < sizeof(kImageTypeArray)-1; i++)
  {
    if([(NSString*)kImageTypeArray[i] isEqual:strVal])
    {
      retVal = i;
      break;
    }
  }
  return (kImageType)retVal;
}

Credit for this goes to Slava Bushtruk

Continuing the tradition of syntatic silliness that abounds in lower level languages, the enums must be declared in a different spot! This is a little counter intuitive and we can do better.

Typedef Enum With #Defined NSArray of NSStrings

// Place this in your .h file, outside the @interface block
typedef enum {
    JPG,
    PNG,
    GIF,
    PVR
} kImageType;
#define kImageTypeArray @"JPEG", @"PNG", @"GIF", @"PowerVR", nil

...

// Place this in the .m file, inside the @implementation block
// A method to convert an enum to string
-(NSString*) imageTypeEnumToString:(kImageType)enumVal
{
    NSArray *imageTypeArray = [[NSArray alloc] initWithObjects:kImageTypeArray];
    return [imageTypeArray objectAtIndex:enumVal];
}

// A method to retrieve the int value from the NSArray of NSStrings
-(kImageType) imageTypeStringToEnum:(NSString*)strVal
{
    NSArray *imageTypeArray = [[NSArray alloc] initWithObjects:kImageTypeArray];
    NSUInteger n = [imageTypeArray indexOfObject:strVal];
    if(n < 1) n = JPG;
    return (kImageType) n;
}

Credit goes to Peter N Lewis although it’s been modified it to work!

There you have it, all the declarations fit in the header, and two methods (similar to the first example), for converting between the enum and string representations.

Oh, one more thing. The original author of the second example code created a category for enum handling. Just the thing for adding to your very own NSArray class definition. Fantastic!

@interface NSArray (EnumExtensions)

- (NSString*) stringWithEnum: (NSUInteger) enumVal;
- (NSUInteger) enumFromString: (NSString*) strVal default: (NSUInteger) def;
- (NSUInteger) enumFromString: (NSString*) strVal;

@end

@implementation NSArray (EnumExtensions)

- (NSString*) stringWithEnum: (NSUInteger) enumVal
{
    return [self objectAtIndex:enumVal];
}

- (NSUInteger) enumFromString: (NSString*) strVal default: (NSUInteger) def
{
    NSUInteger n = [self indexOfObject:strVal];
    if(n == NSNotFound) n = def;
    return n;
}

- (NSUInteger) enumFromString: (NSString*) strVal
{
    return [self enumFromString:strVal default:0];
}

@end

Credit for the category goes to Peter N Lewis on Stackoverflow.com



  1. 16 Responses to “Not So Nasty Enums in Objective-C”

  2. Dave Anderson says:

    Have I missed something or in Typedef Enum With Matched C Array of NSStrings should line #25 not read:

    return kImageTypeArray[enumVal];

    that or the parameter should be:

    theEnumValue

    Dec 22, 2010 | Reply

  3. Paul says:

    @Dave : thank you for the correction. You are correct, I have updated the example accordingly. :D

    Dec 22, 2010 | Reply

  4. Paul says:

    Just discovered some formatting errors in the second last code block. FTFM.

    Aug 12, 2011 | Reply

  5. Dave says:

    Huh, I never realized you were allowed to place semicolons at the end of a method signature implementation (in reference to the last code example). I just tried it in Xcode and indeed it works. I would have thought that would be bad practise at any rate(?)

    Oct 16, 2011 | Reply

  6. Dave says:

    On another note, the return value of indexOfObject when the object is not found is NSNotFound, which according to documentation is equal to NSIntegerMax (at any rate, n is unsigned, and thus wouldn’t be less than 0).

    So I would change

    if(n < 0) n = def;

    to

    if(n == NSNotFound) n = def;

    Oct 16, 2011 | Reply

  7. Paul says:

    @dave: Thanks for the heads up on NSNotFound. I have updated the example. I had actually changed this in LongWeekend Dev Tools to if(!n) but neglected to change this. I’ve changed both if(NSNotFound).

    WRT the ; at the end of the method declaration line. This sometimes happens when copy-pasting from the header file to the implementation file. I have updated it b/c, as you pointed out, it’s not standard.

    Cheers

    Oct 16, 2011 | Reply

  8. Dave says:

    @Paul Er, shouldn’t that be

    if(n == NSNotFound)…

    if(NSNotFound) will always evaluate to true.

    Thanks for the post, by the way. I’ve been trying to figure out the best way to make enums more Objective-C friendly. Peter Lewis’ implementation is indeed nice; I think I’ll be using that from now on as well.

    Oct 16, 2011 | Reply

  9. Paul says:

    @dave: indeed it is, such is the peril of hasty editing. thx again

    Oct 16, 2011 | Reply

  10. Ron says:

    This was really useful. Thanks very much
    There is one thing i noticed with your use of sizeof(). This will not bring back the length of the array but rather a memory size taken up by the array in bytes. So to fix that, instead of

    sizeof(kImageTypeArray)-1

    use

    sizeof(kImageTypeArray)/sizeof(*kImageTypeArray)

    To find out amount of items in the array you divide array by its pointer. Mind the asterisk there

    http://en.wikipedia.org/wiki/Sizeof

    Mar 31, 2012 | Reply

  11. Saumitra says:

    I have a newbie question:
    How can I use the category so as to make this process generic for any enum. I would like to avoid writing the 2 methods in .m file for every enum I have.

    May 7, 2012 | Reply

  12. Paul says:

    @Saumitra: Just copy the last snippet of code into a .m file and it to your project, you should name it something like “NSArray+enums.m”.

    Once added to your build target this will “extend” the NSArray base class with new methods for processing these enums.

    Sorry if it wasn’t obvious form the post.

    May 7, 2012 | Reply

  13. Stewart Gleadow says:

    Thanks for the enum tip. These approaches will become even more concise with the new literal syntax as well.

    Jul 9, 2012 | Reply

  14. Nick Lockwood says:

    I don’t really get the point of this. You use enums when the constants either need to have meaningful integer values, or the values don’t matter and you just want to be able to use them in a switch statement.

    If the constants need to represent specific string values (e.g. a file extension), or you want to use them as dictionary keys, the logical thing to do is use string constants, surely?

    I can’t really think of a single non-contrived scenario where you would want to use the string value of a constant and also want to use it in a switch statement, so why bother with converting between both representations instead of just using the right too for the right job?

    This is certainly the approach apple takes in their own APIs. They use plenty of string constants and plenty of enums, but they use them for different purposes.

    Jul 20, 2012 | Reply

  15. Paul says:

    @NickLockwood: Thank you for your comments.

    > I can’t really think of a single non-contrived scenario

    String to Enum and back can be quite useful for (drum roll please) … Web APIs! JSON and XML based APIs frequently return string values for state values which need to be converted into some kind of in-app representation.

    Also the benefit of enums over straight NSStrings is twofold: i) ease of refactoring and ii) they auto-complete (at least when Xcode code sense isn’t on the fritz!)

    Jul 20, 2012 | Reply

  16. Timo says:

    I simply used the following:

    typedef enum Direction
    {
    DIRECTION_NONE,
    DIRECTION_NORTH,
    DIRECTION_EAST,
    DIRECTION_SOUTH,
    DIRECTION_WEST,
    DIRECTION_ALL
    } Direction;
    static NSString* DirectionString[] = { @”none”, @”north”, @”east”, @”south”, @”west”, @”all” };

    Then, you can get the string using, for instance, DirectionString[DIRECTION_NORTH].

    Sep 26, 2012 | Reply

1 Trackback(s)

  1. Dec 1, 2010: Tweets that mention Not So Nasty Enums in Objective-C | Long Weekend - iPhone & iPad Apps You'll Love! -- Topsy.com

Post a Comment