22

I have been using static const in my header files as so:

static NSString * const myString = @"foo";

But have read that this is not the 'safe' or correct way of doing this. Apparently, if I want my const strings to be accessed from another class, I should be declaring the string in my .h as:

extern NSString * const myString;

Then in my .m file:

NSString * const myString = @"foo";

Is this correct? If so, what is the reason not to declare it as static directly in my .h file? It works perfectly fine, and I can not see any 'safety' issues around this. It is a const, so therefore it can not be changed from outside and its something I intentionally need accessed outside of the class. The only other thing I can think of is that to hide the value of the string?

IPS Brar
  • 346
  • 1
  • 14
ssh88
  • 393
  • 1
  • 3
  • 14
  • Please study a good "C" book, a through understanding of "C" is a really good base for writing code. – zaph May 14 '14 at 11:15
  • you can change the value of the static variable anytime you want, const is not preventing this in any way. Extern does so, as you can only change the value in an declaration statement, so you can not change the variable at runtime. – Jonathan Cichon May 14 '14 at 11:18

7 Answers7

41

Your first variant

static NSString * const myString = @"foo"; // In .h file, included by multiple .m files

defines an myString variable locally in each "translation unit" (roughly speaking: in each .m source file) that includes the header file. All string objects have the same contents "foo", but it may be different objects so that the value of myString (the pointer to the string object) may be different in each unit.

Your second variant

extern NSString * const myString; // In .h file, included by multiple .m files
NSString * const myString = @"foo"; // In one .m file only

defines a single variable myString which is visible "globally".

Example: In one class you send a notification with myString as user object. In another class, this notification is received and the user object compared to myString.

In your first variant, the comparison must be done with isEqualToString: because the sending and the receiving class may have different pointers (both pointing to a NSString object with the contents "foo"). Therefore comparing with == may fail.

In your second variant, there is only one myString variable, so you can compare with ==.

So the second variant is safer in the sense that the "shared string" is the same object in each translation unit.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
3

There is no reason that I know of, for declaring anything as external in Objective-C, while you use Objective-C only in your project. I could think of reasons when you mix it with C or assembler modules etc.

However, extern has the advantage that the constant will really exist only ones in the whole project, if it is that what you want to achieve, if you really need to save on these 20 or so bytes. But that carries the risk of conflicting names. Other libraries may have declared their own externals using the same name. And the linker would care for them using the very same space in memory, although they may be of different types.

And yes, the extern declaration in the header should be accompanied by a corresponding definition in the .m file. I am not sure but I think you could assign @"foo" in the .h file already. You could even declare it outside of @interface/@implementation-@end blocks. (Never tried that myself). In that case the variable would be global and accessible from everywhere even without the extern keyword. On compile time the compiler would complain about accessing them when he does not see its declaration within the chain of #include statements. But academically, one single .m file could contain two or more classes (which I clearly do not advise) and then the variable would be accessible from both classes although it belongs to none of them. In the end, OjbC is just an add-on to ANSI C.

However, there is no point in making them static. These constants are static anyway. They are constants. The purpose of a static variable within a class or even method is it's scope (visibility) is limited to that class but there is only one instance on runtime that is shared by all instances of the class.

Example:

@implementation AClass : NSObject 

static NSString *someString 

- (void) setString:(NSString*) aString{
  someString = aString;
}

- (NSString*) getString (){
  return someString;
}

... and somewhere else:

AClass * a = [[AClass alloc] init];
AClass * b = [[AClass alloc] init]; 

[a setString:@"Me"];
[b setString;@"You"];
NSLog (@"String of a: ", [a getString]);

would print out You but not Me

If that is what you want, and only then, use static.

Using simple preprocessor macros (which I prefer, but I am kinda oldschool here) has the disadvantage that these strings would be copied into the binary each time when the macro is used. Apparently that is not an option for you anyway because you did not even asked for them. However, for most usages preprocessor macros in commonly shared .h files would do the trick of managing constants across classes.

Hermann Klecker
  • 14,039
  • 5
  • 48
  • 71
  • "These constants are static anyway. They are constants." AFAIK constants do not have to be static. – Franklin Yu Feb 21 '16 at 05:55
  • @Franklin: in this case, "static" means a "class" member, not an "instance" member; if not static the output would be "Me" – tontonCD Sep 26 '20 at 23:57
  • @tontonCD I said *constants* do not have to be static. You said that the variable `someString` (*not* a constant) is static. These two statements do not conflict with each other, so I’m confused what you are arguing against. – Franklin Yu Sep 29 '20 at 02:53
1

Using static NSString* const myString = @"foo"; in a header file means that each translation unit gets a separate myString variable. I think the linker may consolidate these, but I wouldn't count on it. That means that code which compares a string it has received using if (someString == myString) ... might get false even if the caller passed myString in, if the caller were from a different translation unit. (Of course, code should use -isEqualToString: instead of ==, but with a properly-declared string constant the latter may be workable.)

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
1

Other answers mentioned that the extern way saves you some memory (albeit marginal), and allows you to compare by == operator (which isn’t a good habit anyway). In addition, if you are writing a dynamically-linked library, there is another advantage: since there is only one copy of the data, you can change the data in next release atomically, without requiring clients to re-compiling their code.

One possible advantage of the static way if the variable were not an object (NSString in your case) but of a primitive type (like an integer) is that, the compiler is able to optimize the access to the constant:

When you declare a const in your program,

int const x = 2;

Compiler can optimize away this const by not providing storage to this variable rather add it in symbol table. So, subsequent read just need indirection into the symbol table rather than instructions to fetch value from memory.

Taken from this answer.

Franklin Yu
  • 8,920
  • 6
  • 43
  • 57
0

When it comes to storage classes, static means one of two things.

A static variable inside a method or function retains its value between invocations.

A static variable declared globally can be called by any function or method, so long as those functions appear in the same file as the static variable. The same goes for static functions.

Whereas static makes functions and variables globally visible within a particular file, extern makes them visible globally to all files.

Any time your application uses a string constant with a non-linguistic value in a public interface, it should declare it as an external string constant.

The pattern is to declare an extern NSString * const in a public header, and define that NSString * const in the implementation.

Source: nshipster.com/c-storage-classes

Tariq
  • 9,861
  • 12
  • 62
  • 103
0

But have read that this is not the 'safe' or correct wary of doing this.

It is safe, unless the program is multi-threaded. In that case, it is not safe unless you protect the global variable with a mutex.

It is however not correct. NSString * const myString means a constant pointer to (non-constant) data. Most likely, you want the variable itself to be constant: const NSString* myString.

Apparently if i want my const strings to be accessed from another class i should be declaring the string in my .h as: extern ...

Correct. Note that extern is acceptable for constants only. It is not acceptable for non-constant variables: global variables are considered to be very bad practice.

if so what is the reason to not just use declare it as static directly in my .h file as it works perfectly fine

The only reason why you would ever want to declare it as static is because you want to limit the scope of the variable to the local .c file, in other words private encapsulation. Therefore it does not ever make sense to declare static variables in a .h file.

It is a const so therefore can not be changed from outside

It can be changed, because it is not const, see my first remark.


Generally, don't do things like this. All of the above fiddling suggests that you have flaws in your very program design, that need to be fixed. You need to design your program in an object-oriented manner, so that every code module is autonomous and doesn't know or care about anything except it's designated task.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 2
    `const NSString* myString` is wrong. First, it means that the pointer is not const and so could be made to point to something else. That violates the intent of `myString` being a constant. Second `NSString` is already immutable, so it doesn't help making the pointed-to object `const`. It could also be harmful, since immutable may not mean strictly `const` and we wouldn't want to impose that requirement on the object. For example, `NSString` might do internal caching when asked for its contents in a different encoding, which should be allowed. – Ken Thomases May 14 '14 at 11:31
  • @KenThomases Why would anyone would take a constant from a library and set it to point at something else? That doesn't make sense. But if you are worried about that I guess you could make it `const NSString* const myString`. However, in my experience `const * const` most often only suggests that the programmer is paranoid and therefore adds pointless clutter. If the object is immutable anyway, all of this seems rather superfluous. If a class is designed as immutable, it shouldn't fiddle around with any internals except during object construction, or it is not immutable by definition. – Lundin May 14 '14 at 11:40
  • First, if somebody could set it, then, by Murphy's Law, they probably will by accident. For instance, using `=` when they meant `==`. Second, "immutable" means only in terms of what's externally visible through its public interface. That's why C++ has the `mutable` keyword, for example. Caching is an implementation detail for optimization and doesn't change the object's value as seen from outside. – Ken Thomases May 14 '14 at 12:03
  • @KenThomases Counter Murphy's law by using professional tools such as compilers with warnings enabled or static analysers. If an object cannot be made const immutable because of some implementation detail, then don't use that class as if it was immutable, simple as that. And then of course, one may question if the whole immutable design pattern ever makes sense anywhere in the real world, but that's a discussion for some other forum... – Lundin May 14 '14 at 12:45
-1
#define myString @"foo"

There is example why:

You will be able concatenate strings in compile time:

NSLog(@"%@", @"Say hello to " myString);

will output: Say hello to foo

Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
  • Did you read the question? OP asks to provide explanation about two ways of declaring constants: static vs extern – Vlad Papko May 14 '14 at 11:53
  • @Visput, my answer is none of them, better use `#define` – Cy-4AH May 14 '14 at 11:55
  • Could you explain why is it better? For me I see disadvantage of using @define because const value @"foo" is visible from .h file. – Vlad Papko May 14 '14 at 11:59
  • 1
    @Cy-4AH define is not a good approach. Define removes the visibility of the data type! Wouldn't pass a code review on a good team – ssh88 May 14 '14 at 12:01
  • @Ssh88, I'd clearly see other disadvantages for #define, especially when I want to size opimize my binaries. But @"foo" is clearly typed as NSString. There is no type hidden at all. – Hermann Klecker May 14 '14 at 12:17
  • @HermannKlecker I didn't mean just in this case. I was generalising in the sense to avoid using #define macro over the data type when possible for reasons as you suggested – ssh88 May 14 '14 at 12:27
  • @Cy-4AH Apple's recommendation is extern: https://developer.apple.com/library/mac/documentation/cocoa/conceptual/codingguidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-1003095 ... Define constants for strings used for such purposes as notification names and dictionary keys. By using string constants, you are ensuring that the compiler verifies the proper value is specified (that is, it performs spell checking). – Tariq May 14 '14 at 12:50
  • @Visput, as I already writed in my answer: you will be able concatenate strings in compile time. Use `extern` if you wish lose processor time for string concatenation. – Cy-4AH May 14 '14 at 13:00
  • @Tariq, Apple uses `extern` for string constans, because it's value can be different in different versions of framework. For own progect, when you are not going export string constan(for example in library) you are free to use `#define` – Cy-4AH May 14 '14 at 13:07
  • @Visput, ofcourse if you wish hide string values (for example if it's pass phrase) then you should use `extern` – Cy-4AH May 14 '14 at 13:12
  • @Cy-4AH The #define is a pre-processor macro. That means that it basically goes through your code and replace your macro with what you've defined. If you use a const, it's going to be a pointer to the string in memory. It's way more efficient than having the same string being allocated wherever/whenever it is used. – Tariq May 15 '14 at 10:27
  • @HermannKlecker, there is don't need comma. There is used compile time string concatenation. – Cy-4AH May 15 '14 at 11:48
  • @Tariq, it's not allocated every time, it's reused from app's string table. You can check with "%p" that in both cases will be used the same pointer. And I know very well that `#define` is pre-processor macro - very convinient tool that all of you afraid as fire. – Cy-4AH May 15 '14 at 12:23