1023

I'm developing a Cocoa application, 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 application?

I'm sure that there's an easy and intelligent way, but right now my classes just redefine the ones they use.

Arsen Khachaturyan
  • 6,472
  • 4
  • 32
  • 36
Allyn
  • 19,710
  • 16
  • 55
  • 68
  • 7
    OOP is about *grouping* your data *with* your logic. What are you proposing is just a good programming practice, i.e., making your program easy to change. – Raffi Khatchadourian Dec 13 '11 at 01:01

14 Answers14

1301

You should create a header file like

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

(you can use extern instead of FOUNDATION_EXPORT if your code will not be used in mixed C/C++ environments or on other platforms)

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
  • 105,416
  • 24
  • 177
  • 204
  • 68
    For an integer constant would it be: extern int const MyFirstConstant = 1; – Dan Morgan Feb 15 '09 at 17:20
  • Is it better to say: extern const NSSTring *const MyFirstConstant ? – Debajit Apr 30 '09 at 22:52
  • 4
    @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 May 01 '09 at 03:27
  • 180
    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 Jun 30 '09 at 02:21
  • 75
    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 Jun 30 '09 at 15:48
  • 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 Feb 26 '10 at 22:11
  • 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 Mar 12 '10 at 07:21
  • 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 Sep 01 '10 at 00:56
  • @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 Sep 29 '10 at 16:04
  • 17
    +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 Oct 19 '10 at 19:03
  • 73
    @Barry: In Cocoa, I have seen a number of classes that define their `NSString` properties with `copy` instead of `retain`. As such, they could (and should) be holding a different instance of your `NSString*` constant, and direct memory address comparison would fail. Also, I would presume that any reasonably optimal implementation of `-isEqualToString:` would check for pointer equality before getting into the nitty-gritty of character comparison. – Ben Mosher Jun 10 '11 at 11:45
  • 4
    Can anyone give me a hint on how to do this in XCode 4? (adding Constants.m to the application/framework's target) – Peter Warbo Jun 14 '11 at 15:04
  • @Ben, once you're using constants as strings (e.g. with a `copy` property), all bets are off. You should be using `isEqualToString:` to check for equality in this case. But you would have to do that if you used `#defined` strings as well. – Barry Wark Jun 14 '11 at 15:15
  • 1
    I had problems with this method related to my inexperience with the pch file that were answered here: http://stackoverflow.com/questions/7439011/error-linking-constants-to-pch-in-objective-c-xcode – Victor Van Hee Sep 16 '11 at 02:09
  • 6
    WRT using == to test for constant equality, this is technically OK but in practice a bad idea. Consider the case where you are consuming a JSON response from an HTTP server and checking if a key in the dictionary is a known key name constant. Using == in this case will fail, because the JSON parser constructed unique NSString* objects for your dictionary keys that you are trying to compare against other NSString* objects that of course have a different memory address. Be sane and use isEqualToString and let the runtime deal with memory addressing. – Yetanotherjosh Mar 11 '12 at 20:27
  • How does one localize these strings? NSLocalizedString seems to fail. – Bob Spryn Mar 31 '12 at 00:44
  • 4
    == for string equality test works in some circumstances, as has been noted, but I think it's just a bad idea. It's too fragile in that a change in coding style or a changed design decision later on will cause the 'correctness' of using == to break. It could really come back and bite you on the rump. – occulus Apr 10 '12 at 10:50
  • After doing all you can use these constant by just putting name of the constant like NSLog(@"%@",MyFirstConstant); in all application – Shahid Aslam May 22 '12 at 05:39
  • @DanMorgan in my case when I used `extern int const playBoardBlock = 1;` in const.h file it comes with a warning, i.e. `'ertern' variable has an initializer`, what's the suggestion. – rptwsthi Dec 05 '12 at 10:26
  • 2
    @rptwsthi Don't assign your const a value in the header file -- instead assign the value in the .m file. You'll still need to declare the const (without assigning a value) in the .h file. – occulus Dec 18 '12 at 16:40
  • 2
    A note/warning. There can be problems when using `NSString` constants like this with ARC. Sometimes the constant is unexpectedly automatically released while app is running (I've experienced that while passing const string to `NSNotificationCenter`, as notification name. At first pass it works fine, but when I want to emit second notification the app gets crashed with "bad memory access"). So, I was end up with `#define`-styled constants. – Cemen Jun 28 '13 at 08:17
  • 1
    FOUNDATION_EXPORT is a minor convenience that defines as FOUNDATION_EXTERN which in turn defines as `extern "C"` for C++ and otherwise as `extern` (other than a slight difference for windows as target...meh) – uchuugaka Jul 23 '13 at 03:46
  • 4
    For the record, == *should* work for NSStrings due to string interning, as long as you're working with constant, immutable strings (i.e. not +stringWithFormat:). However, -isEqualToString:, if it is implemented correctly, *should* short-circuit itself with a pointer equality check first. But, I should note, all of these *should*s could change, so write defensively. – paulrehkugler Dec 05 '13 at 18:19
  • 9
    Do not use `==` for string comparisons under any circumstances. Yes, if you understand the compiler/runtime (!), and you're careful, you can get it right. But it's a leaky abstraction, and requires special invisible knowledge in one part of a codebase about the way another part is put together. @BarryWark: I do sympathize with your point about using them as "symbols" and scoping their use to that sort of thing, but even then, because they are declared publicly as `NSString`s, their "symbolness" is also special knowledge. Better to be consistent about compares and trust `-isEqualToString:`. – Ben Zotto Jan 10 '14 at 03:55
  • 2
    If I define all my constant strings in such a manner, does it increase my memory footprint? – footyapps27 Feb 17 '14 at 15:37
  • 1
    Actually, it reduces the footprint because it makes sure that the string constant only exists once in your program. If you use #define, you _could_ have a different string every time the constant is used. BTW. strings created like @"string" are never deallocated, and copy doesn't copy them. – gnasher729 Apr 14 '14 at 10:53
  • Once we do this, how do we access/use these constants from other classes? I am trying "Constants.MY_CONSTANT" but I am getting a "Property MY_CONSTANTS not found on object of type Constants" error. – clocksmith Aug 07 '14 at 21:20
  • I'm curious, and in searching haven't found a good answer to this question. What is the benefit of declaring the constant in two places? I know with the `extern` keyword it must be defined and then initialized in separate lines, but why not just use `NSString *const MyConstant = @"MyConstantValue"` in a single `.h` file? – Tim Arnold Mar 11 '15 at 17:07
  • @TimArnold With `MyConst` defined as you have, including the `.h` file in multiple spots (as desired by the OP) would result in a **duplicate symbol** error during linking. – dingo sky Mar 29 '15 at 15:23
  • These constants are global variables and therefore could potentially conflict with global variables of frameworks added to the project? – jk7 Oct 06 '16 at 19:32
  • Does this work with ARC? I am having issues where my const NSStrings are null at runtime. – Tjalsma Aug 20 '17 at 01:20
282

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
  • 56,758
  • 22
  • 127
  • 140
  • 12
    I thought you were not supposed to change the value of constants. – ruipacheco Dec 28 '09 at 12:38
  • 71
    Andrew is refering to changing the value of the constant while coding, not while the application is running. – Randall Feb 27 '10 at 11:02
  • 7
    Is there any added value in doing `extern NSString const * const MyConstant`, ie, making it a constant pointer to a constant object rather than just a constant pointer? – Hari Honor Mar 01 '12 at 13:46
  • 4
    What happen, if I use this declaration in the header file, static NSString * const kNSStringConst = @"const value"; What's the difference between not declaring and init separately in .h and .m files? – karim Mar 08 '12 at 13:06
  • @karim Header files get `#include`ed into many `.m` files. In all C languages this basically a copy and paste into one _translation unit_. The short version is that if you do it in the header, changing the value may cause a lot of files to be recompiled. This will become noticable on large projects (maybe 50k+ SLOC) – kizzx2 Sep 09 '12 at 14:56
  • This is more than just a preference - you're using macros when you shouldn't. It's like using goto - it exists for legacy and for some special cases (IE, to break from an inner loop,) but shouldn't be used elsewhere. If you just want a constant, you should use a const. It's what they were made/introduced for. – ArtOfWarfare Mar 20 '13 at 01:38
  • @ArtOfWarfare, when *should* macros be used, if not here? – Dogweather Apr 24 '13 at 08:27
  • 4
    @Dogweather - Someplace where only the compiler knows the answer. IE, if you wanted to include in an about menu which compiler was used to compile a build of an application, you could place it there since the compiled code otherwise wouldn't have anyway of knowing. I can't think of many other places. Macros certainly shouldn't be used in many places. What if I had #define MY_CONST 5 and elsewhere #define MY_CONST_2 25. The result is that you may very well end up with a compiler error when it tries to compile 5_2. Do not use #define for constants. Use const for constants. – ArtOfWarfare Apr 24 '13 at 14:03
  • If I define all my constant strings in such a manner, does it increase my memory footprint? – footyapps27 Feb 17 '14 at 15:37
191

There is also one thing to mention. If you need a non global constant, you should use static keyword.

Example

// In your *.m file
static NSString * const kNSStringConst = @"const value";

Because of the static keyword, this const is not visible outside of the file.


Minor correction by @QuinnTaylor: 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

Community
  • 1
  • 1
kompozer
  • 2,064
  • 1
  • 11
  • 12
  • 41
    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 Jun 30 '09 at 02:22
  • If I don't use the static keyword, will kNSStringConst be available throughout the project? – Danyal Aytekin Nov 07 '11 at 12:31
  • 2
    Ok, just checked... Xcode doesn't provide autocompletion for it in other files if you leave static off, but I tried putting the same name in two different places, and reproduced Quinn's linker errors. – Danyal Aytekin Nov 07 '11 at 13:47
  • 1
    static in a header file doesn't give linker problems. However, each compilation unit including the header file will get its own static variable, so you get 100 of them if you include the header from 100 .m files. – gnasher729 Apr 14 '14 at 10:55
  • @kompozer In which part of the .m file do you place this? – Basil Bourque May 22 '14 at 19:25
  • @BasilBourque Good practice is to put them right above the `@implementation ...` – kompozer Feb 02 '15 at 09:28
  • @kompozer, Why this naming? – Iulian Onofrei Apr 30 '15 at 14:16
  • @IulianOnofrei I'm not sure what you mean. Using `k` in front of the `static` is an used conventions. Nowhere days I don't do this anymore and call `static` vars something like ```static NSString * const StringConst = @"const value";``` – kompozer May 07 '15 at 13:48
  • @kompozer, Yes, I was referring to the variable's name. Is it standard Apple convention? Why not `STRING_CONST` instead of `StringConst`. Is this only in the Java world? – Iulian Onofrei May 07 '15 at 13:50
  • If I put this in a header file, what's the problem? – Gary Lyn May 21 '15 at 03:52
117

The accepted (and correct) answer says that "you can include this [Constants.h] file... in the pre-compiled header for the project."

As a novice, I had difficulty doing this without further explanation -- here's how: In your YourAppNameHere-Prefix.pch file (this is the default name for the precompiled header in Xcode), import your Constants.h inside the #ifdef __OBJC__ block.

#ifdef __OBJC__
  #import <UIKit/UIKit.h>
  #import <Foundation/Foundation.h>
  #import "Constants.h"
#endif

Also note that the Constants.h and Constants.m files should contain absolutely nothing else in them except what is described in the accepted answer. (No interface or implementation).

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Victor Van Hee
  • 8,809
  • 7
  • 30
  • 39
  • I did this but some files throw error on compile "Use of undeclared identifier 'CONSTANTSNAME' If I include the constant.h in the file throwing the error, it works, but that is not what I want to do. I have cleaned, shutdown xcode and build and still problems... any ideas? – J3RM Aug 30 '12 at 20:35
50

I am generally using the way posted by Barry Wark and Rahul Gupta.

Although, I do not like repeating the same words in both .h and .m file. Note, that in the following example the line is almost identical in both files:

// file.h
extern NSString* const MyConst;

//file.m
NSString* const MyConst = @"Lorem ipsum";

Therefore, what I like to do is to use some C preprocessor machinery. Let me explain through the example.

I have a header file which defines the macro STR_CONST(name, value):

// StringConsts.h
#ifdef SYNTHESIZE_CONSTS
# define STR_CONST(name, value) NSString* const name = @ value
#else
# define STR_CONST(name, value) extern NSString* const name
#endif

The in my .h/.m pair where I want to define the constant I do the following:

// myfile.h
#import <StringConsts.h>

STR_CONST(MyConst, "Lorem Ipsum");
STR_CONST(MyOtherConst, "Hello world");

// myfile.m
#define SYNTHESIZE_CONSTS
#import "myfile.h"

et voila, I have all the information about the constants in .h file only.

Regexident
  • 29,108
  • 10
  • 91
  • 98
Krizz
  • 10,788
  • 1
  • 26
  • 42
  • Hmm, there is a bit of a caveat however, you cannot use this technique like this if the header file is imported into the precompiled header, because it won't load the .h file into the .m file because it was already compiled. There is a way though - see my answer (since I can't put nice code in the comments. – Scott Little Dec 02 '11 at 23:43
  • I can't get this working. If I put #define SYNTHESIZE_CONSTS before #import "myfile.h" it does NSString*... in both the .h and .m (Checked using the assistant view and preprocessor). It throws redefinition errors. If I put it after #import "myfile.h" it does extern NSString*... in both files. Then it throws "Undefined symbol" errors. – arsenius Jan 24 '14 at 16:23
29

I myself have a header dedicated to declaring constant NSStrings used for preferences like so:

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;

Then declaring them in the accompanying .m file:

NSString * const PPRememberMusicList = @"Remember Music List";
NSString * const PPLoadMusicAtListLoad = @"Load music when loading list";
NSString * const PPAfterPlayingMusic = @"After playing music";
NSString * const PPGotoStartupAfterPlaying = @"Go to startup pos. after playing";

This approach has served me well.

Edit: Note that this works best if the strings are used in multiple files. If only one file uses it, you can just do #define kNSStringConstant @"Constant NSString" in the .m file that uses the string.

MaddTheSane
  • 2,754
  • 22
  • 26
25

A slight modification of the suggestion of @Krizz, so that it works properly if the constants header file is to be included in the PCH, which is rather normal. Since the original is imported into the PCH, it won't reload it into the .m file and thus you get no symbols and the linker is unhappy.

However, the following modification allows it to work. It's a bit convoluted, but it works.

You'll need 3 files, .h file which has the constant definitions, the .h file and the .m file, I'll use ConstantList.h, Constants.h and Constants.m, respectively. the contents of Constants.h are simply:

// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"

and the Constants.m file looks like:

// Constants.m
#ifdef STR_CONST
    #undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include "ConstantList.h"

Finally, the ConstantList.h file has the actual declarations in it and that is all:

// ConstantList.h
STR_CONST(kMyConstant, "Value");
…

A couple of things to note:

  1. I had to redefine the macro in the .m file after #undefing it for the macro to be used.

  2. I also had to use #include instead of #import for this to work properly and avoid the compiler seeing the previously precompiled values.

  3. This will require a recompile of your PCH (and probably the entire project) whenever any values are changed, which is not the case if they are separated (and duplicated) as normal.

Hope that is helpful for someone.

Scott Little
  • 1,857
  • 15
  • 15
  • 1
    Using #include fixed this headache for me. – OdieO Apr 03 '13 at 07:21
  • Does this have any performance/memory loss when compared to the accepted answer? – Gyfis May 31 '15 at 13:23
  • In answer to the performance compared to the accepted answer, there is none. It is effectively the exact same thing from the point of view of the compiler. You end up with the same declarations. They'd be EXACTLY the same if you replaced the `extern` above with the `FOUNDATION_EXPORT`. – Scott Little Aug 12 '15 at 15:29
14
// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";
axel22
  • 31,198
  • 9
  • 120
  • 134
rahul gupta
  • 396
  • 3
  • 8
12

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
  • 19,335
  • 10
  • 59
  • 84
11

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

Forge
  • 5,854
  • 6
  • 41
  • 58
Abizern
  • 129,329
  • 36
  • 198
  • 252
  • 7
    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 Jun 30 '09 at 02:27
8

Try using a class method:

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

I use it sometimes.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
groumpf
  • 181
  • 1
  • 3
  • 6
    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 Dec 06 '09 at 15:51
  • 2
    @Peter Hosey, while your comments are right, we take that performance hit once per LOC or more in "higher-level" languages like Ruby without every worrying about it. I'm not saying you're not right, but rather just commenting on how standards are different in different "worlds." – Dan Rosenstark Jan 17 '11 at 20:59
  • 1
    True on Ruby. Most of the performance people code for is quite unnecessary for the typical app. – Peter DeWeese May 25 '11 at 21:20
8

If you like namespace constant, you can leverage struct, Friday Q&A 2011-08-19: Namespaced Constants and Functions

// in the header
extern const struct MANotifyingArrayNotificationsStruct
{
    NSString *didAddObject;
    NSString *didChangeObject;
    NSString *didRemoveObject;
} MANotifyingArrayNotifications;

// in the implementation
const struct MANotifyingArrayNotificationsStruct MANotifyingArrayNotifications = {
    .didAddObject = @"didAddObject",
    .didChangeObject = @"didChangeObject",
    .didRemoveObject = @"didRemoveObject"
};
P.J.Radadiya
  • 1,372
  • 10
  • 16
onmyway133
  • 38,911
  • 23
  • 231
  • 237
  • 1
    A great thing! But under ARC you will need to prefix all variables in struct declaration with `__unsafe_unretained` qualifier to get it working. – Cemen Aug 02 '15 at 06:20
7

I use a singleton class, so that I can mock the class and change the constants if necessary for testing. The constants class looks like this:

#import <Foundation/Foundation.h>

@interface iCode_Framework : NSObject

@property (readonly, nonatomic) unsigned int iBufCapacity;
@property (readonly, nonatomic) unsigned int iPort;
@property (readonly, nonatomic) NSString * urlStr;

@end

#import "iCode_Framework.h"

static iCode_Framework * instance;

@implementation iCode_Framework

@dynamic iBufCapacity;
@dynamic iPort;
@dynamic urlStr;

- (unsigned int)iBufCapacity
{
    return 1024u;
};

- (unsigned int)iPort
{
    return 1978u;
};

- (NSString *)urlStr
{
    return @"localhost";
};

+ (void)initialize
{
    if (!instance) {
        instance = [[super allocWithZone:NULL] init];
    }
}

+ (id)allocWithZone:(NSZone * const)notUsed
{
    return instance;
}

@end

And it is used like this (note the use of a shorthand for the constants c - it saves typing [[Constants alloc] init] every time):

#import "iCode_FrameworkTests.h"
#import "iCode_Framework.h"

static iCode_Framework * c; // Shorthand

@implementation iCode_FrameworkTests

+ (void)initialize
{
    c  = [[iCode_Framework alloc] init]; // Used like normal class; easy to mock!
}

- (void)testSingleton
{
    STAssertNotNil(c, nil);
    STAssertEqualObjects(c, [iCode_Framework alloc], nil);
    STAssertEquals(c.iBufCapacity, 1024u, nil);
}

@end
Howard Lovatt
  • 930
  • 8
  • 14
1

If you want to call something like this NSString.newLine; from objective c, and you want it to be static constant, you can create something like this in swift:

public extension NSString {
    @objc public static let newLine = "\n"
}

And you have nice readable constant definition, and available from within a type of your choice while stile bounded to context of type.

Renetik
  • 4,282
  • 37
  • 50