Convert objective-c typedef to its string equivalent

136,637

Solution 1

This is really a C question, not specific to Objective-C (which is a superset of the C language). Enums in C are represented as integers. So you need to write a function that returns a string given an enum value. There are many ways to do this. An array of strings such that the enum value can be used as an index into the array or a map structure (e.g. an NSDictionary) that maps an enum value to a string work, but I find that these approaches are not as clear as a function that makes the conversion explicit (and the array approach, although the classic C way is dangerous if your enum values are not continguous from 0). Something like this would work:

- (NSString*)formatTypeToString:(FormatType)formatType {
    NSString *result = nil;

    switch(formatType) {
        case JSON:
            result = @"JSON";
            break;
        case XML:
            result = @"XML";
            break;
        case Atom:
            result = @"Atom";
            break;
        case RSS:
            result = @"RSS";
            break;
        default:
            [NSException raise:NSGenericException format:@"Unexpected FormatType."];
    }

    return result;
}

Your related question about the correct syntax for an enum value is that you use just the value (e.g. JSON), not the FormatType.JSON sytax. FormatType is a type and the enum values (e.g. JSON, XML, etc.) are values that you can assign to that type.

Solution 2

You can't do it easily. In C and Objective-C, enums are really just glorified integer constants. You'll have to generate a table of names yourself (or with some preprocessor abuse). For example:

// In a header file
typedef enum FormatType {
    JSON,
    XML,
    Atom,
    RSS
} FormatType;

extern NSString * const FormatType_toString[];

// In a source file
// initialize arrays with explicit indices to make sure 
// the string match the enums properly
NSString * const FormatType_toString[] = {
    [JSON] = @"JSON",
    [XML] = @"XML",
    [Atom] = @"Atom",
    [RSS] = @"RSS"
};
...
// To convert enum to string:
NSString *str = FormatType_toString[theEnumValue];

The danger of this approach is that if you ever change the enum, you have to remember to change the array of names. You can solve this problem with some preprocessor abuse, but it's tricky and ugly.

Also note that this assumes you have a valid enum constant. If you have an integer value from an untrusted source, you additionally need to do a check that your constant is valid, e.g. by including a "past max" value in your enum, or by checking if it's less than the array length, sizeof(FormatType_toString) / sizeof(FormatType_toString[0]).

Solution 3

My solution:

edit: I've added even a better solution at the end, using Modern Obj-C

1.
Put names as keys in an array.
Make sure the indexes are the appropriate enums, and in the right order (otherwise exception).
note: names is a property synthesized as *_names*;

code was not checked for compilation, but I used the same technique in my app.

typedef enum {
  JSON,
  XML,
  Atom,
  RSS
} FormatType;

+ (NSArray *)names
{
    static NSMutableArray * _names = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _names = [NSMutableArray arrayWithCapacity:4];
        [_names insertObject:@"JSON" atIndex:JSON];
        [_names insertObject:@"XML" atIndex:XML];
        [_names insertObject:@"Atom" atIndex:Atom];
        [_names insertObject:@"RSS" atIndex:RSS];
    });

    return _names;
}

+ (NSString *)nameForType:(FormatType)type
{
    return [[self names] objectAtIndex:type];
}


//

2.
Using Modern Obj-C you we can use a dictionary to tie descriptions to keys in the enum.
Order DOES NOT matter.

typedef NS_ENUM(NSUInteger, UserType) {
    UserTypeParent = 0,
    UserTypeStudent = 1,
    UserTypeTutor = 2,
    UserTypeUnknown = NSUIntegerMax
};  

@property (nonatomic) UserType type;

+ (NSDictionary *)typeDisplayNames
{
    return @{@(UserTypeParent) : @"Parent",
             @(UserTypeStudent) : @"Student",
             @(UserTypeTutor) : @"Tutor",
             @(UserTypeUnknown) : @"Unknown"};
}

- (NSString *)typeDisplayName
{
    return [[self class] typeDisplayNames][@(self.type)];
}


Usage (in a class instance method):

NSLog(@"%@", [self typeDisplayName]);


Solution 4

Combining @AdamRosenfield answer, @Christoph comment and another trick to handle plain C enums I suggest:

// In a header file
typedef enum {
  JSON = 0,         // explicitly indicate starting index
  XML,
  Atom,
  RSS,

  FormatTypeCount,  // keep track of the enum size automatically
} FormatType;
extern NSString *const FormatTypeName[FormatTypeCount];


// In a source file
NSString *const FormatTypeName[FormatTypeCount] = {
  [JSON] = @"JSON",
  [XML] = @"XML",
  [Atom] = @"Atom",
  [RSS] = @"RSS",
};


// Usage
NSLog(@"%@", FormatTypeName[XML]);

In the worst case - like if you change the enum but forget to change the names array - it will return nil for this key.

Solution 5

define typedef enum in class header:

typedef enum {
    IngredientType_text  = 0,
    IngredientType_audio = 1,
    IngredientType_video = 2,
    IngredientType_image = 3
} IngredientType;

write a method like this in class:

+ (NSString*)typeStringForType:(IngredientType)_type {
   NSString *key = [NSString stringWithFormat:@"IngredientType_%i", _type];
   return NSLocalizedString(key, nil);
}

have the strings inside Localizable.strings file:

/* IngredientType_text */
"IngredientType_0" = "Text";
/* IngredientType_audio */
"IngredientType_1" = "Audio";
/* IngredientType_video */
"IngredientType_2" = "Video";
/* IngredientType_image */
"IngredientType_3" = "Image";
Share:
136,637

Related videos on Youtube

craig
Author by

craig

Certified, Epic-Clarity consultant. Open-source projects: PsCrystal - PowerShell wrapper of the Crystal Reports SDK PsEnterprise - PowerShell wrapper of BusinessObjects Enterprise SDK AppleScript email merge - Email merge using Outlook or Mail/Address Book source-highlight-crystal - GNU source-highlight language for Crystal Reports' formula language crystal-reports-tmbundle - A TextMate bundle for the Crystal Report's forumula language

Updated on December 13, 2020

Comments

  • craig
    craig over 3 years

    Assuming that I have a typedef declared in my .h file as such:

    typedef enum {
      JSON,
      XML,
      Atom,
      RSS
    } FormatType;
    

    I would like to build a function that converts the numeric value of the typedef to a string. For example, if the message [self toString:JSON] was sent; it would return 'JSON'.

    The function would look something like this:

    -(NSString *) toString:(FormatType)formatType {
      //need help here
      return [];
    }
    

    Incidentally, if I try this syntax

    [self toString:FormatType.JSON];
    

    to pass the typedef value to the method, I get an error. What am I missing?

    • Itachi
      Itachi over 7 years
      Maybe we should give a hug to Swift language on enum.
  • Christoph
    Christoph almost 15 years
    you can initialize arrays with explicit indices, eg string[] = { [XML] = "XML" } to make sure the string match the enums properly
  • Barry Wark
    Barry Wark almost 13 years
    I don't think this will work; anywhere the #define is visible, you won't be able to use the actual enum value (i.e. JSON will get replaced with @"JSON" by the preprocessor and will result in a compiler error when assigning to a FormatType.
  • Adam Rosenfield
    Adam Rosenfield over 10 years
    @Christoph: Yes, that's a C99 feature called designated initializers. That's fine to use in Objective-C (which is based off of C99), but for generic C89 code, you can't use those.
  • Jameo
    Jameo about 10 years
    Is there any way to go the other way? For example, get the enum back given a string?
  • Adam Rosenfield
    Adam Rosenfield about 10 years
    @Jameo: Yes, but it's not quite as simple as doing an array lookup. You'll either need to iterate through the FormatType_toString[] array and call -isEqualToString: on each element to find a match, or use a mapping data type such as NSDictionary to maintain the inverse lookup map.
  • anneblue
    anneblue about 10 years
    @Daij-Djan what about returning nil if array.count <= enumValue?
  • Daij-Djan
    Daij-Djan about 10 years
    @anneblue that would catch the error .. it would sill be fragile because if you add an enum value OR the integer value of an enum value changes this goes wrong. The accepted answer would be good
  • Joel Fischer
    Joel Fischer about 10 years
    Do be aware that every time you call +[typeDisplayNames], you're re-creating the dictionary. This is fine if it's only called a few times, but if it's called many times, this will get very expensive. A better solution may be to make the dictionary a singleton, so it's only created once and stays in memory otherwise. Classic memory vs. CPU conundrum.
  • lindon fox
    lindon fox almost 10 years
    @codercat :( sorry - not sure what happened to that website. Not in the Way Back When machine either...
  • Ganesh
    Ganesh over 9 years
    I have a small question on above answer. How to convert string element to kImageType. I need to call the imageTypeEnumToString method by passing the string.Can you please help me out for my problem.
  • Elijah
    Elijah over 9 years
    I like this answer best, because you have the string definitions right next to the enums. Least chance of missing a value. And @Ganesh, to convert from raw value, could do this: return (kImageType)[imageTypeArray indexOfObject:rawValue];
  • user2387149
    user2387149 almost 9 years
    looks nice, but you are allocating and returning full dictionaries when you only need one of its values. Efficiency VS Pretty code? depends on what you want and you will be fine with this if you don't use them that much on your code like in a huge loop. But this will be maybe useful with "dynamic" or non-hard-coded enums coming from a server for example
  • Hunter-Orionnoir
    Hunter-Orionnoir almost 9 years
    Dangerous, but it did what I needed on the cheap. Deftly surrounding in #if debug checks, it should never see the light of production and only those punished will be responsible.
  • AechoLiu
    AechoLiu over 8 years
    The trick of Max O is good about forgetting to add entries in the FormatType_toString array.
  • Muhammad Umair
    Muhammad Umair about 8 years
    switch would be the best to overcome the danger of updating array with respect to enum.
  • TravisWhidden
    TravisWhidden about 8 years
    This worked great in C99 - I'm new at C, and I found this to be the cleanest way to accomplish the question asked. I also added in a default in my implementation for items that may not have been defined. Very clean method. Thanks for the results. Very crafty use of a Macro.
  • voiger
    voiger over 7 years
    @AlexandreG provide your solution, man. It's easy to carp at someone. This solution has its obvious pros and obvious cons both. Make the world better with your solution.
  • hariszaman
    hariszaman about 7 years
    typedef enum FormatType : NSString should be changed to typedef enum FormatType : NSUInteger. I wonder no one realised that. Secondly passing it an invalid enum FormatType_toString[5] would crash
  • Adam Rosenfield
    Adam Rosenfield about 7 years
    @hariszaman: Fixed now, thanks for catching that. That error was introduced by another user that edited this answer a while back, and nobody caught it until now.
  • hariszaman
    hariszaman about 7 years
    @AdamRosenfield adding checks everytime array us accessed seems to be feasible than using dictionaries with key value pair?
  • Nitin
    Nitin about 7 years
    since duplicate answer is not allowed, here is complete solution github.com/ndpiparava/ObjcEnumString
  • uliwitness
    uliwitness about 7 years
    This technique is called X-Macro, in case someone wants to read about it. That comes from the fact that, traditionally, the FOR_EACH_GENDER() macro was always just called X(). One thing you may want to do is #undef FOR_EACH_GENDER before you redefine it with a new meaning.
  • natanavra
    natanavra almost 7 years
    Or change it to be a static variable, e.g. static NSDictionary *dict = nil; if(!dict) dict = @{@(UserTypeParent): @"Parent"}; return dict; Comments won't let you line break, sorry for that.