为什么NSNumber字面量不能用于静态声明

7

我声明:

static NSString *a = @"a";

这是iOS6中正确的声明方式(使用编译器版本可能更加准确,但目前我不知道)。我认为对于数字文字也是这样:

static NSNumber *b=@1;

这可能是一个有效的声明。编译器告诉我初始化元素不是编译时常量。这让我有点惊讶。因为NSNumberNSString一样是不可变的,而且我使用了字面值,所以我认为它也可以是有效的。

有人能给出一个合理的解释吗?


我也曾经想过这个问题,除了历史原因外,我想不出为什么一个会工作而另一个不会。我的猜测是让它工作并不是一个高优先级的任务。 - Jesse Rusak
1
不可变和“编译时”是两个不同的概念。 - Hot Licks
2个回答

21
第一行是编译时常量,因为你赋值的是@"a"而不是类似于static NSString *a = [NSString stringWithFormat:@"a"];(这将会抛出相同的错误)
但是对于NSNumberstatic NSNumber *b = @1;实际上等同于static NSNumber *b = [NSNumber numberWithInt:1];。更多细节请查看Objective-C Literals。 请注意,在上述情况下,右侧不是编译时常量。它是必须在运行时计算的表达式。在C和Objective-C中,静态变量必须使用编译时常量进行初始化。
如果您想将NSNumber作为const使用,请查看此处提到的方法Objective C - How to use extern variables? 此外,请查看Mike Ash关于Objective-C字符字面量的这篇文章

需要注意的是,任何新的文字字面量都不符合编译时常量的要求。

以及,

NSString文字字面量也是编译时常量,因为编译器和库之间有着紧密的耦合。有一个特殊的NSString子类称为NSConstantString,其中包含一个固定的ivar布局:

这种紧密的耦合具有优点,例如产生合法的全局变量初始化程序,并且不需要运行额外的代码来构建对象。但是,也存在重大缺点。 NSConstantString布局永远设置。必须使用完全相同的数据布局维护该类,因为该数据布局已经嵌入了数千个第三方应用程序中。如果Apple更改了布局,则这些第三方应用程序将会中断,因为它们包含NSConstantString

如果NSArray文字常量是编译时常量,那么就需要一个类似的NSConstantArray类,具有固定的布局,可以由编译器生成,并且必须与其他NSArray实现分开维护。这样的代码无法在没有这个NSConstantArray类的旧操作系统上运行。新文字语法可以生成的其他类也存在相同的问题。

尤其是NSNumber文字的情况尤为有趣。Lion引入了标记指针(tagged pointers),允许将NSNumber的内容直接嵌入指针中,消除了单独动态分配对象的需要。如果编译器发出标记指针,则它们的格式永远不会更改,并且与旧操作系统版本的兼容性将丢失。如果编译器发出常量NSNumber对象,那么NSNumber文字将与其他NSNumber显著不同,并可能会导致重大性能损失。

相反,编译器只是调用框架,像手动构建对象一样构造对象。这会导致略微的运行时损失,但不会比没有新语法自己构建它们更糟糕,并且可以使设计更加清晰。


你正在把这件事变得个人化,@vikingosegundo。 - iDev
奇怪,我以为@"a"只是一种类似于[NSString stringWithUTF8String:"a"]的语法糖。 - Ky -

0
在macOS Ventura上,这不再是一个问题,以下代码可以正常编译。

NSArray *constant_nsarray_test=@[ @"string1", @(1) ];

它生成一个带有空文本部分的.o文件,并引用类NSConstantArrayNSConstantIntegerNumber,除了常量字符串之外。

链接该文件或动态加载它允许您检索数组并访问其内容。


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