如何在Objective-c中声明一个常量数组?

65
以下代码给我报错:
//  constants.h
extern NSArray const *testArray;
//  constants.m
NSArray const *testArray = [NSArray arrayWithObjects:  @"foo", @"bar", nil];

我遇到的错误是
初始化元素不是常数

或者如果我去掉指针标识符(*),我会得到:
'NSArray' 的静态分配实例


1
请查看这个 Stack Overflow 的问题:https://dev59.com/j3NA5IYBdhLWcg3wL6sc - conorgriffin
1
“常量”是什么意思?对象的不可变内容,还是指针的不可变性? - zneak
我的标准是:1)值不能被意外更改(所以我猜NSArray很好),2)在程序的任何地方都可以使用(我在我的.pch文件中包含了constants.h),3)只声明一次。 - Andrew
6个回答

71

简而言之,你不能。除了NSString以外,Objective-C对象只能在运行时创建。因此,你不能使用表达式来初始化它们。

有几种方法可以解决这个问题。

(1) 不带const关键字声明NSArray *testArray,然后编写一小段代码,在应用程序生命周期的早期阶段设置该值。

(2) 声明一个方便的类方法来返回数组,然后在该方法内部使用static NSArray *myArray并将其视为单例(在SO上搜索"objective-c singleton",大约会得到无数个如何实例化的答案)。


5
如果你只需要处理类似于问题中的 NSStrings 数组,可以在常量文件中创建一个 C 风格的数组,然后从那里使用一些 C 方法来访问这些值。请参阅我下面的答案 - AngeloS
2
@bbum -- 即使使用 @[...] 语法糖,这仍然是正确的,对吗? - Ben Mosher
2
有人可能会认为,由于块现在是完整的可保留对象类型,它们是 Objective-C 对象的第二种类型,编译器知道布局并可以创建常量实例。可以从块字面量初始化静态存储期变量。 - Nikolai Ruhe
@NikolaiRuhe 是的,但是允许静态块初始化只会让编译器在看似无害的更改后给出“非静态初始化程序”错误,这将是一种完全的痛苦。 - bbum

34

可能有些晚了,但是如果您在程序运行过程中不修改值,并且只涉及字符串,那么您可以通过使用 C 数组来声明您的数组,方法如下:

extern NSString * const MY_CONSTANT_STRING_ARRAY[];

在你的constants.h文件中,然后在你的constants.m文件中,你可以像这样添加对象:

NSString * const MY_CONSTANT_STRING_ARRAY[] = { @"foo", @"bar" };

然后要访问成员,你可以使用类似下面这样的for循环,带有C sizeof()运算符:

显然,这是一个C数组,而不是NSArray,所以您没有所有附加的有趣方法,例如objectAtIndex:,因此您可以在程序中的某个位置创建一个辅助函数,使用我上面概述的方法循环遍历所有字符串,并返回一个NSArray(甚至是NSMutableArray)。但是,如果您像我一样只需要在整个程序中使用NSString值的常量数组,则此方法效果最佳。

这样做将所有字符串数组常量封装在constants.h中,并且通过在.pch文件中添加constants.h而在整个程序中仍然可用,而不是为此数组创建单例或使用一些代码设置数组,这有点违反了constants文件的目的,因为它将实际的常量从constants文件中移除了。

根据@JesseGumpo的评论进行编辑:

由于使用sizeof()来确定数组的大小可能会出现问题,一个简单的解决方法是在您的常量文件中声明数组的大小,如下所示:

//.h
extern int SIZE_OF_MY_CONSTANTS_ARRAY;  

///.m
int SIZE_OF_MY_CONSTANTS_ARRAY = 2;

然后在for循环中访问成员可以像这样:

for (int i=0; i < SIZE_OF_MY_CONSTANTS_ARRAY; i++) 
        NSLog(@"my constant string is: %@", MY_CONSTANT_STRING_ARRAY[i]);

是的,这种方法不能动态捕获数组的大小,但如果您在常量文件中声明一个数组,那么您已经从一开始就知道该数组的大小,因此即使它增加了两行代码,它仍然可以实现将数组放在常量文件中。

如果有人有更多建议或熟悉其他 C 技巧,请在下面留言!


这是不正确的。sizeof()返回内存大小。这可能会经过8次迭代,但在第三次失败。 - Jesse Gumpo
@JesseGumpo,感谢您的评论。在我的实际实现中,我创建了一个 int 常量来表示数组大小,而不是使用 sizeof()。我将编辑我的答案以反映我的最终方法,由于它使用简单数据类型,仍然可以工作并完成在常量文件中拥有一个数组的任务。 - AngeloS
8
sizeof(MY_CONSTANT_STRING_ARRAY) / sizeof(MY_CONSTANT_STRING_ARRAY[0]) 可以完成此操作。 - boecko

9
这里有一个宏,用于在方法作用域中创建静态实例,只需一行代码即可完成。
#define STATIC_ARRAY(x, ...)   \
        static NSArray* x=nil; \
        static dispatch_once_t x##onceToken; \
        dispatch_once(&x##onceToken, ^{ x = @[ __VA_ARGS__ ]; });

使用示例

    STATIC_ARRAY(foo, @"thing1", @"thing2", [NSObject new]);

这是一个很棒的代码片段!我非常喜欢它! - Septronic

7
很简单:
在实现之前加上以下代码,不需要分号。 #define arrayTitle [NSArray arrayWithObjects: @"hi",@"foo",nil] 希望这可以帮到你。

59
每次使用arrayTitle时,它并没有提供一个恒定的数组,而是生成一个新数组。请注意,翻译尽可能保留原意,以通俗易懂为目标。 - Abizern
8
然而,它确实有助于后来访问该线程的其他人,他们只想在头文件中声明,但不关心是否创建一个常量。 - James Billingham
它绝对不会创建一个常量,但它确实创建了一个总是给出相同数组的数组,这基本上就是常量数组所做的事情,对吧?顺便说一句,回答太棒了! - Septronic
不是一个常量数组,但无论如何,这是解决我的问题的最佳方案。 - Alejandro Luengo
预处理器宏也是有害的。 - Iulian Onofrei
刚刚注意到这个问题。这是一个可怕的解决方案。每次在代码中提到arrayTitle,它都会分配一个新的数组实例并填充对象。这是完全不必要的大量分配流量。 - bbum

6

对我来说,以下常量数组的实现方式更方便

static NSString * kHeaderTitles [3] = {@ "ACCOUNT DETAILS", @ "SOCIAL NETWORK", @ "SETTINGS"};
static int kNumbers[3] = {1, 2, 3};

4
我有一个名为“Constants.h”的头文件,其中包含以下常量数组:
#define arrayOfStrings @[@"1", @"2", @"3", @"4"]
#define arraysOfIds @[@(1), @(2), @(3), @(4)]

基本上,当你在代码中调用arrayOfStrings时,它被替换为@[@"1", @"2", @"3", @"4"],并且同样的情况也适用于arraysOfIds。


3
每次提到arrayOfStringsarraysOfIds时,这也会导致一个新的数组实例被创建。 - bbum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接