Objective-C中的常数


1002

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

我知道这是个好主意,因为如果需要,它可以轻松更改键。
另外,这就是整个“将数据与逻辑分离”的概念。

无论如何,有没有一种好的方法可以使整个应用程序一次定义这些常量?

我敢肯定有一种简单而智能的方法,但是现在我的课程只是重新定义了他们使用的课程。


7
OOP就是根据逻辑对数据进行分组。您提出的建议只是一种良好的编程习惯,即使您的程序易于更改。
拉菲·哈查杜安

Answers:


1287

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

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

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

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

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

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

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

使用字符串常量而不是#define'd常量的优点是,您可以使用指针比较(stringInstance == MyFirstConstant)测试是否相等,这比字符串比较([stringInstance isEqualToString:MyFirstConstant])快得多(IMO更易于阅读)。


67
对于整数常量,它将是:extern int const MyFirstConstant = 1;
丹·摩根

180
总体而言,这是一个很好的答案,但有一个明显的警告:您不希望使用Objective-C中的==运算符测试字符串是否相等,因为它会测试内存地址。为此,请始终使用-isEqualToString:。您可以通过比较MyFirstConstant和[NSString stringWithFormat:MyFirstConstant]轻松获得其他实例。即使您使用文字,也不要假设您拥有字符串的哪个实例。(无论如何,#define是一个“预处理程序指令”,在编译之前会被替换,因此无论哪种方式,编译器最终都会看到字符串文字。)
Quinn Taylor

74
在这种情况下,如果确实将其用作常量符号(即使用符号MyFirstConstant而不是包含@“ MyFirstConstant”的字符串),则可以使用==来测试与常量的相等性。在这种情况下,可以使用整数而不是字符串(确实,这就是您正在做的工作-使用指针作为整数),但是使用常量字符串会使调试稍微容易一些,因为常量的值具有易于理解的含义。
巴里·沃克

17
+1表示“ Constants.m应该添加到应用程序/框架的目标中,以便将其链接到最终产品。” 救了我的理智。@amok,在Constants.m上执行“获取信息”,然后选择“目标”选项卡。确保检查了相关目标。
PEZ 2010年

73
@Barry:在Cocoa中,我看到了许多NSStringcopy而不是定义其属性的类retain。这样,它们可能(并且应该)持有您NSString*常量的其他实例,并且直接内存地址比较将失败。另外,我假设任何合理的最佳实现-isEqualToString:都会在进入字符比较的本质之前检查指针是否相等。
Ben Mosher

280

最简单的方法:

// 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";

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


12
我以为您不应该更改常数的值。
ruipacheco

71
安德鲁指的是在编码时而不是在应用程序运行时更改常量的值。
兰德尔

7
这样做是否有任何附加值extern NSString const * const MyConstant,即使其成为指向常量对象的常量指针,而不只是常量指针?
Hari Karam Singh'3

4
如果我在头文件中使用此声明,会发生什么情况,静态NSString * const kNSStringConst = @“ const value”; 在.h和.m文件中不分别声明和初始化有什么区别?
karim 2012年

4
@Dogweather-只有编译器知道答案的地方。IE,如果您想在About菜单中包含使用哪个编译器来编译应用程序的内部版本,则可以将其放在此处,因为否则编译后的代码将一无所知。我想不出其他地方了。当然,宏不应该在很多地方使用。如果我有#define MY_CONST 5和其他地方#define MY_CONST_2 25,该怎么办。其结果是,当它尝试编译5_2时,很可能会遇到编译器错误。请勿将#define用于常量。对常量使用const。
ArtOfWarfare 2013年

190

还有一件事要提。如果需要非全局常量,则应使用static关键字。

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

由于有static关键字,此const在文件外部不可见。


通过@QuinnTaylor进行的较小校正:静态变量在编译单元中可见。通常,这是一个.m文件(如本例所示),但如果在其他位置包含的标头中声明它,它可能会咬住您,因为编译后会出现链接器错误


41
较小的更正:静态变量在编译单元中可见。通常,这是一个.m文件(如本例所示),但如果在其他位置包含的标头中声明它,它可能会咬住您,因为编译后会出现链接器错误。
奎因·泰勒

如果我不使用static关键字,则kNSStringConst在整个项目中都可用吗?
2011年

2
好的,只是检查了一下……如果您不使用static,Xcode不会在其他文件中为其提供自动补全功能,但是我尝试将相同的名称放在两个不同的位置,并重现了Quinn的链接器错误。
2011年

1
头文件中的static不会导致链接器问题。但是,每个包含头文件的编译单元都将获得其自己的静态变量,因此,如果从100个.m文件中包含头,则将获得100个静态变量。
gnasher729 2014年

@kompozer将此文件放在.m文件的哪个部分?
罗勒·布尔克

117

公认的(正确的)答案是:“您可以将此[Constants.h]文件...包括在项目的预编译头中。”

作为新手,我很难在没有进一步说明的情况下执行此操作-方法如下:在YourAppNameHere-Prefix.pch文件(这是Xcode中预编译标头的默认名称)中,将Constants.h导入#ifdef __OBJC__block中

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

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


我这样做了,但是某些文件在编译时抛出错误“使用未声明的标识符'CONSTANTSNAME'。如果我在抛出错误的文件中包含constant.h,它可以工作,但这不是我想要做的。我已经清理,关机xcode和build仍然有问题...有什么想法吗
J3RM

50

我通常使用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"

等,我仅在.h文件中拥有有关常量的所有信息。


嗯,有一点警告,但是,如果将头文件导入到预编译的头中,则不能使用这种技术,因为它不会将.h文件加载到.m文件中,因为它已经被编译。有一种方法,但-看到我的回答(因为我不能把好的代码中的注释。
斯科特·利特尔

我无法正常工作。如果将#define SYNTHESIZE_CONSTS放在#import“ myfile.h”之前,则它在.h和.m中都执行NSString * ...(已使用助手视图和预处理器进行了检查)。它将引发重新定义错误。如果我将其放在#import“ myfile.h”之后,它将在两个文件中都执行extern NSString *...。然后抛出“未定义符号”错误。
arsenius

28

我自己有一个标头,用于声明用于首选项的常量NSString,如下所示:

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文件中进行操作。


25

对@Krizz的建议稍加修改,以便在将常量头文件包含在PCH中时正常工作。由于原始文件已导入到PCH中,因此不会将其重新加载到.m文件中,因此您将没有任何符号,并且链接程序不满意。

但是,下面的修改使其可以工作。这有点令人费解,但是可以。

你需要3档,.h其中有常量定义,该文件.h的文件和.m文件,我将使用ConstantList.hConstants.hConstants.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文件中包含实际的声明,仅此而已:

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

需要注意的几件事:

  1. 我不得不重新定义宏.m文件后, #undef荷兰国际集团它的宏观使用。

  2. 我还必须使用#include代替它,#import以使其正常工作,并避免编译器看到以前预编译的值。

  3. 每当更改任何值时,都需要重新编译您的PCH(可能还有整个项目),而如果正常分开(并重复)它们,则情况并非如此。

希望对某人有帮助。


1
使用#include为我解决了这个头痛问题。
拉姆瑟尔

与接受的答案相比,这会不会有任何性能/内存损失?
Gyfis

与可接受的答案相比,性能没有答案。从编译器的角度来看,这实际上是完全相同的事情。您最终得到相同的声明。如果您将extern以上内容替换为,它们将完全相同FOUNDATION_EXPORT
Scott Little


12

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


11

如果您想要全局常量之类的东西;一种快速的肮脏方法是将常量声明放入pch文件中。


7
编辑.pch通常不是最好的主意。您将必须找到一个实际定义变量的位置,几乎总是一个.m文件,因此在匹配的.h文件中声明它更有意义。如果您在整个项目中都需要使用Constants.h / m对,那么公认的答案是一个不错的选择。通常,我会根据使用常量的位置将常量放在尽可能低的层次上。
奎因·泰勒,

8

尝试使用类方法:

+(NSString*)theMainTitle
{
    return @"Hello World";
}

我有时会用它。


6
类方法不是常量。它在运行时会产生成本,并且可能不会总是返回相同的对象(如果您以这种方式实现,它将返回,但不一定以这种方式实现),这意味着必须isEqualToString:用于比较,即在运行时产生进一步的成本。当您需要常量时,请创建常量。
Peter Hosey,

2
@Peter Hosey,尽管您的评论是正确的,但我们认为在每个LOC中使用Ruby之类的“高级”语言都会使性能下降一次或更多,而不必担心。我并不是说您说的不对,而是只是评论标准在不同的“世界”中如何不同。
丹·罗森斯塔克2011年

1
在Ruby上为真。人们对大多数性能的编码对于典型应用而言都是不必要的。
彼得·德威斯

8

如果您喜欢命名空间常量,则可以利用struct,星期五问答,2011-08-19:命名空间常量和函数

// in the header
extern const struct MANotifyingArrayNotificationsStruct
{
    NSString *didAddObject;
    NSString *didChangeObject;
    NSString *didRemoveObject;
} MANotifyingArrayNotifications;

// in the implementation
const struct MANotifyingArrayNotificationsStruct MANotifyingArrayNotifications = {
    .didAddObject = @"didAddObject",
    .didChangeObject = @"didChangeObject",
    .didRemoveObject = @"didRemoveObject"
};

1
太好了!但是在ARC下,您将需要在结构声明中的所有变量前加__unsafe_unretained限定符以使其起作用。
切门2015年

7

我使用单例类,以便可以模拟该类并根据需要更改常量以进行测试。常量类如下所示:

#import <Foundation/Foundation.h>

@interface iCode_Framework : NSObject

@property (readonly, nonatomic) unsigned int iBufCapacity;
@property (readonly, nonatomic) unsigned int iPort;
@property (readonly, nonatomic) NSString * urlStr;

@end

#import "iCode_Framework.h"

static iCode_Framework * instance;

@implementation iCode_Framework

@dynamic iBufCapacity;
@dynamic iPort;
@dynamic urlStr;

- (unsigned int)iBufCapacity
{
    return 1024u;
};

- (unsigned int)iPort
{
    return 1978u;
};

- (NSString *)urlStr
{
    return @"localhost";
};

+ (void)initialize
{
    if (!instance) {
        instance = [[super allocWithZone:NULL] init];
    }
}

+ (id)allocWithZone:(NSZone * const)notUsed
{
    return instance;
}

@end

它的用法是这样的(请注意,常量c的缩写形式- [[Constants alloc] init]每次都节省了键入操作):

#import "iCode_FrameworkTests.h"
#import "iCode_Framework.h"

static iCode_Framework * c; // Shorthand

@implementation iCode_FrameworkTests

+ (void)initialize
{
    c  = [[iCode_Framework alloc] init]; // Used like normal class; easy to mock!
}

- (void)testSingleton
{
    STAssertNotNil(c, nil);
    STAssertEqualObjects(c, [iCode_Framework alloc], nil);
    STAssertEquals(c.iBufCapacity, 1024u, nil);
}

@end

1

如果NSString.newLine;要从目标c 调用类似的东西,并且希望它是静态常量,则可以快速创建类似这样的东西:

public extension NSString {
    @objc public static let newLine = "\n"
}

而且您有一个很好的可读常量定义,并且可以在选择的类型中使用,同时还限于类型的上下文。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.