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

Почему в следующем коде я не могу просто создать статический массив NSNumbers? Я бы просто использовал массивы C и целые числа, но их нельзя скопировать, и, как вы можете видеть в 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