65

The following code is giving me errors:

//  constants.h
extern NSArray const *testArray;
//  constants.m
NSArray const *testArray = [NSArray arrayWithObjects:  @"foo", @"bar", nil];

The error I get is
initializer element is not constant

Or if I take away the pointer indicator (*) I get:
statically allocated instance of Objective-C class 'NSArray'

FreeAsInBeer
  • 12,937
  • 5
  • 50
  • 82
Andrew
  • 8,363
  • 8
  • 43
  • 71
  • 1
    By 'constant' what do you mean? Immutable contents of the object, or immutable pointer? – zneak Mar 12 '10 at 23:14
  • Well my criteria are: 1) The values can't be accidentally changed (So i guess NSArray is good), 2) available anywhere in the program (I'm including `constants.h` in my .pch file) , 3) Only declared once – Andrew Mar 12 '10 at 23:47
  • 1
    [See this SO question](http://stackoverflow.com/questions/1059708/defining-a-constant-in-objective-c) – conorgriffin Mar 12 '10 at 23:13

6 Answers6

71

In short, you can't. Objective-C objects are, with the exception of NSString, only ever created at runtime. Thus, you can't use an expression to initialize them.

There are a handful of approaches.

(1) Declare NSArray *testArray without the const keyword and then have a bit of code that sets up the value that is invoked very early during application lifecycle.

(2) Declare a convenient class method that returns the array, then use a static NSArray *myArray within that method and treat it as a singleton (search SO for "objective-c singleton" for about a zillion answers on how to instantiate).

h.and.h
  • 690
  • 1
  • 8
  • 26
bbum
  • 162,346
  • 23
  • 271
  • 359
  • 5
    If you're only dealing with an array of `NSStrings` like in the question, you could create a `C` style array in your constants file and then use some `C` methods from there to access the values. See [my answer below](http://stackoverflow.com/a/11485727/630996) – AngeloS Jul 14 '12 at 17:13
  • 2
    @bbum -- this is still true even with the `@[...]` sugar, yes? – Ben Mosher Nov 12 '13 at 01:44
  • 2
    One could argue that with blocks now being full blown retainable object types they are a second type of Objective-C object where the compiler knows the layout and can create constant instances. It's possible to initialize static storage duration variables from a block literal. – Nikolai Ruhe Jan 21 '14 at 17:04
  • @NikolaiRuhe True, but it would be a total pain to allow for static block initialization only to have the compiler then give you the "non-static initializer" error with a seemingly innocuous change. – bbum Jan 21 '14 at 17:36
34

A little late to the party, but since you're not changing the values through the course of the program, if you were only dealing with strings, you could do the following by declaring your array using a C array:

extern NSString * const MY_CONSTANT_STRING_ARRAY[];

in your constants.h file, and then in your constants.m you could add objects to it like so:

NSString * const MY_CONSTANT_STRING_ARRAY[] = { @"foo", @"bar" };

Then to access a member, you could do a for loop like so with a C sizeof() operator:

This obviously is a C array and not a NSArray so you don't get all of the fun methods attached to it like objectAtIndex:, so you could create a helper function somewhere in your program that loops through all of the strings using the method I outlined above and returns an NSArray (or NSMutableArray even). But, if you were doing what I am and just need a constant array of NSString values to use throughout your program, this method works the best.

Doing it this way encapsulates all of your string array contants in constants.h, and is still available throughout your program by adding constants.h in your .pch file instead of creating a singleton just for this array of values or setting the array with a little code, which sorta defeats the purpose of a constants file because it removes the actual constants out of the constants file..

EDIT per @JesseGumpo's Comment:

Since there may be issues with using sizeof() to determine the size of the array, a simple workaround is to declare the size of the array in your constants file like so:

//.h
extern int SIZE_OF_MY_CONSTANTS_ARRAY;  

///.m
int SIZE_OF_MY_CONSTANTS_ARRAY = 2;

And then to access the members in a for loop you can do so like this:

for (int i=0; i < SIZE_OF_MY_CONSTANTS_ARRAY; i++) 
        NSLog(@"my constant string is: %@", MY_CONSTANT_STRING_ARRAY[i]);

Yes, this doesn't dynamically capture the size of the array, but if you're declaring an array in a constants file you already know the size of that array from the start, so even though it adds two more lines of code, it still accomplishes the task of having an array in a constants file.

If anyone has any more suggestions or may know some other C tricks please leave a comment below!

AngeloS
  • 5,536
  • 7
  • 40
  • 58
  • This is incorrect. sizeof() returns the memory size. This will likely go through 8 iterations, but fail on the third – Jesse Gumpo Jul 31 '12 at 09:36
  • @JesseGumpo, thanks for that comment. In my actual implementation I created an int constant for the array size instead of using `sizeof()`. Will edit my answer to reflect my final approach, which since it's using simple data types, still works and accomplishes the task of having an array in a constants file. – AngeloS Aug 06 '12 at 13:07
  • 8
    "sizeof(MY_CONSTANT_STRING_ARRAY) / sizeof(MY_CONSTANT_STRING_ARRAY[0])" would do it – boecko Aug 24 '12 at 12:23
9

Here's a macro to do it in one line for a static instance in a method scope.

#define STATIC_ARRAY(x, ...)   \
        static NSArray* x=nil; \
        static dispatch_once_t x##onceToken; \
        dispatch_once(&x##onceToken, ^{ x = @[ __VA_ARGS__ ]; });

Use example

    STATIC_ARRAY(foo, @"thing1", @"thing2", [NSObject new]);
JoeM
  • 134
  • 1
  • 3
7

It's pretty easy :

#define arrayTitle [NSArray arrayWithObjects: @"hi",@"foo",nil]

put before implementation and without semicolon.

hope it helps.

HelmiB
  • 12,303
  • 5
  • 41
  • 68
  • 59
    That doesn't really give you a constant array, it just generates a new one everytime you use `arrayTitle`, – Abizern Feb 11 '12 at 12:33
  • 8
    It does, however, help other people visiting the thread later who just want to declare in the header file, but don't care about making a constant. – James Billingham Sep 26 '13 at 00:31
  • It definitely doesn't create a constant, but it does make an array that always give the same array, which is pretty much what a constant array does, right? Awesome answer by the way! – Septronic Oct 19 '15 at 18:51
  • Not a constant array, buy anyway, the best solution to my problem – Alejandro Luengo Dec 13 '15 at 16:01
  • Preprocessor macros are evil too. – Iulian Onofrei Jan 20 '17 at 10:17
  • Just noticed this. This is a horrible solution. Every time you mention `arrayTitle` in code anywhere, it is going to **allocate a new instance of the array and fill it with objects**. That's a lot of allocation traffic that is completely unnecessary. – bbum Mar 23 '17 at 23:37
6

As for me, it is more convenient to use the following implementation for an array of constants

static NSString * kHeaderTitles [3] = {@ "ACCOUNT DETAILS", @ "SOCIAL NETWORK", @ "SETTINGS"};
static int kNumbers[3] = {1, 2, 3};
Vlad Smelov
  • 71
  • 1
  • 3
4

I have a header file called "Constants.h" and within the next constant arrays:

#define arrayOfStrings @[@"1", @"2", @"3", @"4"]
#define arraysOfIds @[@(1), @(2), @(3), @(4)]

Basically, when you call arrayOfStrings in your code, is replaced with @[@"1", @"2", @"3", @"4"] and the same thing with arraysOfIds.

Javier Flores Font
  • 2,075
  • 15
  • 13
  • 3
    This also will cause a *new instance of the array to be created* every time `arrayOfStrings` or `arraysOfIds` is mentioned. – bbum Mar 23 '17 at 23:38