views:

43

answers:

4

Why is it that in the following code, I can not just simply make a static array of NSNumbers? I would just use C arrays and ints, but those cannot be copied and as you can see in init(), I have to copy the array to another one. The error I recieve is "Initializer element is not constant." It's very confusing; I'm not even sure what that means considering I don't have the const keyword anywhere in there.

Also, as a sidenote, the getNextIngredient method gives me the error "cannot use object as a parameter to a method" and "incompatible types in return", but I'm not sure why.

Here is the code:

// 1 = TOMATO
// 2 = LETTUCE
// 3 = CHEESE
// 4 = HAM

#import "Recipe.h"




@implementation Recipe

// List of hardcoded recipes
static NSArray *basicHam = [[NSArray alloc] initWithObjects:[[NSNumber alloc] numberwithInt:1], [[NSNumber alloc] numberwithInt:2], [[NSNumber alloc] numberWithInt:3], [[NSNumber alloc] numberwithInt:4]];

// Upon creation, check the name parameter that was passed in and set the current recipe to that particular array.
// Then, set nextIngredient to be the first ingredient of that recipe, so that Game can check it.
-(id) initWithName: (NSString*)name {
    self = [super init];

    indexOfNext = 0;

    if (self) {
        if ([name isEqualToString: @"Basic Ham"]) {
            currRecipe = [NSArray arrayWithArray: basicHam]; 
        }                                
    }
}

-(NSNumber) getNextIngredient {
    return [currRecipe  objectAtIndex:indexOfNext];
}
+7  A: 

An NSArray is never a statically allocated object and, thus, cannot be the initializer for a static variable.

Do something like:

@implementation Recipe

+ (NSArray *) basicHam {
    static NSArray *hams;
    if (!hams) 
        hams = [[NSArray alloc] initWithObjects:[NSNumber numberwithInt:1], [NSNumber numberwithInt:2], [NSNumber numberWithInt:3], [NSNumber numberwithInt:4], nil];
    return hams;
}

However, note a couple of things:

  • I changed your code slightly. You don't alloc, then numberWithInt: an NSNumber. That won't work.

  • I added a nil at the end of the argument list. That is necessary.

And, still, it must be observed that an array that effectively contains a small set of natural counting numbers in order with no gaps is quite distinctly odd. Anytime that x = foo[x] is an identity expression, it typically indicates there is something decidedly odd about the patterns in use.

bbum
A: 

Thanks. From what little I know, that looks like a class function that returns the NSArray. Does that mean I'm going to have to call [Recipe basicHam] everytime I want to use it?

You also say you don't alloc and when numberWithInt, but that's what you're doing inside the if statement, is it not?

Also, the array will contain more than just 1,2,3,4 in the future. This is just a simple case.

Vexir
This would have been better as a comment on bbum's answer. In any case, yes, you have to call [Recipe basicHam] whenever you want to access the array. As for alloc/numberWithInt, your code is calling `[[NSNumber alloc] numberWithInt:1]`, which a) leaks the number, and b) won't work anyway because numberWithInt: is a class method. bbum's code calls alloc on the NSArray, but subsequently only calls `[NSNumber numberWithInt:1]` to generate the NSNumber instances.
Kevin Ballard
I didn't see any comment button (still don't) on answers without a comment already on them :(
Vexir
A: 

The classic way of doing this is with an +initialize method:

static NSArray *basicHam;

@implementation Recipe

+ (void)initialize {
    if (self == [Recipe class]) {
        basicHam = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2],
                                                    [NSNumber numberWithInt:3], [NSNumber numberWithInt:4, nil]];
    }
}

An alternative which works if you need this in C instead of attached to an Obj-C class is something like the following:

static NSArray *basicHam;

static void initBasicHam() __attribute__((constructor)) {
    basicHam = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2],
                                                [NSNumber numberWithInt:3], [NSNumber numberWithInt:4, nil]];
}

That said, I would still recommend going with bbum's answer, as that's far more idiomatic.

Kevin Ballard
A: 

here's a more thorough example (which also uses the cocoa idiom bbum outlined). it points out a few other errors, and addresses your sidenote:

/* Recipe.h */

@interface Recipe : NSObject
{
    NSUInteger indexOfNext;
    NSArray * currentRecipe;
}

- (id)initWithName:(NSString *)name;
- (id)initWithBasicHam;

- (NSNumber *)getNextIngredient;

@end

extern NSString * const Recipe_DefaultRecipeName_BasicHam;

/* Recipe.m */

NSString * const Recipe_DefaultRecipeName_BasicHam = @"Basic Ham";

@implementation Recipe

/* @return list of hardcoded recipes */
+ (NSArray *)basicHam
{
    // there may be a better place to declare these
    enum { TOMATO = 1, LETTUCE = 2, CHEESE = 3, HAM = 4 };
    static NSArray * result = 0;
    if (0 == result) {
        result = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:TOMATO], [NSNumber numberWithInt:LETTUCE], [NSNumber numberWithInt:CHEESE], [NSNumber numberWithInt:HAM], nil];
    }
    return result;
}

/* Upon creation, check the name parameter that was passed in and set the current recipe to that particular array. */
/* Then, set nextIngredient to be the first ingredient of that recipe, so that Game can check it. */
- (id)initWithName:(NSString *)name
{
    self = [super init];
    if (0 != self) {
    /* note: set your ivar here (after checking 0 != self) */
        indexOfNext = 0;

        if ([name isEqualToString:Recipe_DefaultRecipeName_BasicHam]) {
            currentRecipe = [[Recipe basicHam] retain];
        }
    }
    return self;
}

- (id)initWithBasicHam
{
    self = [super init];
    if (0 != self) {
        indexOfNext = 0;
        currentRecipe = [[Recipe basicHam] retain];
    }
    return self;
}

- (NSNumber *)getNextIngredient
{
    assert(currentRecipe);
    return [currentRecipe objectAtIndex:indexOfNext];
}

@end

instead of string literals, it may be best to create string keys, as well as use convenience constructors, such as - (id)initWithBasicHam (as demonstrated).

also, you'd typically call [[Recipe basicHam] copy] rather than Recipe basicHam] retain] -- but that is not necessary in this particular example.

Justin