Cocoa: Dictionary with enum keys? - objective-c

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.

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];

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.

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.

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

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.

Related

How to get classname in objective c Like 'NSString'

I want to get the class name of an object as what we are using.
That means now if I write this code
NSString *s = [NSString string];
NSLog(#"%#",[s class]);
The output is __NSCFConstantString
How can I get it as NSString itself ?
Note : NSString is just an example
I know __NSCFConstantString is correct. But my intention is to get like NSString. Is there any way to acheive this?
Give these a try, they'll output NSString. Keep in mind, the second set requires importing the Objective-C runtime header.
#import <objc/runtime.h>
NSString *string = #"I'm a string.";
NSLog(#"%#",NSStringFromClass([string classForCoder]));
NSLog(#"%#",NSStringFromClass([string classForKeyedArchiver]));
NSLog(#"%s",class_getName([string classForCoder]));
NSLog(#"%s",class_getName([string classForKeyedArchiver]));
Now, this won't work in all cases. For example, trying to get the class of NSConstantString, in this manner will output NSString. If you require checking the class name as a string in this way, you probably should reconsider your approach to solving the problem.
NSString is a so-called "class cluster". That means that the init methods will return
an instance of some subclass (such as __NSCFConstantString or __NSCFString).
You will never get an instance with the class equal to NSString.
If your intention is to check whether an object is a NSString or not then
use isKindOfClass:
if ([s isKindOfClass:[NSString class]]) {
// this is a string …
}
Other examples of class clusters are NSNumber, NSDictionary, NSArray
and their mutable variants.
NSLog(#"%#", NSStringFromClass([s class]));

Can the NSMultableArray mention which object inside the NSMultableArray?

The NSMutableArray can store every NSObject, but can I mention the NSMutableArray can get store my item only, for example, a NSMutableArray that store NSString only?
I remember that the java array can do that, can the objective C array do the similar things? Thanks.
Objective-C does not have this kind of generic constraint on NSArray/NSMutableArray. You have therefore two solutions:
Subclass NSArray/NSMutableArray and check for element type. It is strongly discouraged as NSArray/NSMutableArray is a class "cluster" and not obvious to subclass.
Create a category with specific methods that check the right type. You will have a compile-time enforcement of the type.
You can try it like this -
NSMutableArray *arr = [[[NSMutableArray alloc] init] autorelease];
if([obj isKindOfClass:[NSString class]])
[arr addObject:obj];
This way you end up adding only NSString to your arr.
Not by default, no. NSArray and its mutable counterpart just store pointers which happen to point obj-c objects. These objects can of any type. It would be up to you to make sure that only NSString's get in your array.
You could potentially subclass NSArray and override the addObject: methods such that they throw an exception if you try to add a non-NSString object.

what is this weird code notation mean

what's this line mean when using the second NSDictionay beside the message body:
NSDictionary *item = (NSDictionary *) [self.content objectAtIndex:indexPath.row];
(NSDictionary *) a type cast. It tells the compiler to assume that the object returned by the objectAtIndex: method is of the type NSDictionary * even though the return type of the method is different.
self.content is a property of type NSArray (I guess!)
This line returns you the Object (which seams to be a NSDictionary) at Index indexPath.row. (NSDictionary*) casts the object to NSDictionary.
This is a cast, as in C.
In your case, "self.content" seems to be an NSArray. So [self.content objectAtIndex:indexPath.row] would be an NSObject. Except that here, for some reason, you know it's an NSDictionary. So you explicitly cast it in order to avoid a compiler warning (that would tell you "hey, you're assigning an NSObject to an NSDictionary variable)

Save part of NSDictionary

I have a NSDictionary with a NSString and NSArray
I have to save only the NSArray in a variable without knowing the key.
Is this possible?
If I'm understanding you correctly, you have a dictionary that contains both an NSString and an NSArray, and you want to extract just the NSArray, without knowing what the key is.
One way to do that is to look through the dictionary with fast enumeration:
NSString *key;
for(key in someDictionary){
id someObject = [someDictionary objectForKey: key];
}
and then look at the objects to see which one is an NSArray:
if ([someObject isKindOfClass:[NSArray class]]) {
// do something with the array
}
(obligatory warning: explicitly checking an object's class is often a sign of a flawed design. In most cases, you should be checking for behavior (-respondsToSelector), not class identity)

Objective C /iPhone : Is it possible to re initialize an NSArray?

I read that non mutable data types can't be modified once created.(eg NSString or NSArray).
But can they be re-initialized to point to a different set of objects?
If so, do I use release to free any alloc from first time round in between uses? eg:
myArray declared as NSArray *myArray in interface, and as nonatomic/retain property.myArray set in initialization code to a point to an array of strings as follows.
self.myArray = [myString componentsSeparatedByString:#","];
But later I want to re-initialize myArray to point to a different set of strings
self.myArray = [myOtherString componentsSeparatedByString:#","];
Is it possible? Thanks...
It really depends what you mean with re-initialize. You can assign another immutable object to a pointer, because the pointers aren't constant.
Example:
#interface MyObj : NSObject {
NSString *name; // not needed in 64bit runtime AFAIK
}
#property(retain) NSString *name; // sane people use copy instead of retain
// whenever possible. Using retain can
// lead to some hard to find errors.
#end
/* ... another file ... */
MyObj *theObject = [[[MyObj alloc] init] autorelease];
theObject.name = #"Peter";
NSString *oldName = theObject.name;
NSLog(#"%#", theObject.name); // -> Peter
NSLog(#"%#", oldName); // -> Peter
theObject.name = #"Martin";
NSLog(#"%#", theObject.name) // -> Martin
NSLog(#"%#", oldName) // -> Peter
If the behavior above is what you want, that's fine.
If you want that last line to return Martin you're in trouble. Those are constant strings and are not meant to be modified. You could, if you really want, modify the memory of the object directly, but this is dangerous and not recommended. Use mutable objects if you need such behaviour.
Yes you can reinitialized the NSArray. Here is the sample code that i used to re-initialized the NSArray.
NSString *keywords = #"FirstName|LastName|Address|PhoneNumber";
NSArray *arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"First Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
arr = nil;
keywords = #"First_Name|Last_Name|_Address|_PhoneNumber";
arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"Second Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
Of course they can. Saying that an NSArray is immutable doesn't mean that an attribute of a class of that type cannot be changed. You can't change the content, but you can assign new content to it.
If you want to make also changing the reference impossible you should use const keyword.