Objective-C中的常量

All*_*lyn 996 cocoa constants objective-c

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

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

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

Bar*_*ark 1282

你应该创建一个像头文件

// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc.
Run Code Online (Sandbox Code Playgroud)

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

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

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

// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";
Run Code Online (Sandbox Code Playgroud)

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

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

  • 总的来说,很好的答案,有一个明显的警告:你不想在Objective-C中使用==运算符测试字符串相等性,因为它测试内存地址.始终使用-isEqualToString:为此.您可以通过比较MyFirstConstant和[NSString stringWithFormat:MyFirstConstant]轻松获得不同的实例.不要假设您拥有的字符串实例,即使是文字.(无论如何,#define是一个"预处理器指令",并且在编译之前被替换,所以编译器最终会看到一个字符串文字.) (179认同)
  • 在这种情况下,如果它真正用作常量符号(即使用符号MyFirstConstant而不是包含@"MyFirstConstant"的字符串),则可以使用==来测试与常量的相等性.在这种情况下,可以使用整数代替字符串(实际上,这就是你正在做的事情 - 将指针用作整数)但是使用常量字符串会使调试稍微容易一些,因为常量的值具有人类可读的含义. (74认同)
  • @Barry:在Cocoa中,我看到许多类用`copy`而不是`retain`来定义它们的`NSString`属性.因此,它们可以(并且应该)持有`NSString*`常量的不同实例,并且直接内存地址比较将失败.另外,我认为`-isEqualToString:`的任何合理优化的实现都会在进入字符比较的细节之前检查指针的相等性. (73认同)
  • 对于整数常量,它是:extern int const MyFirstConstant = 1; (67认同)
  • +1"Constants.m应添加到您的应用程序/框架的目标中,以便它链接到最终产品." 救了我的理智.@amok,在Constants.m上执行"获取信息"并选择"目标"选项卡.确保检查相关目标. (17认同)
  • 在任何情况下都不要使用`==`进行字符串比较.是的,如果您了解编译器/运行时(!),并且您需要小心,那么您可以正确使用它.但它是一个漏洞的抽象,并且需要在代码库的一个部分中关于另一个部分组合在一起的方式的特殊隐形知识.@BarryWark:我同情你的观点,把它们用作"符号",并将它们用于那种事情,但即便如此,因为它们被公开宣称为"NSString",它们的"象征"也是特殊的知识.比较和信任`-isEqualToString:`更好地保持一致. (9认同)
  • WRT使用==来测试持续的相等性,这在技术上是可以的,但实际上是一个坏主意.考虑从HTTP服务器使用JSON响应并检查字典中的键是否为已知键名常量的情况.在这种情况下使用==将失败,因为JSON解析器为您的字典键构造了唯一的NSString*对象,您试图与其他NSString*对象进行比较,这些对象当然具有不同的内存地址.要理智并使用isEqualToString并让运行时处理内存寻址. (6认同)
  • @Debajit是的,在C++世界中,const NSString*const MyConstant是正确的.但是,由于Objective-C是C超集,因此const正确性不是其历史的一部分,并且您会收到很多关于将incrrect(const)指针传递给期望NSString*的方法的警告,即使NSString*是不可变的,因此可以声明const NSString*. (4认同)
  • 任何人都可以给我一个如何在XCode 4中做到这一点的提示吗?(将Constants.m添加到应用程序/框架的目标) (4认同)
  • ==对于字符串相等测试在某些情况下工作,正如已经指出的那样,但我认为这只是一个坏主意.它太脆弱了以后编码风格的改变或后来改变的设计决定将导致使用==打破的"正确性".它真的可以回来咬你的臀部. (4认同)
  • 对于记录,==*应该*由于字符串实习而适用于NSStrings,只要您使用常量不可变字符串(即不是+ stringWithFormat :).但是,-isEqualToString :,如果它被正确实现,*应该*首先使用指针相等性检查进行短路.但是,我应该注意到,所有这些*都应该改变,所以写下防御性的. (4认同)
  • @rptwsthi不要在头文件中为const赋值 - 而是在.m文件中赋值.您仍然需要在.h文件中声明const(不指定值). (2认同)
  • 注意/警告.使用ARC这样的`NSString`常量时可能会出现问题.有时常量会在应用程序运行时意外自动释放(我在将const字符串传递给`NSNotificationCenter`作为通知名称时经历过这种情况.第一次传递它工作正常,但当我想发出第二个通知时,应用程序崩溃了"糟糕的内存访问").所以,我最终得到了`#define`-styled constants. (2认同)
  • 如果我以这种方式定义所有常量字符串,它是否会增加我的内存占用量? (2认同)

And*_*ant 279

最简单的方法:

// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"
Run Code Online (Sandbox Code Playgroud)

更好的方法:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";
Run Code Online (Sandbox Code Playgroud)

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

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

小智 190

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

// In your *.m file
static NSString * const kNSStringConst = @"const value";
Run Code Online (Sandbox Code Playgroud)

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


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

  • 次要修正:静态变量在*编译单元*中可见.通常,这是一个单独的.m文件(如本例所示),但如果您在其他地方包含的标头中声明它,它会咬你,因为在编译后你会遇到链接器错误. (41认同)
  • 好的,刚刚检查过......如果你让静态关闭,Xcode不会在其他文件中为它提供自动完成功能,但我尝试在两个不同的地方放置相同的名称,并重现Quinn的链接器错误. (2认同)
  • 头文件中的 static 不会产生链接器问题。但是,包含头文件的每个编译单元都会获得自己的静态变量,因此如果包含 100 个 .m 文件中的头文件,则会获得 100 个静态变量。 (2认同)

Vic*_*Hee 118

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

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

#ifdef __OBJC__
  #import <UIKit/UIKit.h>
  #import <Foundation/Foundation.h>
  #import "Constants.h"
#endif
Run Code Online (Sandbox Code Playgroud)

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


Kri*_*izz 50

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

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

// file.h
extern NSString* const MyConst;

//file.m
NSString* const MyConst = @"Lorem ipsum";
Run Code Online (Sandbox Code Playgroud)

因此,我喜欢做的是使用一些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
Run Code Online (Sandbox Code Playgroud)

在我想要定义常量的.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"
Run Code Online (Sandbox Code Playgroud)

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


Mad*_*ane 28

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

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;
Run Code Online (Sandbox Code Playgroud)

然后在随附的.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";
Run Code Online (Sandbox Code Playgroud)

这种方法对我很有帮助.

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


Sco*_*tle 25

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

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

你需要3档,.h其中有常量定义,该文件.h的文件和.m文件,我将使用ConstantList.h,Constants.hConstants.m分别.Constants.h简单的内容是:

// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"
Run Code Online (Sandbox Code Playgroud)

Constants.m文件看起来像:

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

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

// ConstantList.h
STR_CONST(kMyConstant, "Value");
…
Run Code Online (Sandbox Code Playgroud)

有几点需要注意:

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

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

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

希望对某人有帮助.


小智 14

// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";
Run Code Online (Sandbox Code Playgroud)


Gra*_*erg 12

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


Abi*_*ern 11

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

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

小智 8

尝试使用类方法:

+(NSString*)theMainTitle
{
    return @"Hello World";
}
Run Code Online (Sandbox Code Playgroud)

我有时会用它.

  • 类方法不是常量.它在运行时有成本,并且可能并不总是返回相同的对象(如果你以这种方式实现它,但你没有必要那样实现它),这意味着你必须使用`isEqualToString:`比较,这是运行时的进一步成本.当你想要常量时,制作常量. (6认同)
  • @Peter Hosey,虽然你的评论是正确的,但我们会在像Ruby这样的"更高级别"语言中使用LOC或者更多的性能,而不必担心它.我不是说你不对,而只是评论标准在不同的"世界"中是如何不同的. (2认同)

onm*_*133 8

如果你喜欢名称空间常量,你可以利用struct,Friday Q&A 2011-08-19:Namespaced Constants and Functions

// 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"
};
Run Code Online (Sandbox Code Playgroud)


How*_*att 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
Run Code Online (Sandbox Code Playgroud)

它就像这样使用(注意使用常量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
Run Code Online (Sandbox Code Playgroud)