How do I properly create static NSArrays full of NSNumbers in Objective-C (Cocoa)?
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];
}
In modern times, you would use dispatch_once()
to do the one time initialization. Xcode has a handy template built in for doing exactly that.
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:
anNSNumber
. 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.
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.
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.
This can be done in a thread safe manner with dispatch_once
. For example:
- (NSArray *)staticSpeeds {
static dispatch_once_t onceToken;
static NSArray *speeds;
dispatch_once(&onceToken, ^{
speeds = [NSArray
arrayWithObjects:[NSNumber numberWithFloat:1.0], nil];
});
return speeds;
}
精彩评论