我正在开发一个Cocoa应用程序,并使用常量NSString作为存储我的首选项的键名的方法。
我明白这是个好主意,因为它允许在必要时轻松更换钥匙。另外,它是整个"将数据与逻辑分离"的概念。
无论如何,有没有一种好的方法可以让这些常量为整个应用程序定义一次?我相信有一种简单而聪明的方法,但现在我的课程只是重新定义他们使用的方法。
- OOP是关于用逻辑对数据进行分组的。你所建议的只是一个好的编程实践,也就是说,让你的程序易于更改。
你应该创建一个头文件
1 2 3 4
| // Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc. |
(可以使用EDCOX1×1)代替EDCOX1(2),如果您的代码不在混合C/C++环境或其他平台上使用)
您可以将此文件包含在每个使用常量的文件中,也可以包含在项目的预编译头文件中。
在.m文件中定义这些常量
1 2 3
| // Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant"; |
常量.m应该添加到应用程序/框架的目标中,以便将其链接到最终产品中。
使用字符串常量而不是#defined常量的好处是,您可以使用指针比较(stringInstance == MyFirstConstant来测试相等性,这比字符串比较([stringInstance isEqualToString:MyFirstConstant]快得多(而且更容易读取,imo)。
- 对于整型常量,它是:extern int const myfirstconstant=1;
- 最好说:extern const nsstring*const myfirstconstant?
- @ DeBjjIT是的,在C++世界中,const NScord*const MySimple是正确的。但是,由于objective-c是一个c超集,const正确性并不是其历史记录的一部分,因此对于将increct(const)指针传递给需要nsstring*的方法,您会收到许多警告,即使nsstring*是不可变的,因此可以声明为const nsstring*。
- 总的来说,答案很好,但有一个明显的警告:您不希望使用Objective-C中的==运算符测试字符串是否相等,因为它测试内存地址。为此,请始终使用-IsEqualToString:您可以通过比较myFirstConstant和[nsString StringWithFormat:myFirstConstant]轻松获得不同的实例。即使使用文字,也不要假设您拥有的字符串的实例是什么。(在任何情况下,define都是一个"预处理器指令",在编译之前被替换,因此编译器在最后看到一个字符串文字的任何一种方式。)
- 在这种情况下,如果它真的被用作常量符号(即,使用符号myfirstconstant而不是包含@"myfirstconstant"的字符串),可以使用==来测试与常量的相等性。在这种情况下,可以使用整数而不是字符串(实际上,这就是您要做的——将指针用作整数),但是使用常量字符串可以使调试稍微容易一些,因为常量的值具有人类可读的含义。
- 后续问题:假设你已经声明了一个静态的非数组。内存管理规则(比如在iPhone上)通常要求保持/释放平衡。因为这是静态的,你有什么不同的做法吗?例如,我想我可以分配/init…然后在释放释放释放。就这些吗?(我见过dealloc中没有发布的示例代码,这让我有点怀疑。)
- 这在编译我的应用程序时很好,但是在编译具有相同文件的静态库时,我会得到一个奇怪的错误:"asm"或"attribute"在"*"token之前。你知道怎么回事吗?
- 如何做到这一点:常量.m应该添加到应用程序/框架的目标中,以便将其链接到最终产品中。?
- @amok是的,常量.m需要链接(静态地作为目标的一部分)或者通过框架链接到任何使用常量的代码。
- +1表示"常量.m应该添加到应用程序/框架的目标中,以便将其链接到最终产品中。"这使我保持了理智。@amok,在constants.m上"获取信息",然后选择"目标"选项卡。确保已检查相关目标。
- @巴里:在Cocoa中,我见过许多类用copy而不是retain定义它们的NSString属性。因此,它们可能(并且应该)持有不同的NSString*常量实例,直接内存地址比较将失败。另外,我假设任何合理优化的-isEqualToString:实现在进入字符比较的实质之前都会检查指针是否相等。
- 在Xcode4中,有人能给我一个提示吗?(向应用程序/框架的目标添加常量.m)
- @本,一旦你使用常量作为字符串(例如,使用copy属性),所有的赌注都将被取消。在这种情况下,您应该使用isEqualToString:来检查是否相等。但如果使用#defined字符串,就必须这样做。
- 我对这个方法有一些问题,因为我对这里回答的PCH文件缺乏经验:stackoverflow.com/questions/7439011/…
- WRT使用==来测试常量相等性,这在技术上是可以的,但实际上是一个坏主意。考虑使用HTTP服务器的JSON响应的情况,并检查字典中的键是否是已知的键名常量。在这种情况下,使用==将失败,因为JSON解析器为您的字典键构造了唯一的nsstring*对象,您正试图与其他nsstring*对象进行比较,这些对象当然具有不同的内存地址。保持理智,使用isEqualToString,让运行时处理内存寻址。
- 如何本地化这些字符串?nslocalizedstring似乎失败了。
- ==对于字符串相等性测试,在某些情况下是有效的,正如前面提到的,但我认为这只是一个坏主意。它太脆弱了,因为编码风格的改变或稍后更改的设计决策将导致使用==的"正确性"中断。它真的会回来咬你的屁股。
- 完成所有操作后,只需在所有应用程序中输入常量的名称,如nslog(@"%@",myFirstConstant);即可使用这些常量。
- @在我的例子中,当我在const.h文件中使用extern int const playBoardBlock = 1;时,它附带了一个警告,即'ertern' variable has an initializer,这是什么建议。
- @rptws我不会在头文件中为const赋值——而是在.m文件中赋值。您仍然需要在.h文件中声明const(不指定值)。
- 我得到了"未定义的符号为架构i386"为此…
- 注意/警告。在对arc使用这样的NSString常量时可能会出现问题。有时,当应用程序运行时,常量会意外地自动释放(我在将const字符串作为通知名称传递给NSNotificationCenter时遇到过这种情况)。第一次通过时它可以正常工作,但是当我想发出第二个通知时,应用程序会因为"内存访问不良"而崩溃。所以,我得到了#define样式的常量。
- 基础出口是一个小的方便,它定义为基础的外部,它又被定义为EDCOX1,3,C++,否则为EDCOX1,4(除了Windows作为目标的微小差别…MEH)。
- 对于记录,==应该适用于NSstring,因为字符串内部存在,只要您使用的是常量、不可变的字符串(即not+stringwithformat:)。但是,-isEqualToString:,如果实现正确,则应首先使用指针相等性检查来短路自身。但是,我要注意的是,所有这些应该都可以改变,所以要写得防御性。
- 在任何情况下,都不要使用==进行字符串比较。是的,如果您了解编译器/运行时!!)你很小心,你可以把它弄好。但是它是一个泄漏的抽象,并且需要在代码库的一部分中有关于另一部分组合方式的特殊的隐形知识。@巴里沃克:我很同情你把它们作为"符号"使用,并将它们的使用范围限定在这类事情上的观点,但即使如此,因为它们被公开宣布为NSString,它们的"符号性"也是专门知识。最好在比较和信任方面保持一致1〔7〕。
- 如果我以这种方式定义所有常量字符串,它会增加我的内存占用吗?
- 实际上,它减少了内存占用,因为它确保字符串常量在程序中只存在一次。如果使用define,则每次使用常量时都可以使用不同的字符串。btw.像@"string"这样创建的字符串永远不会被释放,而copy不会复制它们。
- 一旦我们这样做了,我们如何从其他类访问/使用这些常量?我正在尝试"constants.my_constant",但我得到一个"property my_constants not found on object of type constants"错误。
- 我很好奇,在寻找这个问题时没有找到一个好的答案。在两个地方声明常量有什么好处?我知道,使用extern关键字时,必须先定义它,然后在单独的行中初始化,但为什么不在单个.h文件中使用NSString *const MyConstant = @"MyConstantValue"?
- @timarnold与MyConst定义为您所拥有的,包括.h文件在多个点(如OP所需)将导致链接过程中出现重复符号错误。
- 如果我有本地化字符串,这个解决方案就不能工作了,对吗?
- 这些常量是全局变量,因此可能与添加到项目中的框架的全局变量冲突?
- 这对ARC有效吗?我的const nsstrings在运行时为空时出现问题。
最简单的方法:
1 2
| // Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant" |
更好的方法:
1 2 3 4 5
| // Prefs.h
extern NSString * const PREFS_MY_CONSTANT;
// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant"; |
第二个好处之一是更改常量的值不会导致整个程序的重建。
- 我以为你不应该改变常量的值。
- 安德鲁指的是在编码时更改常量的值,而不是在应用程序运行时更改。
- 在执行extern NSString const * const MyConstant时是否有任何附加值,即使其成为指向常量对象的常量指针而不仅仅是常量指针?
- 如果在头文件中使用这个声明,会发生什么情况,static nsstring*const knsstringconst=@"const value";在.h和.m文件中不分别声明和init有什么区别?
- @karim头文件将#include转换成许多.m文件。在所有C语言中,这基本上是一个复制粘贴到一个翻译单元中。简短的版本是,如果在头文件中执行此操作,更改值可能会导致重新编译大量文件。这将在大型项目(可能是50k+sloc)中变得明显。
- 这不仅仅是一种偏好——你在不应该使用宏的时候使用宏。这就像使用goto——它存在于传统和某些特殊情况下(例如,从内部循环中断),但不应该在其他地方使用。如果只需要一个常量,就应该使用一个常量。这就是他们被制造/引进的目的。
- @战争艺术,如果不在这里,什么时候应该使用宏?
- @dogweather-只有编译器知道答案的地方。也就是说,如果你想在一个关于菜单中包含编译器用来编译一个应用程序的构建,你可以把它放在那里,因为编译后的代码无论如何都不会知道。我想不出其他地方了。宏当然不应该在很多地方使用。如果我定义了我的常数5,其他地方定义了我的常数25,会怎么样?结果是,当你试图编译5_2时,你很可能会得到一个编译器错误。不要使用定义常量。常量使用const。
- 如果我以这种方式定义所有常量字符串,它会增加我的内存占用吗?
还有一件事要提。如果需要非全局常量,则应使用static关键字。
例子
1 2
| // In your *.m file
static NSString * const kNSStringConst = @"const value"; |
由于static关键字,该常量在文件外部不可见。
@quinntaylor进行的微小更正:静态变量在编译单元内可见。通常,这是一个.m文件(如本例中所示),但是如果您在包含在其他地方的头文件中声明它,它可能会咬到您,因为编译后会得到链接器错误。
- 小修正:静态变量在编译单元内可见。通常,这是一个.m文件(如本例中所示),但是如果您在包含在其他地方的头文件中声明它,它可能会咬你一口,因为编译后会得到链接器错误。
- 如果我不使用static关键字,那么knsstringconst会在整个项目中都可用吗?
- 好的,刚刚检查过…如果您不使用static,xcode不会在其他文件中为它提供自动完成功能,但是我尝试在两个不同的地方使用相同的名称,并复制了Quinn的链接器错误。
- 头文件中的静态不会给链接器带来问题。但是,每个编译单元(包括头文件)都将获得自己的静态变量,因此如果包含100.m文件中的头文件,则会得到100个静态变量。
- @你把这个放在.m文件的哪个部分?
- @巴兹尔布尔克的好做法是把它们放在埃多克斯的正上方。
- @Kompozer,为什么这样命名?
- @我不知道你的意思。在static前面使用k是一种常用的约定。在任何地方,我都不再这样做了,我把staticvars叫做static NSString * const StringConst = @"const value";vars。
- @是的,我指的是变量的名称。这是标准的苹果大会吗?为什么不用STRING_CONST代替StringConst。这只是在Java世界里吗?
- 如果我把它放在头文件中,有什么问题?
接受(并且正确)的回答说"你可以包括这个[常量.h]文件…在项目的预编译头中。"
作为一个新手,在没有进一步解释的情况下,我很难做到这一点——方法如下:在yourappnamehere-prefix.pch文件(这是xcode中预编译头的默认名称)中,在#ifdef __OBJC__块中导入constants.h。
1 2 3 4 5
| #ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import"Constants.h"
#endif |
还要注意,constants.h和constants.m文件中除了接受的答案中描述的内容之外,不应包含任何其他内容。(没有接口或实现)。
- 我这样做了,但是一些文件在编译时抛出了错误"使用未声明的标识符‘constantsname’,如果我在抛出错误的文件中包含常量.h,它会工作,但这不是我想做的。我已经清理、关闭Xcode和构建,但仍然存在问题…有什么想法吗?
我通常使用巴里·沃克和拉胡尔·古普塔发布的方式。
不过,我不喜欢在.h和.m文件中重复相同的单词。注意,在下面的示例中,两个文件中的行几乎相同:
1 2 3 4 5
| // file.h
extern NSString* const MyConst;
//file.m
NSString* const MyConst = @"Lorem ipsum"; |
因此,我喜欢使用一些C预处理器机器。让我通过这个例子来解释。
我有一个头文件,它定义了宏STR_CONST(name, value):
1 2 3 4 5 6
| // 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对中,我执行以下操作:
1 2 3 4 5 6 7 8 9
| // 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 synthesis consts放在import"myfile.h"之前,它会执行nsstring*…在.h和.m中(使用助手视图和预处理器进行检查)。它会引发重新定义错误。如果我把它放在import"myfile.h"之后,它会执行extern nsstring*…在两个文件中。然后它抛出"未定义的符号"错误。
我自己也有一个头,专门声明用于如下首选项的常量nsstring:
1 2 3 4
| extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying; |
然后在附带的.m文件中声明它们:
1 2 3 4
| 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"。
稍微修改@krizz的建议,以便在pch中包含常量头文件时能正常工作,这是很正常的。由于原始文件被导入PCH,所以它不会将其重新加载到.m文件中,因此您不会得到任何符号,链接器也不高兴。
但是,下面的修改允许它工作。这有点复杂,但很有效。
你需要3个文件,有固定定义的.h文件,.h文件和.m文件,我将分别使用ConstantList.h、Constants.h和Constants.m。Constants.h的内容很简单:
1 2 3
| // Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include"ConstantList.h" |
Constants.m文件看起来像:
1 2 3 4 5 6
| // Constants.m
#ifdef STR_CONST
#undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include"ConstantList.h" |
最后,ConstantList.h文件中包含实际声明,即:
1 2 3
| // ConstantList.h
STR_CONST(kMyConstant,"Value");
… |
需要注意的几点:
我必须在#undef之后重新定义.m文件中的宏,以便使用宏。
我还必须使用#include而不是#import,这样才能正常工作,避免编译器看到以前预编译的值。
这将需要在任何值发生更改时重新编译PCH(可能是整个项目),而如果这些值按正常方式分离(和复制),则情况并非如此。
希望这对某人有帮助。
- 使用包括为我解决这个头痛。
- 与接受的答案相比,这是否有任何性能/内存损失?
- 与接受的答案相比,在回答性能时,没有任何答案。从编译器的角度来看,它实际上是完全相同的。最终得到相同的声明。如果你把上面的extern换成FOUNDATION_EXPORT,它们就完全一样了。
1 2 3 4 5
| // Prefs.h
extern NSString * const RAHUL;
// Prefs.m
NSString * const RAHUL = @"rahul"; |
正如阿比泽所说,你可以把它放进PCH文件。另一种不太脏的方法是为所有密钥创建一个include文件,然后将其包含在您使用的密钥所在的文件中,或者将其包含在PCH中。在它们自己的include文件中,至少有一个地方可以查找和定义所有这些常量。
如果您需要类似于全局常量的东西,一种快速而肮脏的方法是将常量声明放入pch文件中。
- 编辑.pch通常不是最好的主意。您必须找到一个地方来实际定义变量,几乎总是一个.m文件,所以在匹配的.h文件中声明它更有意义。如果您在整个项目中都需要常量,那么创建一个constants.h/m对是一个很好的答案。我通常将常量放在尽可能低的层次结构中,这取决于它们的使用位置。
如果您喜欢名称空间常量,可以利用struct,Friday Q&A 2011-08-19:名称空间常量和函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // 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"
}; |
- 太棒了!但在ARC下,需要在结构声明中的所有变量前面加上__unsafe_unretained限定符,以使其正常工作。
尝试使用类方法:
1 2 3 4
| +(NSString*)theMainTitle
{
return @"Hello World";
} |
我有时会用它。
- 类方法不是常量。它在运行时是有成本的,并且可能不会总是返回相同的对象(如果您以这种方式实现它,它会返回,但您不一定以这种方式实现它),这意味着您必须使用isEqualToString:进行比较,这是运行时的进一步成本。当你想要常数时,做常数。
- @彼得·霍西,虽然你的评论是正确的,但我们认为在Ruby这样的"高级"语言中,每个loc的性能都会受到一次或更多的影响,而不必担心。我不是说你不对,而是在评论不同"世界"的标准是如何不同的。
- 真正的红宝石。对于典型的应用程序来说,人们编写的大多数性能代码都是不必要的。
我使用一个单例类,这样我就可以模拟类,并在测试需要时更改常量。常量类如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #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]都省去了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #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 |
如果您想从目标C中调用类似于此NSString.newLine;的东西,并且希望它是静态常量,那么可以使用swift创建类似于此的东西:
1 2 3 4
| public extension NSString {
@objc public static let newLine ="
"
} |
而且您有很好的可读常量定义,并且可以从您选择的类型中获得,而Stile则绑定到类型的上下文中。