Objective-C 中的常量

Constants in Objective-C

提问人:Allyn 提问时间:2/12/2009 最后编辑:Arsen KhachaturyanAllyn 更新时间:1/21/2022 访问量:446396

问:

我正在开发一个 Cocoa 应用程序,我正在使用常量 s 来存储我的偏好的键名称。NSString

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

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

我确信有一种简单而智能的方法,但现在我的类只是重新定义了它们使用的类。

iOS Objective-C Cocoa NSString 常量

评论

8赞 Raffi Khatchadourian 12/13/2011
OOP 是关于使用逻辑对数据进行分组。你建议的只是一个好的编程实践,即使你的程序易于更改。

答:

11赞 Abizern 2/12/2009 #1

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

评论

7赞 Quinn Taylor 6/30/2009
编辑 .pch 通常不是最好的主意。您必须找到一个实际定义变量的位置,几乎总是一个 .m 文件,因此在匹配的 .h 文件中声明它更有意义。创建一个 Constants.h/m 对的公认答案是一个很好的答案,如果你在整个项目中需要它们。我通常会根据常量的使用位置,将常量放在层次结构中尽可能靠后的位置。
285赞 Andrew Grant 2/12/2009 #2

最简单的方法:

// 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 12/28/2009
我以为你不应该改变常量的值。
74赞 Randall 2/27/2010
Andrew 指的是在编码时更改常量的值,而不是在应用程序运行时更改常量的值。
7赞 Hari Honor 3/1/2012
这样做是否有任何附加值,即使其成为指向常量对象的常量指针,而不仅仅是常量指针?extern NSString const * const MyConstant
4赞 karim 3/8/2012
如果我在头文件中使用此声明,会发生什么,static NSString * const kNSStringConst = @“const value”;在 .h 和 .m 文件中不分别声明和初始化有什么区别?
4赞 ArtOfWarfare 4/24/2013
@Dogweather - 只有编译器知道答案的地方。IE,如果你想在关于菜单中包含哪个编译器用于编译应用程序的构建,你可以把它放在那里,否则编译的代码无论如何都不会知道。我想不出很多其他地方。宏当然不应该在很多地方使用。如果我有 #define MY_CONST 5 和其他地方 #define MY_CONST_2 25 怎么办。结果是,当它尝试编译 5_2 时,您很可能最终会遇到编译器错误。不要将 #define 用于常量。将 const 用于常量。
12赞 Grant Limberg 2/12/2009 #3

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

1312赞 Barry Wark 2/12/2009 #4

您应该创建一个头文件,如下所示:

// 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应该添加到应用程序/框架的目标中,以便将其链接到最终产品。

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

评论

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

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

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

由于关键字的原因,此常量在文件外部不可见。static


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

评论

41赞 Quinn Taylor 6/30/2009
次要更正:静态变量在编译单元中可见。通常,这是一个单个 .m 文件(如本例所示),但如果在其他地方包含的标头中声明它,它可能会咬你,因为编译后会出现链接器错误。
0赞 Danyal Aytekin 11/7/2011
如果我不使用 static 关键字,kNSStringConst 是否在整个项目中都可用?
2赞 Danyal Aytekin 11/7/2011
好的,刚刚检查过...如果您关闭静态,Xcode 不会在其他文件中为其提供自动完成功能,但我尝试将相同的名称放在两个不同的地方,并重现了 Quinn 的链接器错误。
2赞 gnasher729 4/14/2014
头文件中的静态不会给链接器带来问题。但是,每个编译单元(包括头文件)都将获得自己的静态变量,因此,如果包含 100 个 .m 文件中的头文件,则将获得 100 个静态变量。
0赞 Basil Bourque 5/23/2014
@kompozer 你把它放在.m文件的哪个部分?
9赞 groumpf 12/6/2009 #6

尝试使用类方法:

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

我有时会使用它。

评论

7赞 Peter Hosey 12/6/2009
类方法不是常量。它在运行时有成本,并且可能并不总是返回相同的对象(如果您以这种方式实现它,它就会返回,但您不一定以这种方式实现它),这意味着您必须用于比较,这在运行时会进一步产生成本。当您需要常量时,请创建常量。isEqualToString:
2赞 Dan Rosenstark 1/18/2011
@Peter Hosey,虽然您的评论是对的,但在像 Ruby 这样的“高级”语言中,我们对每个 LOC 或更多次的性能造成影响,而不必担心。我并不是说你不对,而只是评论不同“世界”的标准有何不同。
1赞 Peter DeWeese 5/26/2011
在 Ruby 上是真的。对于典型的应用程序来说,人们编写的大多数性能都是完全不必要的。
117赞 Victor Van Hee 9/18/2011 #7

公认的(和正确的)答案是“你可以包括这个 [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 文件除了已接受的答案中描述的内容外,绝对不包含任何其他内容。(无接口或实现)。

评论

0赞 J3RM 8/31/2012
我这样做了,但有些文件在编译时抛出错误“使用未声明的标识符'CONSTANTSNAME'如果我在抛出错误的文件中包含constant.h,它就会起作用,但这不是我想做的。我已经清理、关闭了 xcode 并构建了,但仍然有问题......有什么想法吗?
14赞 rahul gupta 9/28/2011 #8
// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";
51赞 Krizz 12/3/2011 #9

我通常使用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 文件中拥有有关常量的所有信息。

评论

0赞 Scott Little 12/3/2011
嗯,有一点警告,但是,如果将头文件导入到预编译头中,则不能像这样使用此技术,因为它不会将 .h 文件加载到 .m 文件中,因为它已经编译过。不过有一种方法 - 看看我的答案(因为我不能在评论中放好代码。
0赞 arsenius 1/25/2014
我无法让它工作。如果我 #define SYNTHESIZE_CONSTS 放在“myfile.h”之前 #import 它会做 NSString*...在 .h 和 .m 中(使用助手视图和预处理器进行检查)。它会引发重新定义错误。如果我把它放在“myfile.h”之后 #import 它确实是 extern NSString*...在这两个文件中。然后它会抛出“未定义符号”错误。
25赞 Scott Little 12/3/2011 #10

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

但是,以下修改允许它工作。这有点复杂,但它有效。

您将需要 3 个文件,具有常量定义的文件,file 和 file,我将分别使用 和 。内容简单来说就是:.h.h.mConstantList.hConstants.hConstants.mConstants.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. 我必须在 ing 之后重新定义文件中的宏才能使用宏。.m#undef

  2. 我还必须使用 #include 而不是让它正常工作,并避免编译器看到先前预编译的值。#import

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

希望这对某人有所帮助。

评论

1赞 OdieO 4/3/2013
使用 #include 为我解决了这个头痛的问题。
0赞 Gyfis 5/31/2015
与公认的答案相比,这是否有任何性能/内存损失?
0赞 Scott Little 8/12/2015
与公认的答案相比,在回答性能时,没有。从编译器的角度来看,这实际上是完全相同的事情。你最终会得到相同的声明。如果您将上述内容替换为 .externFOUNDATION_EXPORT
7赞 Howard Lovatt 7/16/2012 #11

我使用单例类,这样我就可以模拟该类并在必要时更改常量以进行测试。常量类如下所示:

#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
29赞 MaddTheSane 1/27/2013 #12

我自己有一个标头,专门用于声明用于首选项的常量 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";

这种方法对我很有帮助。

编辑:请注意,如果在多个文件中使用字符串,则效果最佳。如果只有一个文件使用它,则只需在使用该字符串的 .m 文件中执行即可。#define kNSStringConstant @"Constant NSString"

10赞 onmyway133 7/25/2015 #13

如果你喜欢命名空间常量,你可以利用结构, 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赞 Cemen 8/2/2015
一件了不起的事情!但是在 ARC 下,您需要在结构声明中的所有变量前面加上限定符才能使其正常工作。__unsafe_unretained
1赞 Renetik 1/22/2019 #14

如果你想从目标 c 调用这样的东西,并且你希望它是静态常量,你可以在 swift 中创建这样的东西:NSString.newLine;

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

而且你有很好的可读常量定义,并且可以从你选择的类型中获取,同时 stile 绑定到类型的上下文。