views:

31085

answers:

6

I'm developing a Cocoa app, and I'm using constant NSStrings as ways to store key names for my preferences. I understand this is a good idea because it allows easy changing of keys if necessary. Plus, it's the whole 'separate your data from your logic' notion. Anyway, is there a good way to make these constants defined once for the whole app? I'm sure that there's an easy and intelligent way, but right now my classes just redefine the ones they use. Suck.

Thanks.

+2  A: 

If you want something like global constants; a quick an dirty way is to put the constant declarations into the pch file.

Abizern
Editing the .pch is usually not the best idea. You'll have to find a place to actually *define* the variable, almost always a .m file, so it makes more sense to *declare* it in the matching .h file. The accepted answer of creating a Constants.h/m pair is a good one if you need them across the whole project. I generally put constants as far down the hierarchy as possible, based on where they will be used.
Quinn Taylor
+21  A: 

Easiest way:

// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"

Better way:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";

One benefit of the second is that changing the value of a constant does not cause a rebuild of your entire program.

Andrew Grant
I prefer constants over macros.
Georg
I thought you were not supposed to change the value of constants.
Rui Pacheco
Andrew is refering to changing the value of the constant while coding, not while the application is running.
Randall
+2  A: 

As Abizer said, you could put it into the PCH file. Another way that isn't so dirty is to make a include file for all of your keys and then either include that in the file you're using the keys in, or, include it in the PCH. With them in their own include file, that at least gives you one place to look for and define all of these constants.

Grant Limberg
+72  A: 

You should create a header file like

// Constants.h
extern NSString * const MyFirstConstant;
extern NSString * const MySecondConstant;
//etc.

You can include this file in each file that uses the constants or in the pre-compiled header for the project.

You define these constants in a .m file like

// Constants.m
NSString * const MyFirstConstant = @"FirstConstant";
NSString * const MySecondConstant = @"SecondConstant";

Constants.m should be added to your application/framework's target so that it is linked in to the final product.

The advantage of using string constants instead of #define'd constants is that you can test for equality using pointer comparison (stringInstance == MyFirstConstant) which is much faster than string comparison ([stringInstance isEqualToString:MyFirstConstant]) (and easier to read, IMO).

Barry Wark
For an integer constant would it be:extern int const MyFirstConstant = 1;
Dan Morgan
Is it better to say: extern const NSSTring *const MyFirstConstant ?
Nocturne
@Debajit Yes, in the C++ world, const NSString * const MyConstant is correct. Since Objective-C is a C superset, however, const correctness is not part of its history and you get many warnings about passing the incrrect (const) pointer to methods that expect an NSString* even though an NSString * is immutable so could be declared const NSString *.
Barry Wark
Overall, great answer, with one glaring caveat: you DO NOT want to test for string equality with the == operator in Objective-C, since it tests memory address. Always use -isEqualToString: for this. You can easily get a different instance by comparing MyFirstConstant and [NSString stringWithFormat:MyFirstConstant]. Make no assumptions about what instance of a string you have, even with literals. (In any case, #define is a "preprocessor directive", and is substituted before compilation, so either way the compiler sees a string literal in the end.)
Quinn Taylor
In this case, it's OK to use == to test for equality with the constant, if it's truly used as a constant symbol (i.e. the symbol MyFirstConstant instead of a string containing @"MyFirstConstant" is used). An integer could be used instead of a string in this case (really, that's what you're doing--using the pointer as an integer) but using a constant string makes debugging slightly easier as the value of the constant has human-readable meaning.
Barry Wark
Followup question: Let's say you've declared a static **NSArray**. Memory management rules (say on the iPhone) normally mandate retain/release balancing. Because this is static, do you do anything differently? For instance, I imagine I could alloc/init ... and then release in the dealloc. Is that all there is to it? (I've seen sample code where there is no release in the dealloc, and that left me a bit suspicious.)
Joe D'Andrea
This works fine when compiling my app, but when compiling a static library with the same files, I get a weird error: "'asm' or '__attribute__' before '*' token". Any ideas what's up with that?
Shabbyrobe
how do I do this: Constants.m should be added to your application/framework's target so that it is linked in to the final product. ?
amok
@amok Yes, Constants.m needs to be linked (either statically as part of the target) or via a framework to any code that uses the constants.
Barry Wark
+1 for "Constants.m should be added to your application/framework's target so that it is linked in to the final product." Saved my sanity. @amok, do "Get info" on Constants.m and choose the "Targets" tab. Make sure it's checked for the relevant target(s).
PEZ
+11  A: 
kompozer
Minor correction: static variables are visible within a *compilation unit*. Usually, this is a single .m file (as in this example), but it can bite you if you declare it in a header which is included elsewhere, since you'll get linker errors after compilation.
Quinn Taylor
+1  A: 

And what you think about using a class method ? :

+(NSString*)theMainTitle
{
    return @"Hello World";
}

I use it sometimes.

A class method isn't a constant. It has a cost at run time, and may not always return the same object (it will if you implement it that way, but you haven't necessarily implemented it that way), which means you have to use `isEqualToString:` for the comparison, which is a further cost at run time. When you want constants, make constants.
Peter Hosey