如何在Objective-C中创建常量的最佳方法?

162

我正在创建一个Reddit客户端以用于学习目的。我需要有一个包含常量的文件。我想导入这个文件到Reddit-Prefix.pch文件中,以便让所有文件都可以使用这些常量。这样做好吗?此外,我已经做了调查并找到了几种创建常量的方法,但不知道该使用哪种:

  • #define
  • const
  • static const
  • extern const
  • enum

那么哪种方式是首选?有什么惯例吗?我知道“这取决于情况”,但我的问题更具体地是:每种解决方案的使用情况是什么?

另外,如果使用extern const,我需要导入文件吗,还是常量将在没有导入文件的情况下全局可用?

我能得出的一个逻辑推论是,enum是定义自定义错误域之类内容最好的选择(我实际上是对的吗?)。但其他方面呢?


请访问此链接:https://dev59.com/8Wgu5IYBdhLWcg3wrY0m,您的解决方案在此帖子中。 - BhavikKama
3
那是一个更具针对性的问题,对比两种具体的解决方案。 - Peter Hosey
对于 - 静态常量,#define,枚举,这个链接很有用https://dev59.com/onI-5IYBdhLWcg3wxruQ,提供了关于这三种const替代方案的良好解释。 - BhavikKama
enum 只适用于整数值。#define 和常量可以是任何数据类型。 - rmaddy
conststatic constextern const除了作用域之外都是相同的。因此实际上只有三种选择。 - rmaddy
2个回答

394

第一个问题是您希望常量具有什么范围,实际上有两个问题:

  • 这些常量是否特定于单个类,或者在整个应用程序中使用它们是否有意义?
  • 如果它们是特定于类的,它们是供类的客户端使用还是仅在类内部使用?

如果它们是特定且仅在单个类内部使用,请在.m文件顶部声明它们为static const,如下所示:

static NSString *const MyThingNotificationKey = @"MyThingNotificationKey";

如果它们与单个类有关,但应该是公共的/被其他类使用,请在头文件中声明它们为extern,并在.m文件中定义它们:
//.h
extern NSString *const MyThingNotificationKey;

//.m
NSString *const MyThingNotificationKey = @"MyThingNotificationKey";

如果它们应该是全局的,请在头文件中声明它们并在相应模块中定义它们,专门用于这些常量。
您可以将它们混合使用,以获得不同级别的全局性,对于简单不属于一起的全局常量 - 如果需要,可以将它们放在单独的模块中,每个模块都有自己的头文件。
为什么不使用#define?
旧答案是“宏没有类型信息”,但是现代编译器非常聪明,可以对文本替换(宏扩展)和变量进行所有类型检查。
现代答案是因为调试器不知道您的宏。如果MyThingNotificationKey是一个宏,则无法在调试器命令中说[myThing addObserver:self forKey:MyThingNotificationKey];如果它是一个变量,则调试器只能知道它是一个变量。
为什么不使用枚举?
好吧,在评论中rmaddy比我先回答了:枚举只能定义整数常量。像序列标识符号码、位掩码、四字节代码等内容。

对于这些目的,enum非常好,您绝对应该使用它。(更好的是,使用NS_ENUMNS_OPTIONS。) 对于其他事情,您必须使用其他东西;enum除了整数之外什么也做不了。

还有其他问题

我在考虑将文件导入Reddit-Prefix.pch文件中,以使常量对所有文件都可用。 这样做好吗?

可能是无害的,但可能过度了。 在需要它们的地方导入您的常量头文件。

每种解决方案的用例是什么?

  • #define:功能有限。老实说,我不确定现在还有什么好的理由用它来定义常量。
  • const:最适合用于本地常量。此外,如果您在头文件中声明了一个常量并且现在正在定义它,则必须使用此选项。
  • static const:最适合用于特定于文件(或类)的常量。
  • extern const:在头文件中导出常量时必须使用此选项。

另外,如果使用 extern const,我需要导入文件吗?还是常量可以全局使用而无需导入文件?

您需要导入文件,可以在每个使用它的文件中导入,也可以在前缀头文件中导入。


4
为什么不完全在.h文件中使用static NSString *const - Iulian Onofrei
4
如果在应用程序中而非框架中,你可以这样做:使用static NSString *const foo = @"foo";。在这种情况下,你的头文件决定了字符串的内容,而且它必须在任何地方都是相同的。如果你更改了字符串并且不同的参与方在不同版本的头文件中使用不同的字符串,那么这些字符串在运行时将不匹配。但是,在框架中,你只想提供对符号的访问,并让框架成为该符号真实值的唯一来源,以便每个人都从同一个地方获取相同的字符串。这就是使用extern的原因。 - Peter Hosey
关于 #define 的附加说明:它们不能保证在内存中具有相同的地址(取决于它们如何声明,它们可能会在每次使用时分配一个新实例),因此使用 myObject == MyDefine 不总是按预期工作,但使用 myObject == MyStaticConst 将会。 - Ky -
在拼写上像 static NSString *const 而不是 static NSString const* 有意义吗?有什么区别吗?! - kokos8998
@kokos8998 这有区别吗?是的,有区别。static NSString const *static const NSString * 是一样的,意思是“指向不可变NSString的(可变)指针” - 在这里有点无用,因为NSString已经是不可变的。你想要的是 static NSString * const - 这是一个“指向NSString的常量指针”。 - David

10

FOUNDATION_EXPORT

建议使用 FOUNDATION_EXPORT 来提高兼容性,因为它在 Foundation 中定义,并且编译成 C、C++ 和 Win32 的兼容格式。

如 NSObjCRuntime.h 中所定义。

#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif

#if TARGET_OS_WIN32

    #if defined(NSBUILDINGFOUNDATION)
        #define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllexport)
    #else
        #define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllimport)
    #endif

    #define FOUNDATION_IMPORT FOUNDATION_EXTERN __declspec(dllimport)

#else
    #define FOUNDATION_EXPORT  FOUNDATION_EXTERN
    #define FOUNDATION_IMPORT FOUNDATION_EXTERN
#endif

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