【问题标题】:When to use static string vs. #define何时使用静态字符串与 #define
【发布时间】:2026-02-01 07:00:01
【问题描述】:

我有点困惑什么时候最好使用:

static NSString *AppQuitGracefullyKey = @"AppQuitGracefully";

而不是

#define AppQuitGracefullyKey    @"AppQuitGracefully"

我看到过类似 C 或 C++ 的问题,我认为这里的不同之处在于,这是专门针对 Objective C 的,利用对象,在 iPhone 等设备上,可能存在堆栈、代码空间或我还没有掌握的记忆问题。

一种用法是:

appQuitGracefully =  [[NSUserDefaults standardUserDefaults] integerForKey: AppQuitGracefullyKey];

还是只是风格问题?

谢谢。

【问题讨论】:

    标签: objective-c cocoa cocoa-touch xcode


    【解决方案1】:

    如果你使用静态,编译器会在你的二进制文件中嵌入字符串的一个副本,并且只是传递指向该字符串的指针,从而生成更紧凑的二进制文件。如果您使用#define,则每次使用时都会在源中存储一个单独的字符串副本。常量字符串合并将处理许多重复,但你无缘无故地让链接器工作得更加努力。

    【讨论】:

    • 常量字符串合并是编译器的工作,而不是链接器的工作。
    • 这是不对的。 Objective-c 使用String Interning,如here 所述。 #define 为每次使用存储字符串的副本。 @kennytm 是对的,编译器会这样做,而不是链接器
    • @AndréFratelli 事实上,常量字符串合并是一种链接器优化,它由 OS X/macOS 中的 C 和 CF/NSStrings 的静态链接器实现。它不是使用字符串实习来完成的,也不是由编译器完成的(尽管编译器可能会在写出它生成的目标文件之前合并字符串本身)。您可以在 the linker sources 中看到执行此操作的代码。所以不,kennytm 是不对的;它确实是链接器。
    • @alastair 我想这是有道理的,因为链接器需要解析所有目标文件中的符号。那时我找到了一些来源(不记得是哪个)解释编译器确实实现了实习,所以也许两者都在这样做是有道理的。无论如何,重点是 #define 不会创建副本,无论如何仍然有效。
    【解决方案2】:

    "static const" vs "#define" vs "enum"static 的主要优点是类型安全。

    除此之外,#define 方法引入了内联字符串连接的灵活性,这是静态变量无法做到的,例如

    #define ROOT_PATH @"/System/Library/Frameworks"
    [[NSBundle bundleWithPath:ROOT_PATH@"/UIKit.framework"] load];
    

    但这可能不是一个好的风格:)。

    【讨论】:

    • 我很惊讶。我不知道@"one string"@" another string" 是有效的。
    • 从您的评论到 cdespinosa 的回答,这是否意味着使用 #define 不会产生重复?
    • @user523234 确实如此。编译器使用String Interning
    【解决方案3】:

    其实我都不推荐,你应该改用extern。 Objective-c 已经定义了FOUNDATION_EXPORT,它是more portable 而不是extern,所以一个全局的NSString 实例看起来像这样:

    .h

    FOUNDATION_EXPORT NSString * const AppQuitGracefullyKey;
    

    .m

    NSString * const AppQuitGracefullyKey = @"AppQuitGracefully";
    

    我通常将这些放在声明文件中(例如MyProjectDecl.h)并在需要时导入。

    这些方法有一些不同之处:

    • #define 有几个缺点,例如类型不安全。确实有解决方法(例如#define ((int)1)),但有什么意义呢?此外,这种方法也存在调试缺点。编译器更喜欢常量。请参阅this 讨论。
    • 静态全局变量是visible in the file they are declared.
    • extern 使变量对所有文件可见。这与静态形成鲜明对比。

    静态和外部的可见性不同。还值得注意的是,这些方法都不会复制字符串(甚至#define),因为编译器使用String Interning 来防止这种情况。在this NSHipster post 他们展示了证据:

    NSString *a = @"Hello";
    NSString *b = @"Hello";
    BOOL wtf = (a == b); // YES
    

    只有当两个变量指向同一个实例时,运算符==才会返回YES。如您所见,确实如此。

    结论是:对全局常量使用FOUNDATION_EXPORT。它对调试很友好,并且会在您的整个项目中可见。

    【讨论】:

      【解决方案4】:

      在进行了一些搜索之后(this 问题/答案等),我认为重要的是,无论何时使用字符串文字 @"AppQuitGracefully" 都会创建常量字符串,无论您使用多少次它将指向相同的对象。

      所以我认为(如果我错了,我很抱歉)上面答案中的这句话是错误的:If you use a #define, there will be a separate copy of the string stored in the source on each use.

      【讨论】:

        【解决方案5】:

        当我需要从库或框架中导出 NSString 符号时,我使用static。当我在很多地方需要一个可以轻松更改的字符串时,我会使用#define。无论如何,编译器和链接器会负责优化。

        【讨论】:

          【解决方案6】:

          使用#define:

          你不能调试标识符的值

          使用#define 和其他宏是预处理器的工作, 当您首先点击构建/运行时,它将预处理源代码,它将与所有宏一起工作(以符号 # 开头),

          假设,你已经创建了,

          #define LanguageTypeEnglish @"en"
          

          并在代码中的 2 个地方使用了它。

          NSString *language = LanguageTypeEnglish;
          NSString *languageCode = LanguageTypeEnglish;
          

          它将在所有地方将 "LanguageTypeEnglish" 替换为 @"en"。 因此将生成 2 个 @"en" 副本。 即

          NSString *language = @"en";
          NSString *languageCode = @"en";
          

          记住,在这个过程之前,编译器不在图片中。

          预处理完所有的宏后,编译器进来,它会得到这样的输入代码,

          NSString *language = @"en";
          NSString *languageCode = @"en";
          

          并编译它。

          使用静态:

          它尊重范围并且是类型安全的。 你可以调试标识符的值

          编译过程中如果编译器发现,

          static NSString *LanguageTypeRussian = @"ru";
          

          然后它会检查之前是否存储了同名的变量, 如果是,它只会传递该变量的指针, 如果没有,它将创建该变量并传递它的指针,下一次它只会传递相同的指针。

          所以使用静态,在作用域内只生成一个变量的副本。

          【讨论】: