静态的NSString使用与内联NSString常量

45
在Objective-C中,我的理解是指令@"foo"定义了一个常量NSString。如果我在多个地方使用@"foo",那么同一个不可变的NSString对象会被引用。
为什么我经常看到以下代码片段(例如在UITableViewCell重用中)?
static NSString *CellId = @"CellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellId];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:CellId];

只是:

而不仅仅是:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellId"];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:@"CellId"];

我猜这是为了防止我在标识符名称上出现拼写错误,而编译器无法捕捉到。但是,如果是这样的话,我不是可以直接使用吗?
#define kCellId @"CellId"

避免使用静态NSString *位?还是我漏掉了什么?

这也保证了指针相等性,这可以为您带来一些性能优势。据我所知,宏符号没有这个保证。 - eonil
6个回答

61
将文字转换为常量是一个好的实践,因为:
1. 它有助于避免拼写错误,就像你所说的那样 2. 如果你想要改变常量,你只需要在一个地方进行修改
我更喜欢使用"static NSString* const",因为它比"#define"稍微安全一些。除非我真的需要,否则我倾向于避免使用预处理器。

18
字符串常量可以在gdb中轻松检查。#define很麻烦。但是你在这里使用的const是错误的。它需要是"static NSString* const"。在Objective-C中,你的const顺序实际上没有任何有用的作用。尝试以我的方式重新分配字符串变量和你的方式,你会发现在一个情况下你会得到编译器错误,而在另一个情况下则不会。 - Rob Napier
1
你实际上并没有避免预处理器。所有的@符号都是预处理指令。 - uchuugaka
3
也许 @uchuugaka 的意思是要"避免使用预处理器",即避免依赖预处理宏替换(即 #define)和可能带来的所有潜在问题。 - Nicolas Miari

35
我喜欢这里所有的回答,但没有一个简单的例子来正确声明一个...所以...
如果你想让常量在外部可见(即“全局”)...在头文件中声明它...
extern NSString *const MyTypoProneString;
然后在.m文件中定义它,在任何@implementation之外,像这样...
NSString * const MyTypoProneString = @"iDoNtKnOwHoW2tYpE";
话虽如此...如果你只是想要一个static const,它是局部的,只在类的实现(甚至某个方法!)中可见...只需在实现(或方法)内部声明字符串为...
static NSString *MavisBeacon = @"She's a freakin' idiot";
虽然我确实展示了如何做到这一点...但我还没有被说服,认为这种风格在任何方面都比荒谬地更短、更简单、更少重复的“单一”声明更好。
#define SomeStupidString @"DefiningConstantsTwiceIsForIdiots"

使用#define...它们要少得多烦人。只是不要让预处理器的反对者打击你。

7
稍微有些老旧,但实际上不使用 #define 有一个非常好的理由 - #define 宏基本上是替换 - 因此,每次在代码中调用 SomeStupidString 时,预处理器都会将其替换为一个 新的 字符串字面量。相反,使用 extern / static const 可以将所有引用指向内存中的单个位置。这样做会更加节省内存并提高性能。 - Matt S.
好知道...我希望我能找出如何用可变参数宏或其他方式批量创建它们,尽管到目前为止我还没有找到一个好的方法来生产它们,更不用说不必将声明和实现分成两个文件了(哭泣)。 - Alex Gray
3
Matt S.的评论实际上是不正确的。在应用程序中,任何地方相同的字符串字面量都指向同一个对象。创建两个NSString字面量并检查它们的指针。即使在发布模式下,它们也是相同的对象。 - Logan Moseley
Logan - 那一定是最近的编译器优化。如果他们真的这样做了,那就太好了 :) - Matt S.
鉴于单元格重用标识符几乎总是只使用和输入两次 - 一次在故事板或nib中,一次在方法中 - 并且通常都是从类名复制粘贴 - 直接将字符串放入dequeue调用中是我认为最简单的方法。至于拼写错误,无论如何,您都会在第一次运行时立即发现,尽管个人而言,我从未成功地制造过那种拼写错误。 - theLastNightTrain

9
你应该将静态变量设为常量。
静态变量和宏之间的一个区别是宏在调试器中不太好用。宏也不具备类型安全性。
关于静态变量与宏的许多建议同样适用于Objective-C,就像CC++一样。

谢谢提供这些参考资料,我之前没有看到过。 - Trashpanda
通过Google在SO上搜索“static const宏”以获取更多信息。 - outis

3

在多个地方使用@"foo"时,不能保证运行时会为它们使用相同的存储空间,特别是跨编译单元或库边界的情况可能更不会如此。
我更愿意使用static NSString *string = @"foo",尤其是在有大量字面字符串的情况下。


2
我认为这是为了防止我在标识符名称中犯错而编译器无法捕获。
正确。这只是基本的防御性编程实践。编译结果(希望如此)两种方式应该是相同的。

But If so, couldn't I just:

#define kCellId @"CellId"

and avoid the static NSString * bit? Or am I missing something?

是的,但是kCellId符号至少在您的编译单元中将被全局定义。声明静态变量使该符号局部于该块。

通常会将字符串常量定义为全局变量或静态变量,而不是预处理器定义。这有助于确保在不同的编译单元之间只处理单个字符串实例。


1

这是我对Alex Gray的评论的扩展版本:

当你认为应该使用#define来定义字符串宏时,大多数情况下你都不应该这样做。原因是#define宏基本上是预处理器中的正则表达式替换。每当预处理器看到一个被调用的宏,它就会用你定义的内容替换它。这意味着每次都会在内存中分配一个新的字符串字面量,这在像单元重用标识符这样的地方非常糟糕(这也是为什么苹果的UITableViewController默认代码使用静态变量)。

相反,使用extern/static const会将所有引用指向内存中的同一个位置,就像eonil提到的那样。这样更节省内存,并且在移动设备上具有更高的性能,这非常重要。


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