我正在开发一个Cocoa应用程序,我使用常量nsstring作为存储我的首选项的键名的方式。
我知道这是一个好主意,因为它允许在必要时轻松更改密钥。 此外,这是整个“将数据与逻辑分离”的概念。
不管怎样,有没有一种好方法让这些常量在整个应用程序中定义一次?
我相信有一种简单而聪明的方法,但现在我的类只是重新定义它们所使用的方法。
我正在开发一个Cocoa应用程序,我使用常量nsstring作为存储我的首选项的键名的方式。
我知道这是一个好主意,因为它允许在必要时轻松更改密钥。 此外,这是整个“将数据与逻辑分离”的概念。
不管怎样,有没有一种好方法让这些常量在整个应用程序中定义一次?
我相信有一种简单而聪明的方法,但现在我的类只是重新定义它们所使用的方法。
简单的方法:
// 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";
第二种方法的一个好处是,改变常量的值不会导致整个程序的重新构建。
正如Abizer所说,您可以将其放入PCH文件中。另一种不那么脏的方法是为所有键创建一个包含文件,然后将其包含在您使用键的文件中,或者将其包含在PCH中。在它们自己的包含文件中,至少给了您一个查找和定义所有这些常量的地方。
你应该创建一个像这样的头文件:
// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc.
(如果您的代码不会在混合C/ c++环境或其他平台上使用,则可以使用extern而不是FOUNDATION_EXPORT。)
您可以将此文件包含在每个使用常量的文件中,或者包含在项目的预编译头中。
在.m文件中定义这些常量:
// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";
常量。应该将M添加到应用程序/框架的目标中,以便将其链接到最终产品中。
使用字符串常量而不是#define'd常量的优点是,你可以使用指针比较(stringInstance == MyFirstConstant)来测试是否相等,这比字符串比较([stringInstance isEqualToString:MyFirstConstant])快得多(并且更容易阅读,IMO)。
还有一件事要提一下。如果你需要一个非全局常量,你应该使用static关键字。
例子
// In your *.m file
static NSString * const kNSStringConst = @"const value";
因为static关键字,这个const在文件外部是不可见的。
@QuinnTaylor的小修正:静态变量在编译单元中是可见的。通常,这是一个单独的.m文件(如本例所示),但是如果你在包含在其他地方的头文件中声明它,它会咬你一口,因为编译后你会得到链接器错误
公认的(正确的)答案是“你可以包含这个[Constants.h]文件…在项目的预编译头中。”
作为新手,在没有进一步解释的情况下,我很难做到这一点——以下是如何做到的:pch文件(这是Xcode中预编译头文件的默认名称),在#ifdef __OBJC__块中导入Constants.h。
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "Constants.h"
#endif
还要注意Constants.h和Constants.h。M文件中除了已接受的答案中描述的内容外,绝对不应该包含任何其他内容。(没有接口或实现)。
// Prefs.h
extern NSString * const RAHUL;
// Prefs.m
NSString * const RAHUL = @"rahul";
我通常使用Barry Wark和Rahul Gupta发布的方法。
虽然,我不喜欢在.h和.m文件中重复相同的单词。 注意,在下面的例子中,这一行在两个文件中几乎是相同的:
// file.h
extern NSString* const MyConst;
//file.m
NSString* const MyConst = @"Lorem ipsum";
因此,我喜欢使用一些C预处理器机制。 让我通过例子来解释。
我有一个头文件,它定义宏STR_CONST(名称,值):
// 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文件只有。
对@Krizz的建议进行了轻微修改,以便在常量头文件被包含在PCH中时正常工作,这是相当正常的。由于原始文件被导入到PCH中,它不会重新加载到.m文件中,因此您没有得到任何符号,链接器也不高兴。
但是,下面的修改允许它工作。这有点复杂,但很有效。
你需要3个文件,。h文件有常量定义,.h文件和。m文件,我将使用ConstantList.h, Constants.h和Constants。m,分别。Constants.h的内容很简单:
// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"
和常数。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");
…
有几点需要注意:
我必须在.m文件中#undefing后重新定义宏。 我还必须使用#include而不是#import来正常工作,并避免编译器看到之前预编译的值。 这将需要在任何值发生更改时重新编译PCH(可能还包括整个项目),如果正常情况下将它们分开(并复制),则不会出现这种情况。
希望这对大家有所帮助。
我使用了一个单例类,这样我就可以模拟该类并在测试时更改常量。常量类是这样的:
#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
我自己有一个头专门声明常量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";
这种方法对我很有帮助。
编辑:注意,如果字符串在多个文件中使用,则效果最好。如果只有一个文件使用它,你可以在使用字符串的。m文件中做#define kNSStringConstant @“常量NSString”。
如果你喜欢命名空间常量,你可以使用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"
};
如果你想调用nsstring。newline;从objective c,你想要它是静态常量,你可以在swift中创建这样的东西:
public extension NSString {
@objc public static let newLine = "\n"
}
这样你就有了很好的可读的常量定义,并且可以从你选择的类型中获得,同时还可以绑定到类型的上下文。