关于cocoa:objective-c中的常量

Constants in Objective-C

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

我明白这是个好主意,因为它允许在必要时轻松更换钥匙。另外,它是整个"将数据与逻辑分离"的概念。

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


你应该创建一个头文件

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)。


最简单的方法:

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

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


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

例子

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

由于static关键字,该常量在文件外部不可见。

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


接受(并且正确)的回答说"你可以包括这个[常量.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文件中除了接受的答案中描述的内容之外,不应包含任何其他内容。(没有接口或实现)。


我通常使用巴里·沃克和拉胡尔·古普塔发布的方式。

不过,我不喜欢在.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文件中常量的所有信息。


我自己也有一个头,专门声明用于如下首选项的常量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.hConstants.hConstants.mConstants.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(可能是整个项目),而如果这些值按正常方式分离(和复制),则情况并非如此。

  • 希望这对某人有帮助。


    1
    2
    3
    4
    5
    // Prefs.h
    extern NSString * const RAHUL;

    // Prefs.m
    NSString * const RAHUL = @"rahul";

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


    如果您需要类似于全局常量的东西,一种快速而肮脏的方法是将常量声明放入pch文件中。


    如果您喜欢名称空间常量,可以利用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"
    };


    尝试使用类方法:

    1
    2
    3
    4
    +(NSString*)theMainTitle
    {
        return @"Hello World";
    }

    我有时会用它。


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

    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则绑定到类型的上下文中。