25

I need to create a dictionary/hashmap where the

  • Keys are enums
  • Values are some subclass of NSObject

NSDictionary won't work here (enums don't conform to NSCopying).

I could perhaps use a CFDictionaryRef here, but I'd like to know if is there any other way to achieve this.

Rui Peres
  • 25,741
  • 9
  • 87
  • 137
Debajit
  • 46,327
  • 33
  • 91
  • 100

5 Answers5

30

Since enums are integers, you can wrap the enum in an NSNumber. When you add/retreive something to/from the map, you pass the enum to the NSNumber constructor...

Assuming you've got an enum like...

enum ETest {
    FOO, BAR
};

You can use it in an NSDictionary like this...

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject: @"Foo!" forKey:[NSNumber numberWithInt: FOO]];
NSLog(@"getting value for FOO -> %@", 
      [dict objectForKey: [NSNumber numberWithInt: FOO]]);  
[dict release]; 
VoidPointer
  • 17,651
  • 15
  • 54
  • 58
28

With VoidPointer's suggestion, it may be better to use NSValue for those times when enums turn out not to be integers (such as when -fshort-enums is in play, which should be never as you'd probably break compatibility with Foundation).

NSValue *value = [NSValue value: &myEnum withObjCType: @encode(enum ETest)];

That's not going to add much here but gives you the general "I want to use <name of non-ObjC type> in a collection class" technique.

Notice that with modern compilers you can tell enums to use a fixed underlying type. This means you can control what storage is used for the enum, but as the above solution is general it still applies even when you know this.

  • Nice, this is indeed a better fit as it's exactly what NSValue is there for. Does NSValue copy the value pointed to int the ctor? – VoidPointer Jul 27 '09 at 13:29
  • Ok, http://cocoadev.com/forums/comments.php?DiscussionID=1169&page=1 says it does make a copy :) – VoidPointer Jul 27 '09 at 13:30
  • I'm doing what you suggest to convert UIKeyboardType to an `NSValue`, but am getting an error: http://stackoverflow.com/questions/6130315/how-to-add-a-method-to-uitextfield-uitextview/6132880#6132880 – ma11hew28 May 26 '11 at 02:16
  • @MattDiPasquale - You are replacing @encode(enum ETest) with @encode(enum UIKeyboardType) right? Type is not only determined by compiler flags, but can also vary (especially between signed and unsigned) based on the range of the enum values themselves. – DougW Jul 20 '11 at 18:14
9

Further extending on the suggestion from Graham Lee...

You could use an objective-c category in order to add a method to NSMutableDictionary that allows you to add a value with a key of your non NSObject type. This keeps your code free from the wrapping/unwrapping syntax.

Again, assuming

enum ETest { FOO, BAR };

First, we're adding a convince constructor to NSValue:

@interface NSValue (valueWithETest)
+(NSValue*) valueWithETest:(enum ETest)etest; 
@end

@implementation NSValue (valueWithETest)
+(NSValue*) valueWithETest:(enum ETest)etest
{
    return [NSValue value: &etest withObjCType: @encode(enum ETest)];
}
@end

Next we'll add 'enum ETest' support to NSMutableDictionary

@interface NSMutableDictionary (objectForETest)
-(void) setObject:(id)anObject forETest:(enum ETest)key;
-(id) objectForETest:(enum ETest)key;
@end

@implementation NSMutableDictionary (objectForETest)
-(void) setObject:(id)anObject forETest:(enum ETest)key
{
    [self setObject: anObject forKey:[NSValue valueWithETest:key]];
}
-(id) objectForETest:(enum ETest)key
{
    return [self objectForKey:[NSValue valueWithETest:key]];
}
@end

The original Example can thus be transformed to

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject: @"Bar!" forETest:BAR];
NSLog(@"getting value Bar -> %@", [dict objectForETest: BAR]);      
[dict release];

Depending on how much you use your enum to access the dictionary this may ease readability of your code quite a bit.

VoidPointer
  • 17,651
  • 15
  • 54
  • 58
  • time to stop the answer ping-pong :-). +1 for the NSValue category, I personally wouldn't use the NSMutableDictionary category though. I'd be constantly wondering why I didn't pass an object as the key. And why I couldn't call `-[NSDictionary objectForETest:]` :-/ –  Jul 27 '09 at 22:11
  • I wish there was at least a way to identify the "type" of some /any primitive, á la `[object class]`. it would make the boxing / unboxing process a LOT more liveable. – Alex Gray Oct 02 '12 at 04:40
7

enums don't conform to NSCopying

This is an understatement; enums do not "conform" to anything as they are not objects; they are primitive C values which are interchangeable with integers. That's the real reason why they can't be used as keys. The keys and values of NSDictionary need to be objects. But since enums are integers, you can just wrap them into NSNumber objects. This is probably the simplest option.

Another option, if the enums are contiguous from 0 up to some number (i.e. you didn't set any values manually), is that you can use an NSArray where the index represents the key enum's value. (Any "missing" entries would have to be filled with NSNull.)

newacct
  • 119,665
  • 29
  • 163
  • 224
2

The category approach has its own uses, but the newer boxed expressions (e.g. @(FOO)) should take care of type conversion for you. It works very transparently by explicitly boxing the enum when using it as a key.

outis
  • 75,655
  • 22
  • 151
  • 221
FrostyL
  • 811
  • 6
  • 9