21

Im digging for ways to enum objc object such as NSString, I remember there a new feature in a version of Xcode4+ which offering a new way to enum , but not clearly. Anyone know that?

kimimaro
  • 1,263
  • 2
  • 12
  • 21
  • possible duplicate of [enum Values to NSString (iOS)](http://stackoverflow.com/questions/6331762/enum-values-to-nsstring-ios) – Ja͢ck Jul 10 '14 at 07:17
  • It would be better if you specifically said in the question title "in Objective-C". see unrelated answers below... – Motti Shneor Oct 19 '20 at 19:44
  • See my answer at https://stackoverflow.com/questions/6331762/enum-values-to-nsstring-ios - I believe that might be a more elegant solution to this issue. – BooTooMany Oct 22 '14 at 20:46

7 Answers7

36

OK, I answered myself. Guess I make a mistake.

This is the new feature I mentioned above:

typedef enum Language : NSUInteger{
     ObjectiveC,
     Java, 
     Ruby, 
     Python, 
    Erlang 
}Language;

It's just a new syntax for enum in Xcode 4.4, but I'm so foolish to think we can exchange "NSUInteger" to "NSString".

So here is the way I found that works:

http://longweekendmobile.com/2010/12/01/not-so-nasty-enums-in-objective-c/

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

FYI. 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.

@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
Alex Zavatone
  • 4,106
  • 36
  • 54
kimimaro
  • 1,263
  • 2
  • 12
  • 21
28

Alternative way to use struct:

extern const struct AMPlayerStateReadable
{
    __unsafe_unretained NSString *ready;
    __unsafe_unretained NSString *completed;
    __unsafe_unretained NSString *playing;
    __unsafe_unretained NSString *paused;
    __unsafe_unretained NSString *broken;
} AMPlayerState;

const struct AMPlayerStateReadable AMPlayerState =
{
    .ready = @"READY",
    .completed = @"COMPLETE",
    .playing = @"PLAYING",
    .paused = @"PAUSED",
    .broken = @"BROKEN"
};

Then you can use like this:

NSString *status = AMPlayerState.ready;

Easy to use, readable. Would be nice if someone update/edit answer with advantages/disadvantages of this approach.

Injectios
  • 2,777
  • 1
  • 30
  • 50
17

Recommended way from apple docs:

You use the NS_TYPED_ENUM to group constants with a raw value type that you specify. Use NS_TYPED_ENUM for sets of constants that can't logically have values added in a Swift extension, and use NS_TYPED_EXTENSIBLE_ENUM for sets of constants that can be expanded in an extension. Apple docs

typedef NSString *MyEnum NS_TYPED_ENUM;
extern MyEnum const MyEnumFirstValue;
extern MyEnum const MyEnumSecondValue;
extern MyEnum const MyEnumThirdValue;

in the .h file. Define your strings in the .m file

MyEnum const MyEnumFirstValue = @"MyEnumFirstValue"
MyEnum const MyEnumSecondValue = @"MyEnumSecondValue";
MyEnum const MyEnumThirdValue = @"MyEnumThirdValue";

Works as expected in both Objective-C

- (void)methodWithMyEnum:(MyEnum)myEnum { }

and Swift

func method(_ myEnum: MyEnum) { }
Joakim
  • 3,224
  • 3
  • 29
  • 53
  • 3
    Amazing how many bad answers I had to read till this one. I KNEW it existed, and I knew there MUST be a new ObjC compiler directive for this - same way "non-optionals" were introduced for smoother bridging with swift. but I couldn't find the Apple docs for the life of me. Great. Thanks. – Motti Shneor Oct 19 '20 at 19:42
10

This will be validated by compiler, so you won't mix up indices accidentally.

NSDictionary *stateStrings =
 @{
   @(MCSessionStateNotConnected) : @"MCSessionStateNotConnected",
   @(MCSessionStateConnecting) : @"MCSessionStateConnecting",
   @(MCSessionStateConnected) : @"MCSessionStateConnected",
  };
NSString *stateString = [stateStrings objectForKey:@(state)];

<nbsp;>

var stateStrings: [MCSessionState: String] = [
    MCSessionState.NotConnected : "MCSessionState.NotConnected",
    MCSessionState.Connecting : "MCSessionState.Connecting",
    MCSessionState.Connected : "MCSessionState.Connected"
]
var stateString = stateStrings[MCSessionState.Connected]

UPDATE: A more Swifty way is to extend the enum with CustomStringConvertible conformance. Also, this way the compiler will safeguard to implement every new addition to the underlying enum (whereas using arrays does not), as switch statements must be exhaustive.

extension MCSessionState: CustomStringConvertible {
    
    public var description: String {
        switch self {
        case .notConnected:
            return "MCSessionState.notConnected"
        case .connecting:
            return "MCSessionState.connecting"
        case .connected:
            return "MCSessionState.connected"
        @unknown default:
            return "Unknown"
        }
    }
}

// You can use it like this.
var stateString = MCSessionState.connected.description

// Or this.
var stateString = "\(MCSessionState.connected)"
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
8

Update in 2017

Recent down votes drew my attention, and I'd like to add that enum is really easy to work with String now:

enum HTTPMethod: String {
    case GET, POST, PUT
}

HTTPMethod.GET.rawValue == "GET" // it's true

Original Answer

Unfortunately I ended up using:


#define HLCSRestMethodGet       @"GET"
#define HLCSRestMethodPost      @"POST"
#define HLCSRestMethodPut       @"PUT"
#define HLCSRestMethodDelete    @"DELETE"
typedef NSString*               HLCSRestMethod;

I know this is not what OP asked, but writing actual code to implement enum seems to be an overkill to me. I would consider enum as a language feature (from C) and if I have to write code, I would come up with some better classes that does more than enum does.

Update

Swift version seems to be prettier, although the performance can never be as good.

struct LRest {
    enum HTTPMethod: String {
        case Get = "GET"
        case Put = "PUT"
        case Post = "POST"
        case Delete = "DELETE"
    }
    struct method {
        static let get = HTTPMethod.Get
        static let put = HTTPMethod.Put
        static let post = HTTPMethod.Post
        static let delete = HTTPMethod.Delete
    }

}

superarts.org
  • 7,009
  • 1
  • 58
  • 44
  • 1
    I would also rather go with this approach than lines on lines of code for something this simple. You don't lose too much from not using enum too. – pnizzle Jul 25 '14 at 05:39
  • 3
    The question was about introducing such enumerated NSStrings in Objective-C. Not Swift. – Motti Shneor Oct 19 '20 at 19:45
1

I think you are looking for the inline array function. eg

@[@"stringone",@"stringtwo",@"stringthree"];

if not, i'm not sure you can enum objects.

you could however have a static array of strings and have the enum reference object at index.

Bergasms
  • 2,203
  • 1
  • 18
  • 20
0

This is how I do it, although it's not perfect. I feel the switch mechanism could be improved... also not positive about hash-collision resistance, don't know what apple uses under the hood.

#define ElementProperty NSString *
#define __ElementPropertiesList @[@"backgroundColor", @"scale", @"alpha"]

#define epBackgroundColor __ElementPropertiesList[0]
#define epScale __ElementPropertiesList[1]
#define epAlpha __ElementPropertiesList[2]

#define switchElementProperty(__ep) switch(__ep.hash)
#define caseElementProperty(__ep) case(__ep.hash)
-(void)setValue:(id)value forElementProperty:(ElementProperty)ep;
[self setValue:@(1.5) forElementProperty:epScale];
//Compiler unfortunately won't warn you if you are missing a case
switchElementProperty(myProperty) {
    caseElementProperty(epBackgroundColor):
        NSLog(@"bg");
        break;
    caseElementProperty(epScale):
        NSLog(@"s");
        break;
    caseElementProperty(epAlpha):
        NSLog(@"a");
        break;
}
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195