PDA

View Full Version : Best practices for how you store your keys for things like NSUserDefaults




foidulus
Sep 25, 2010, 06:14 AM
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
Sep 25, 2010, 11:27 AM
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
Sep 25, 2010, 11:40 AM
^ 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
Sep 25, 2010, 11:47 AM
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
Sep 25, 2010, 12:00 PM
I believe the way they're done in most of AppKit (instead of #define) is:

extern NSString *const Key;

In the .h file and:

NSString *const Key = @"Key Value";

In the corresponding .m

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

foidulus
Sep 25, 2010, 12:33 PM
I believe the way they're done in most of AppKit (instead of #define) is:



extern NSString *const Key;

In the .h file and:

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:



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:



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

compiles just without any warnings

Maybe an oversight on apple's part?

kainjow
Sep 25, 2010, 12:38 PM
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
Sep 25, 2010, 01:18 PM
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:
const NSString *GFSAVE_SERVER_DEFAULTS_NAME_KEY=@"savedservername";

is not the same as:
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:
cd /System/Library/Frameworks/Foundation.framework/Headers/
grep const *.h | grep NSString
sample output:
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:
#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:
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
Sep 26, 2010, 01:50 PM
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
Sep 26, 2010, 11:49 PM
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!