在Objective-C中定义常量

16

我想在Objective-C中定义一个常量。

之前我有以下函数:

+(NSString *) getDocumentsDir {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
    NSString *documentsDir = [paths objectAtIndex: 0];
    paths = nil;
    return documentsDir;
}

我想定义一个常量"Documents_Dir",只需要在函数被调用时定义一次,并在此后访问以前创建的值。

我尝试了以下代码,但没有成功:

#define getDocumentsDir \
{   \
#ifdef Documents_Dir    \
return Documents_Dir;   \
#else   \
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);  \
NSString *documentsDir = [paths objectAtIndex: 0];  \
#define Documents_Dir [paths objectAtIndex: 0]; \
paths = nil;    \
return Documents_Dir;   \
#endif  \
}   \

我对编译预处理指令不太熟悉,所以希望能得到帮助。

3个回答

34

前言:了解预编译指令和真正的常量之间的区别是值得的。 #define只是在编译器构建代码之前执行文本替换。这对于数值常量和typedef非常有效,但不总是最适合函数或方法调用。我假设您确实需要一个真正的常量,这意味着创建搜索路径的代码只应执行一次。


在您的 MyClass.m 文件中,定义变量并在+initialize方法中填充它,如下所示:

static NSArray *documentsDir;

@implementation MyClass

+ (void) initialize {
    if (documentsDir == nil) {
        documentsDir = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES) lastObject] retain];
    }
}

...

@end
static修饰符使其仅在声明它的编译单元内可见。对于一个简单的常量,这就足够了。
如果该类有子类,则将默认调用+initialize每个子类一次,因此您需要在分配给documentsDir之前检查它是否为nil,以避免泄漏内存。 (或者,正如Peter Lewis指出的那样,您可以使用== -isMemberOfClass:方法检查当前正在初始化的类是否为MyClass。)如果子类还需要直接访问常量,则需要在 MyClass.h 文件中预先声明变量为extern(子类包括该文件)。
extern NSArray *documentsDir;

@interface MyClass : NSObject
...
@end

如果您将变量预定义为 extern,则必须从定义中static关键字移除以避免编译错误。这是必要的,以便该变量可以跨多个编译单元。

注意:在Objective-C代码中,更好的声明方法是将某些内容声明为extern,并使用<objc/objc-api.h>中声明的OBJC_EXPORT(一个#define),它是基于您是否正在使用C++设置的。只需用OBJC_EXPORT替换extern即可完成。


编辑:我刚刚碰巧看到了相关的Stack Overflow问题


这是因为该符号在多个文件中被导入。由于涉及的文件被包含在许多其他文件中,请使用我的子类指导 - 在头文件中将变量声明为extern,然后选择一个地方(在.m文件中)将其声明为static,并选择一个(可能不同的)地方进行初始化。如果您已经这样做了,但错误仍然存在,请在变量声明前加上__attribute__((unused))以告诉编译器抑制有关未使用符号的警告。(我个人使用#define UNUSED attribute((unused))作为此操作的速记。) - Quinn Taylor
不幸的是,这对我没有起作用。我在头文件中添加了“OBJC_EXPORT NSString *Documents_Dir;”,并在实现文件中添加了“static NSString *Documents_Dir;”。尽管如此,我仍然收到一个错误 - “静态声明‘Documents_Dir’跟随非静态声明”。我做错了什么? - Ilya Suzdalnitski
1
我应该表述得更清楚。如果你将变量声明为extern,则需要从定义中删除static,因为你不再希望它仅属于单个编译单元。我修改了我的答案以使其更加明确。 - Quinn Taylor
1
实际上,应该是OBJC_EXTERN。OBJC_EXPORT与创建库有关。 - Ryan H.
不需要检查 if (documentsDir == nil),因为 initialize 只会被调用一次。 - skywinder
显示剩余3条评论

12

最简单的解决方案是将路径更改为静态变量并仅计算一次,像这样:

+(NSString *) getDocumentsDir {
    static NSString *documentsDir = nil;
    if ( !documentsDir ) {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
        documentsDir = [paths objectAtIndex: 0];
    }
    return documentsDir;
}

"static"告诉编译器,documentsDir实际上是一个全局变量,尽管只能在函数内部访问。因此它被初始化为nil,第一次调用getDocumentsDir将对其进行评估,然后进一步调用将返回预先评估的值。


+1 很好的想法,我忘记了你可以在函数或方法内声明静态变量!这确实是最简单的方法,但请记住,没有其他编译单元(子类或其他)将能够引用该符号。如果您只需要在一个文件中使用常量,请尝试此方法。 - Quinn Taylor
1
这正是我所需要的,能够提供一个不变的数字数组而无需一次又一次地实例化它。如果你想在类方法中使用documentsDir,请将“static NSString *documentsDir = nil;”放在类方法之外。 - Elise van Looij

1

关于Peter N Lewis代码的一点优化:

-(NSString *) documentsDir {
    static NSString *documentsDir = nil;
    return documentsDir ?: (documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]);
}

这实际上是一种优化吗?我的意思是,这样做会更快吗?(在我看来似乎完全相同。)你确定这不只是减少了字符吗?如果是后者,我反对这种方法的可读性原则。在我看来,这样做只会让人感到困惑,没有必要。 - livingtech
不是更快,而是更安全:我用“lastObject”替换了“objectAtIndex:0”。 - Cœur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接