84

When compiling this code, I get the error "initializer element is not a compile-time constant". Can anyone explain why?

#import "PreferencesController.h"

@implementation PreferencesController

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}


NSImage* imageSegment = [[NSImage alloc] initWithContentsOfFile:@"/User/asd.jpg"];//error here
jscs
  • 63,694
  • 13
  • 151
  • 195
Nick
  • 841
  • 1
  • 6
  • 3

7 Answers7

115

When you define a variable outside the scope of a function, that variable's value is actually written into your executable file. This means you can only use a constant value. Since you don't know everything about the runtime environment at compile time (which classes are available, what is their structure, etc.), you cannot create objective c objects until runtime, with the exception of constant strings, which are given a specific structure and guaranteed to stay that way. What you should do is initialize the variable to nil and use +initialize to create your image. initialize is a class method which will be called before any other method is called on your class.

Example:

NSImage *imageSegment = nil;
+ (void)initialize {
    if(!imageSegment)
        imageSegment = [[NSImage alloc] initWithContentsOfFile:@"/User/asd.jpg"];
}
- (id)init {
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}
ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
  • 5
    Another option is to use a function with `__attribute__ ((constructor))`. –  May 26 '11 at 23:32
  • 13
    Yet another option is to switch the type of your source file from Objective-C to Objective-C++ (or rename it from .m to .mm, which has the same effect). In C++, such initializers don't need to be compile-time constant values, and the original code would work just fine. – imre Feb 08 '14 at 15:10
  • 3
    Is there any way to declare a `const` in such a fashion? I.e. a variable that can only be set once and never again? – devios1 Feb 06 '15 at 18:18
  • What if I want to include a constant from several files. Is it possible to write initialize function multiple times, ie. can it be comprised of several parts? – Vladimir Despotovic Sep 12 '17 at 21:16
  • @VladimirDespotovic No it can't be split. You can have an `+initialize` method for different classes, or it could call functions from other files, but you have to be really careful with things like this. It's really best to avoid it if possible. – ughoavgfhw Sep 13 '17 at 23:30
  • How to properly define a dictionary then, as a constant? – Vladimir Despotovic Sep 13 '17 at 23:31
26

A global variable has to be initialized to a constant value, like 4 or 0.0 or @"constant string" or nil. A object constructor, such as init, does not return a constant value.

If you want to have a global variable, you should initialize it to nil and then return it using a class method:

NSImage *segment = nil;

+ (NSImage *)imageSegment
{
    if (segment == nil) segment = [[NSImage alloc] initWithContentsOfFile:@"/user/asd.jpg"];
    return segment;
}
mipadi
  • 398,885
  • 90
  • 523
  • 479
11

Because you are asking the compiler to initialize a static variable with code that is inherently dynamic.

bbum
  • 162,346
  • 23
  • 271
  • 359
6

The reason is that your are defining your imageSegment outside of a function in your source code (static variable).

In such cases, the initialization cannot include execution of code, like calling a function or allocation a class. Initializer must be a constant whose value is known at compile time.

You can then initialize your static variable inside of your init method (if you postpone its declaration to init).

sergio
  • 68,819
  • 11
  • 102
  • 123
  • 1
    Static storage class is the problem. The issue would still occur even if it were inside a function (and declared static). – jww Sep 20 '13 at 09:44
  • @noloader: I have never stated the contrary... :-) only, the specific case of static storage in the OP's case was a global variable... (in parenthesis, the concept - it all depends on who the reader is whether it is better starting with the concept or the concrete case). – sergio Sep 20 '13 at 11:09
4

You can certainly #define a macro as shown below. The compiler will replace "IMAGE_SEGMENT" with its value before compilation. While you will achieve defining a global lookup for your array, it is not the same as a global variable. When the macro is expanded, it works just like inline code and so a new image is created each time. So if you are careful in where you use the macro, then you would have effectively achieved creating a global variable.

#define IMAGE_SEGMENT [[NSImage alloc] initWithContentsOfFile:@"/User/asd.jpg"];

Then use it where you need it as shown below. Each time the below code is executed, a new object is created with a new memory pointer.

imageSegment = IMAGE_SEGMENT
Kris Subramanian
  • 1,850
  • 18
  • 15
0

You can use the static singleton approach with dispatch_once:

#define STATIC_VAR(type, code) ^type() { \
    static type object; \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        object = code; \
    }); \
    return object; \
};

#define let __auto_type const

let imageSegment = STATIC_VAR(UIImage*, [[UIImage alloc] initWithContentsOfFile:@"image.jpg"]);
let imageRect = STATIC_VAR(CGRect, CGRectMake(0, 0, 100, 100));

// How to use:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.imageView.image = imageSegment();
    self.imageView.frame = imageRect();
}

It is thread safe, works lazily with any type and makes only a single instance.

iUrii
  • 11,742
  • 1
  • 33
  • 48
0

I got this error while practicing C language, my code that I was trying to run was this

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    char *name;
    int age;
} person;

person *p = (person *)malloc(sizeof(person));

and I realized while reading answers, that in C, I should have main function, which I forgot to use, so put the person code in main function, thus removing the error as follows

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    char *name;
    int age;
} person;

int main()
{

    person *p = (person *)malloc(sizeof(person));
    return 0;
}
Akshay Vijay Jain
  • 13,461
  • 8
  • 60
  • 73