题 Objective-C中的常量


我正在开发一个 可可 应用程序,我正在使用常量 NSStrings作为存储我的首选项的键名的方法。

我知道这是一个好主意,因为它可以在必要时轻松更改密钥。此外,它是整个'将您的数据与逻辑分离'的概念。

无论如何,是否有一种很好的方法可以为整个应用程序定义一次这些常量?我确信这是一种简单而聪明的方式,但是现在我的课程只是重新定义了他们使用的课程。


969
2018-02-11 21:52


起源


OOP是关于 分组 你的数据 同 你的逻辑。你提出的建议只是一个很好的编程实践,即使你的程序易于改变。 - Raffi Khatchadourian


答案:


您应该创建一个头文件,如

// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc.

(您可以使用 extern 代替 FOUNDATION_EXPORT 如果您的代码不会在混合C / C ++环境或其他平台上使用)

您可以在使用常量的每个文件中或在项目的预编译头中包含此文件。

您可以在.m文件中定义这些常量

// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";

应将Constants.m添加到应用程序/框架的目标中,以便将其链接到最终产品。

使用字符串常量代替的优点 #define'常量是你可以使用指针比较来测试相等性(stringInstance == MyFirstConstant)这比字符串比较快得多([stringInstance isEqualToString:MyFirstConstant])(并且更容易阅读,IMO)。


1239
2018-02-11 22:38



对于整数常量,它是:extern int const MyFirstConstant = 1; - Dan Morgan
总的来说,很好的答案,有一个明显的警告:你不想在Objective-C中使用==运算符测试字符串相等性,因为它测试内存地址。始终使用-isEqualToString:为此。您可以通过比较MyFirstConstant和[NSString stringWithFormat:MyFirstConstant]轻松获得不同的实例。不要假设你有一个字符串的实例,即使是文字也是如此。 (无论如何,#define是一个“预处理器指令”,在编译之前被替换,所以编译器最终会看到一个字符串文字。) - Quinn Taylor
在这种情况下,如果它真正用作常量符号(即使用符号MyFirstConstant而不是包含@“MyFirstConstant”的字符串),则可以使用==来测试与常量的相等性。在这种情况下,可以使用整数而不是字符串(实际上,这就是你正在做的事情 - 将指针用作整数)但是使用常量字符串会使调试稍微容易一些,因为常量的值具有人类可读的含义。 - Barry Wark
+1“Constants.m应添加到您的应用程序/框架的目标中,以便它链接到最终产品。”救了我的理智。 @amok,在Constants.m上执行“获取信息”并选择“目标”选项卡。确保检查相关目标。 - PEZ
@Barry:在Cocoa中,我看过许多定义他们的类 NSString 属性 copy 代替 retain。因此,他们可以(而且应该)持有你的不同实例 NSString* 常量和直接内存地址比较会失败。另外,我认为任何合理的最佳实现 -isEqualToString: 在进入字符比较的细节之前,会检查指针是否相等。 - Ben Mosher


最简单的方法:

// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"

更好的方法:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";

第二个好处的一个好处是更改常量的值不会导致整个程序的重建。


264
2018-02-11 22:02



我以为你不应该改变常量的值。 - ruipacheco
Andrew正在引用在编码时更改常量的值,而不是在应用程序运行时。 - Randall
这样做有没有附加价值 extern NSString const * const MyConstant,即,使它成为常量对象的常量指针,而不仅仅是一个常量指针? - Hari Karam Singh
如果我在头文件中使用这个声明,静态NSString * const kNSStringConst = @“const value”会发生什么? .h和.m文件中不单独声明和初始化有什么区别? - karim
@Dogweather - 只有编译器知道答案的地方。 IE,如果你想在一个about菜单中包含哪个编译器用于编译应用程序的构建,你可以把它放在那里,因为编译后的代码否则无论如何都不知道。我想不出很多其他的地方。当然不应该在许多地方使用宏。如果我有#define MY_CONST 5和其他地方#define MY_CONST_2 25怎么办?结果是当你尝试编译5_2时,你可能最终会遇到编译器错误。不要将#define用于常量。使用const作为常量。 - ArtOfWarfare


还有一件事要提。如果你需要一个非全局常量,你应该使用 static 关键词。

// In your *.m file
static NSString * const kNSStringConst = @"const value";

因为 static 关键字,此const在文件外部不可见。


小修正 @QuinnTaylor 静态变量在a中可见 编制单位。通常,这是一个单独的.m文件(如本例所示),但如果你在其他地方包含的头文件中声明它,它会咬你,因为你在编译后会遇到链接器错误


182
2018-02-12 16:28



小修正:静态变量在a中可见 编制单位。通常,这是一个单独的.m文件(如本例所示),但如果您在其他地方包含的标头中声明它,它会咬你,因为编译后会出现链接器错误。 - Quinn Taylor
如果我不使用static关键字,整个项目中kNSStringConst是否可用? - Danyal Aytekin
好的,刚刚检查过...如果你让静态关闭,Xcode不会在其他文件中为它提供自动完成功能,但我尝试在两个不同的地方放置相同的名称,并重现了Quinn的链接器错误。 - Danyal Aytekin
头文件中的静态不会给链接器带来问题。但是,包含头文件的每个编译单元都将获得自己的静态变量,因此如果包含100个.m文件的头,则可以获得100个。 - gnasher729
@kompozer你在.m文件的哪个部分放置了这个? - Basil Bourque


接受的(和正确的)答案说“你可以在项目的预编译头中包含这个[Constants.h]文件。”

作为一个新手,我很难做到这一点而没有进一步的解释 - 这里是如何:在你的YourAppNameHere-Prefix.pch文件中(这是Xcode中预编译头的默认名称),导入你的Constants.h 在 - 的里面 #ifdef __OBJC__ 块

#ifdef __OBJC__
  #import <UIKit/UIKit.h>
  #import <Foundation/Foundation.h>
  #import "Constants.h"
#endif

另请注意,除了接受的答案中描述的内容之外,Constants.h和Constants.m文件中除了其他内容之外绝对不应包含任何其他内容。 (没有接口或实现)。


117
2017-09-18 14:08



我做了这个,但是一些文件在编译时出错“使用未声明的标识符'CONSTANTSNAME'如果我在文件中包含constant.h引发错误,它可以工作,但这不是我想要做的。我已经清理,关机xcode和构建仍然存在问题...任何想法? - J3RM


我通常使用Barry Wark和Rahul Gupta发布的方式。

虽然,我不喜欢在.h和.m文件中重复相同的单词。 请注意,在以下示例中,两个文件中的行几乎相同:

// file.h
extern NSString* const MyConst;

//file.m
NSString* const MyConst = @"Lorem ipsum";

因此,我喜欢做的是使用一些C预处理器机器。 让我通过这个例子来解释一下。

我有一个定义宏的头文件 STR_CONST(name, value)

// StringConsts.h
#ifdef SYNTHESIZE_CONSTS
# define STR_CONST(name, value) NSString* const name = @ value
#else
# define STR_CONST(name, value) extern NSString* const name
#endif

在我想要定义常量的.h / .m对中,我执行以下操作:

// myfile.h
#import <StringConsts.h>

STR_CONST(MyConst, "Lorem Ipsum");
STR_CONST(MyOtherConst, "Hello world");

// myfile.m
#define SYNTHESIZE_CONSTS
#import "myfile.h"

et voila,我只有.h文件中有关常量的所有信息。


50
2017-12-02 21:53



嗯,有一点需要注意,如果将头文件导入到预编译头中,则不能使用这种技术,因为它不会将.h文件加载到.m文件中,因为它已经编译过了。有一种方法 - 请参阅我的答案(因为我不能在评论中放置好的代码。 - Scott Little
我不能让这个工作。如果我在#import“myfile.h”之前放置#define SYNTHESIZE_CONSTS,它会在.h和.m中执行NSString * ...(使用助手视图和预处理器检查)。它会引发重新定义错误。如果我把它放在#import“myfile.h”之后,它会在两个文件中执行extern NSString * ....然后它抛出“未定义的符号”错误。 - arsenius


稍微修改一下@Krizz的建议,这样如果将常量头文件包含在PCH中它就能正常工作,这是很正常的。由于原件被导入PCH,因此不会将其重新加载到PCH中 .m 文件,因此您没有符号,链接器不满意。

但是,以下修改允许它工作。这有点令人费解,但它确实有效。

你需要 3 文件, .h 具有常量定义的文件 .h 文件和 .m 文件,我会用 ConstantList.hConstants.h 和 Constants.m, 分别。的内容 Constants.h 简单地说:

// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"

Constants.m 文件看起来像:

// Constants.m
#ifdef STR_CONST
    #undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include "ConstantList.h"

最后, ConstantList.h file中包含实际的声明,这就是全部:

// ConstantList.h
STR_CONST(kMyConstant, "Value");
…

有几点需要注意:

  1. 我不得不重新定义中的宏 .m 文件   #undef将它用于宏使用。

  2. 我也不得不使用 #include 代替 #import 为了正常工作,避免编译器看到以前预编译的值。

  3. 这将需要在任何值更改时重新编译您的PCH(可能还有整个项目),如果它们正常分开(和重复)则不是这种情况。

希望对某人有所帮助。


25
2017-12-03 00:03



使用#include为我解决这个问题。 - Ramsel
与接受的答案相比,这是否有任何性能/内存损失? - Gyfis
在回答与接受的答案相比的表现时,没有。从编译器的角度来看,它实际上是完全相同的。你最终会得到相同的声明。如果你更换它们,它们就完全一样了 extern 以上与 FOUNDATION_EXPORT。 - Scott Little


我自己有一个标题专门用于声明用于首选项的常量NSStrings,如下所示:

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;

然后在随附的.m文件中声明它们:

NSString * const PPRememberMusicList = @"Remember Music List";
NSString * const PPLoadMusicAtListLoad = @"Load music when loading list";
NSString * const PPAfterPlayingMusic = @"After playing music";
NSString * const PPGotoStartupAfterPlaying = @"Go to startup pos. after playing";

这种方法对我很有帮助。

编辑:请注意,如果在多个文件中使用字符串,这将最有效。如果只有一个文件使用它,你可以这样做 #define kNSStringConstant @"Constant NSString" 在使用该字符串的.m文件中。


24
2018-01-26 23:15





// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";

14
2017-09-28 04:37





正如Abizer所说,你可以将它放入PCH文件中。另一种不那么脏的方法是为所有密钥创建一个包含文件,然后将其包含在您正在使用密钥的文件中,或者将其包含在PCH中。将它们放在自己的包含文件中,至少可以为您提供一个查找和定义所有这些常量的位置。


12
2018-02-11 22:05





如果你想要像全局常量那样的东西;一个快速的脏方法是将常量声明放入 pch 文件。


11
2018-02-11 21:56



编辑.pch通常不是最好的主意。你必须找到一个实际的地方 确定 变量,几乎总是一个.m文件,所以它更有意义 宣布 它在匹配的.h文件中。如果您在整个项目中需要它们,那么创建Constants.h / m对的接受答案是很好的。我通常根据它们的使用位置将常量尽可能地放在层次结构中。 - Quinn Taylor
不...它很快但很脏 - trillions