45

In Objective-C, my understanding is that the directive @"foo" defines a constant NSString. If I use @"foo" in multiple places, the same immutable NSString object is referenced.

Why do I see the following code snippet so often (for example in UITableViewCell reuse)?

static NSString *CellId = @"CellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellId];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:CellId];

Instead of just:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellId"];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:@"CellId"];

I assume it is to protect me from making a typo in the identifier name that the compiler wouldn't catch. But if so, couldn't I just use

#define kCellId @"CellId"

and avoid the static NSString * bit? Or am I missing something?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Trashpanda
  • 14,758
  • 3
  • 29
  • 18
  • This also **guarantees pointer equality** which can give you some performance benefit. As far as I know, macro symbols don't have the guarantee. – eonil Feb 27 '13 at 13:29

6 Answers6

61

It's good practice to turn literals into constants because:

  1. It helps avoid typos, like you said
  2. If you want to change the constant, you only have to change it in one place

I prefer using static NSString* const, because it's slightly safer than #define. I tend to avoid the preprocessor unless I really need it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tom Dalling
  • 23,305
  • 6
  • 62
  • 80
  • 18
    And string constants can be easily examined in gdb. #define is a real pain. But your use of const here is incorrect. It needs to be "static NSString* const". Your const ordering doesn't actually achieve anything useful in objc. Try reassigning your string variable done this way versus my way and you'll see that in one case you get a compiler error and the other you don't. – Rob Napier Dec 21 '09 at 01:33
  • 1
    You're not actually avoiding the preprocessor. All @ sigils are actually preprocessor directives. – uchuugaka Oct 31 '13 at 02:40
  • 3
    @uchuugaka Perhaps by "avoiding the preprocessor" he meant "avoid relying on preprocessor macro replacement (i.e., `#define`) and all potential issues that might entail". – Nicolas Miari Dec 16 '14 at 06:54
35

I love all the answers here without a simple example of how to correctly declare one... so...

If you want the constant to be externally visible (i.e., "global")... declare it as such in a header...

extern NSString *const MyTypoProneString;

and define it in a .m file, outside any @implementation like...

NSString * const MyTypoProneString = @"iDoNtKnOwHoW2tYpE";

That said... if you simply want a static const that is local to your class' implementation (or even a certain method!)... simply declare the string inside the implementation (or method) as...

static NSString *MavisBeacon = @"She's a freakin' idiot";

Although I do show how to do this... I have yet to be convinced that this style is in any way better than the ridiculously shorter, simpler, and less repetitive single declaration, a la...

#define SomeStupidString @"DefiningConstantsTwiceIsForIdiots"

Use #define's... they are way less annoying.. Just don't let the preprocessor-player-haters get you down.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex Gray
  • 16,007
  • 9
  • 96
  • 118
  • 7
    Slightly old, but actually there's a really, really good reason for not using #define - #define macros are basically replacements - so anytime you call SomeStupidString in code, the preprocessor replaces that with a *new* string literal. Using extern / static const instead points all references to a single place in memory. This is *much* more memory efficient and performant. – Matt S. Feb 25 '14 at 03:39
  • Good to know... I wish I could figure out how to create them _en masse_, with a variadic Macro, _or something_, though... I've yet to find a good way of _churning them out_, let alone not having to split up the declaration + the implementation in TWO (whine, sob) files... – Alex Gray Feb 25 '14 at 06:55
  • 3
    Matt S.'s comment is actually not correct. Identical string literals, anywhere within an app, point to the same object. Create two NSString literals and inspect their pointers. It appears that, even in RELEASE, they are the same object. – Logan Moseley May 01 '15 at 18:00
  • Logan - that must be a recent compiler optimization. That would be nice if they did :) – Matt S. Aug 20 '15 at 19:42
  • Given that the cell reuse identifier is almost always used and typed only twice ever - once in the storyboard or nib, and once in the method - and both times usually a copy-paste from the class name anyway - just putting a string directly into the dequeue call is the simplest IMO. As for typos, you'd spot that instantly on first run in any case, although personally I've never managed to make that kind of typo anyway. – theLastNightTrain Sep 18 '15 at 08:05
9

You should make the static variable const.

One difference between static variable and a macro is that macros don't play well with debuggers. Macros also aren't type-safe.

Much of the static-var-vs-macro advice for C and C++ applies to Objective-C.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
outis
  • 75,655
  • 22
  • 151
  • 221
3

It is not guaranteed that when using @"foo"in multiple places the runtime uses the same storage for them, and certainly may not be the case across compilation unit or library boundaries.
I would rather use static NSString *string = @"foo", especially with a lot of literal strings.

apaderno
  • 28,547
  • 16
  • 75
  • 90
2

I assume it is to protect me from making a typo in the identifier name that the compiler wouldn't catch.

Correct. It's just basic defensive programming practice. The compiled result (hopefully) is the same either way.

But If so, couldn't I just:

#define kCellId @"CellId"

and avoid the static NSString * bit? Or am I missing something?

Yes. But the kCellId symbol would be globally defined, at least in your compilation unit. Declaring a static variable makes the symbol local to that block.

You will typically see string constants defined as global variables or static variables rather than preprocessor defines. This helps ensure that you're only dealing with a single string instance between different compilation units.

Community
  • 1
  • 1
Darren
  • 25,520
  • 5
  • 61
  • 71
1

Here's an expanded version of my comment to Alex Gray:

Anytime you think you should use a #define for string macros, you mostly likely shouldn't. The reason is because #define macros are basically regex replacements to the preprocessor. Anytime the preprocessor sees a macro called, it replaces it with whatever you defined. This means a new string literal every single time will get allocated into memory, which is really bad in places like cell reuse identifiers (thus why Apple's UITableViewController default code uses a static).

Using extern / static const instead points all references to a single place in memory, as eonil mentioned. This is much more memory efficient and performant, which is very important on mobile devices.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Matt S.
  • 1,882
  • 13
  • 17