Как правилно да създам статични NSArrays, пълни с NSNumbers в Objective-C (Cocoa)?

Защо в следния код не мога просто да направя статичен масив от NSNumbers? Просто бих използвал C масиви и int, но те не могат да бъдат копирани и както можете да видите в init(), трябва да копирам масива в друг. Грешката, която получавам, е „Елементът на инициализатора не е постоянен“. Много е объркващо; Дори не съм сигурен какво означава това, като се има предвид, че нямам ключовата дума const никъде там.

Освен това, като странична бележка, методът getNextIngredient ми дава грешката „не може да използва обект като параметър към метод“ и „несъвместими типове в замяна“, но не съм сигурен защо.

Ето кода:

// 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];
}

person Vexir    schedule 21.10.2010    source източник


Отговори (4)


Класическият начин да направите това е с метод +initialize:

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]];
    }
}

Алтернатива, която работи, ако имате нужда от това в C вместо прикрепено към Obj-C клас, е нещо като следното:

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]];
}

Въпреки това бих препоръчал да отидете с отговора на bbum, тъй като това е много по-идиоматично.

person Lily Ballard    schedule 21.10.2010

ето един по-задълбочен пример (който също използва очертания идиом какао bbum). той посочва няколко други грешки и адресира вашата странична бележка:

/* 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

вместо низови литерали, може да е най-добре да създадете низови ключове, както и да използвате удобни конструктори, като - (id)initWithBasicHam (както е показано).

освен това обикновено извиквате [[Recipe basicHam] copy] вместо Recipe basicHam] retain] -- но това не е необходимо в този конкретен пример.

person justin    schedule 21.10.2010

Това може да се направи по безопасен начин с dispatch_once. Например:

- (NSArray *)staticSpeeds {
    static dispatch_once_t onceToken;
    static NSArray *speeds;
    dispatch_once(&onceToken, ^{
        speeds = [NSArray
                  arrayWithObjects:[NSNumber numberWithFloat:1.0], nil];
    });
    return speeds;
}
person Tad    schedule 22.05.2018

person    schedule
comment
Въпрос относно това - няма ли да има условие за състезание при инициализацията на радиолюбители? Няма ли моделът dispatch_once да е по-добър? - person Evan Grim; 18.06.2012
comment
Мда; dispatch_once() е много по-добро решение. Мисля, че това е написано преди това да е публичен API. :) - person bbum; 19.06.2012