Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

foidulus

macrumors 6502a
Original poster
Jan 15, 2007
904
1
So I'm a bit new to the Obj-C/Cocoa world and I have sort of a best practices question,

In Java and the like, the recommended way to store constant value keys for hashmaps that have constant keys is to make a constant somehwere and use that. That way it's easier for other packages to use the same keys and everyone stays in sync.

I am currently doing that with an Obj-C cocoa project I am working on by defining the strings as extern const in a class that basically holds all the constants. However when I go to use the strings I of course get a compiler warning about discarding qualifiers from target type. I know it's perfectly valid since the stringForKey SHOULDNT be changing my strings, but I hate compiler warnings and try to generate as few as possible.

So what I am doing is casting them to (NSString *) whenever I use them, but this doesn't seem like the best practice either. What is the recommended way of storing all these keys?
 

Sydde

macrumors 68030
Aug 17, 2009
2,552
7,050
IOKWARDI
I find the easiest approach is to create a single .h file that gets imported, directly or indirectly, by everything that uses the key strings and just use

#define aKeyString @"TheParicularKeyString"

for each key

(By "indirectly imported", I mean imported via other imported headers that import the constants header.)
 

kainjow

Moderator emeritus
Jun 15, 2000
7,958
7
^ I do the same. However, I only put them in a shared file if they're used across multiple files, otherwise I stick them at the top of the file it's used in.

And since you're getting warnings, you should post your exact code so we can see what's wrong.
 

chown33

Moderator
Staff member
Aug 9, 2009
10,761
8,455
A sea of green
I find the easiest approach is to create a single .h file that gets imported, directly or indirectly, by everything that uses the key strings and just use

#define aKeyString @"TheParicularKeyString"

for each key

(By "indirectly imported", I mean imported via other imported headers that import the constants header.)

I agree. AFAIK, Objective-C will only have one instance of each string literal, regardless of how many times it's used.
 

JoshDC

macrumors regular
Apr 8, 2009
115
0
I believe the way they're done in most of AppKit (instead of #define) is:

Code:
extern NSString *const Key;

In the .h file and:

Code:
NSString *const Key = @"Key Value";

In the corresponding .m

Which gives you certain benefits such as knowing the type during autocomplete.
 

foidulus

macrumors 6502a
Original poster
Jan 15, 2007
904
1
I believe the way they're done in most of AppKit (instead of #define) is:

Code:
extern NSString *const Key;

In the .h file and:

Code:
NSString *const Key = @"Key Value";

In the corresponding .m

Which gives you certain benefits such as knowing the type during autocomplete.

Yeah but thats what gives you the warning since valueForKey: expects an NSString, not a const NSString, the C compiler is very picky about that stuff.

The #define works like a charm

Though the one thing I did just notice is that NS(Mutable)Dictionary doesn't really care if you use constants or not, it won't flag it in the compiler, but user defaults does:

For instance:


Code:
const NSString *GFSAVE_SERVER_DEFAULTS_NAME_KEY=@"savedservername";

 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSString *saveServerName=[defaults stringForKey:GFSAVE_SERVER_DEFAULTS_NAME_KEY];

throws a compiler warning but:

Code:
NSMutableDictionary *burt;
NSString *earnie=[burt objectForKey:GFSAVE_SERVER_DEFAULTS_NAME_KEY];

compiles just without any warnings

Maybe an oversight on apple's part?
 

kainjow

Moderator emeritus
Jun 15, 2000
7,958
7
Keys in a dictionary are of type "id", they don't have to be strings, but NSUserDefaults does expect a string, thus the warning.
 

chown33

Moderator
Staff member
Aug 9, 2009
10,761
8,455
A sea of green
Code:
const NSString *GFSAVE_SERVER_DEFAULTS_NAME_KEY=@"savedservername";

 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSString *saveServerName=[defaults stringForKey:GFSAVE_SERVER_DEFAULTS_NAME_KEY];

throws a compiler warning ...
Just wanted to point out that:
Code:
const NSString *GFSAVE_SERVER_DEFAULTS_NAME_KEY=@"savedservername";
is not the same as:
Code:
NSString * const GFSAVE_SERVER_DEFAULTS_NAME_KEY=@"savedservername";
The latter is what JoshDC posted.

The former effectively says "GFSAVE_SERVER_DEFAULTS_NAME_KEY is a pointer to a const NSString". Perhaps surprisingly to you, this does not prevent the pointer variable from being reassigned. The latter says "GFSAVE_SERVER_DEFAULTS_NAME_KEY is a constant pointer to an NSString". This does prevent reassignment. For "NSString", you can roughly interpret it as "an opaque typedef'ed struct named NSString".

Note that Objective-C uses the const keyword identically to C. That means you can consult a C language reference doc to learn precisely how const is used in C, and exactly the same rules apply in Objective-C.

Furthermore, you can see for yourself what's in the Cocoa headers. Example command-lines:
Code:
cd /System/Library/Frameworks/Foundation.framework/Headers/ 
grep const *.h | grep NSString
sample output:
Code:
NSUserDefaults.h:FOUNDATION_EXPORT NSString * const NSCurrencySymbol;
NSUserDefaults.h:FOUNDATION_EXPORT NSString * const NSDecimalSeparator;
NSUserDefaults.h:FOUNDATION_EXPORT NSString * const NSThousandsSeparator;
NSUserDefaults.h:FOUNDATION_EXPORT NSString * const NSDecimalDigits;

EDIT:
Code demonstration:
Code:
#import <Foundation/Foundation.h>

const NSString * KEY1=@"key1";

NSString * const KEY2=@"key2";

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

	NSLog( @"KEY1 = %@, KEY2 = %@", KEY1, KEY2 );

	KEY1 = (const NSString*) @"devious";

	NSLog( @"KEY1 = %@, KEY2 = %@", KEY1, KEY2 );

//	KEY2 = @"does not compile";

	NSLog( @"KEY1 = %@, KEY2 = %@", KEY1, KEY2 );
	
    [pool drain];
    return 0;
}
Output:
Code:
2010-09-25 11:30:26.704 a.out[19653] KEY1 = key1, KEY2 = key2
2010-09-25 11:30:26.704 a.out[19653] KEY1 = devious, KEY2 = key2
2010-09-25 11:30:26.704 a.out[19653] KEY1 = devious, KEY2 = key2
 

Catfish_Man

macrumors 68030
Sep 13, 2001
2,579
2
Portland, OR
The reason for using extern const strings instead of #define'd ones, btw, is that they will share storage across compilation modules. Pretty unimportant for most app code, but handy for frameworks.
 

foidulus

macrumors 6502a
Original poster
Jan 15, 2007
904
1
The reason for using extern const strings instead of #define'd ones, btw, is that they will share storage across compilation modules. Pretty unimportant for most app code, but handy for frameworks.

Ultimately though, thanks to the posters on this forum I know that its not really necessary to declare them const, technically it prevents the memory pointed to by the object(but not the pointer itself) to be protected, but since NSStrings are immutable anyhow that really doesn't do a whole lot of anything.

BTW, I ultimately decided just to knock the const off, no difference in the code and no more warnings. Thanks!
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.