19

根据Cocoa Programming for Mac OS X,第 3 版,第 202 页(第 13 章):

您将在应用程序的几个类中注册、阅读和设置默认值。为确保您始终使用相同的名称,您应该在单个文件中声明这些字符串,然后只需将该文件#import 到您使用这些名称的任何文件中。有几种方法可以做到这一点。例如,您可以使用 C 预处理器的 #define 命令,但大多数 Cocoa 程序员为此使用全局变量。

这真的是正确的最佳做法吗?全局变量?这对我来说似乎很疯狂——与我所学过的一切背道而驰。

更好的设计是定义了这些的简单单例类吗?还是走向全球真的是正确的最佳实践?考虑到许多人认为单身人士是穿着漂亮衣服的全球人,还有比这更好的模式吗?

4

7 回答 7

63

需要明确的是,建议创建不可变的全局变量,而不是内联字符串常量(难以重构且无编译时检查)或#defines(无编译时检查)。以下是您可以这样做的方法...

在 MyConstants.h 中:

extern NSString * const MyStringConstant;

在 MyConstants.m 中:

NSString * const MyStringConstant = @"MyString";

然后在任何其他 .m 文件中:

#import "MyConstants.h"

...
[someObject someMethodTakingAString:MyStringConstant];
...

这样,您可以在编译时检查您没有拼错字符串常量,您可以在比较常量时检查指针相等而不是字符串相等 [1],并且调试更容易,因为常量有运行-时间字符串值。

[1] 在这种用法中,您本质上是使用指针值作为常量。碰巧那些特定的整数也指向可以在调试器中使用的字符串

于 2008-12-03T23:59:05.857 回答
18

全局变量或单例将在这里完成同样的事情。两者都可用于将 Cocoa 中的“关键”名称转换为编译器错误时不会引发编译器错误。这是主要目的。全局变量虽然需要更少的输入,但更容易一些。

而不是这样做:

[myArray setObject:theObject forKey:MyGlobalVariableKeyName];

您必须按照以下方式做一些事情:

[myArray setObject:theObject 
            forKey:[[MySingletonVariableClass getInstance] myVariableKeyName];

对于相同的效果,全局变量本质上是更少的输入。

于 2008-12-03T18:26:15.170 回答
18

将其称为全局变量在技术上是正确的,但具有误导性。

它是一个全局常量——在范围内是全局的,但它是常量,因此从全局变量不好的意义上来说,它并不坏。

为了展示全局常量是如何常见、安全​​和众多的,请考虑以下全局常量示例:

  • 课程中的每一堂课
  • 每个#define
  • 每个枚举
  • Cocoa 声明的几乎所有名称(不包括罕见的全局变量,如NSApp)。

唯一需要担心全局常量的时候是它们的名称过于通用(它们可能会污染全局命名空间)。因此,不要使用可能与任何内容冲突的名称(始终使用前缀并始终使名称特定于任务,例如NSKeyValueObservingOptionNew)。

于 2008-12-03T23:37:20.830 回答
3

我可以接受在编译时设置且永不更改的常量全局变量。如果你硬编码一个字符串,它是一样的,只是被编译器隐藏了。我会避免像瘟疫这样的可变全局变量。

请记住,Apple 本身也使用相同的技术。我期望定义的许多常量实际上是常量。如果标头可访问但框架不可访问,您将收到链接错误。

于 2008-12-03T18:37:20.420 回答
1

建立在@Barry Wark 和@Matt Gallagher 的优秀答案的基础上,我的初步回应(见本答案的结尾)还有第三种方法,那就是使用宏/包含组合,确保您只键入一次变量名,并且因此它同时包含在 .h 和 .m 文件中。

<编辑>

“总有别的办法……”

在考虑了如何使它更简单,而不涉及额外的头文件之后,这里有一个使用嵌套宏的更简洁的方法。

在 .h 文件中

#define defineKeysIn_h_File(key)   extern NSString * const key; 
#define defineKeysIn_m_File(key)   NSString * const key = @#key; 


#define myKeyDefineKeys(defineKey) \
/**start of key list*/\
defineKey(myKeyABC);\
defineKey(myKeyXYZ);\
defineKey(myKey123);\
/*end of key list*/

myKeyDefineKeys(defineKeysIn_h_File);

在 .m 文件中

myKeyDefineKeys(defineKeysIn_m_File);

实施说明

您可以在多个标题中多次使用它,但是您需要将“myKeyDefineKeys”的名称更改为唯一的,我建议给它与您定义的键相同的前缀 - 为了我使用的示例“ myKey”贯穿始终。

在另一个文件中,我可能会使用“myOtherKeyDefineKeys”。

也不要乱用defineKeysIn_h_File 和defineKeysIn_m_File 宏,否则你会收到定义已更改的警告。

<结束编辑>

原始答案,仍然有效,但没有改进

首先,创建一个 vanilla.h 文件并删除默认的#ifdef 等,然后输入您的密钥,如下所示:(这是我为扩展 AVAudioPlayer 编写的类别的剪切和粘贴)

//  playFromConsts.h


define_key(AVAudioPlayer_key_player);
define_key(AVAudioPlayer_key_duration);
define_key(AVAudioPlayer_key_filename);
define_key(AVAudioPlayer_key_filepath);
define_key(AVAudioPlayer_key_fileurl);
define_key(AVAudioPlayer_key_urlString);
define_key(AVAudioPlayer_key_envelope);
define_key(AVAudioPlayer_key_startDate);
define_key(AVAudioPlayer_key_linkToPlayer);
define_key(AVAudioPlayer_key_linkFromPlayer);
define_key(AVAudioPlayer_key_linkToPlayerEnvelope);
define_key(AVAudioPlayer_key_linkFromPlayerEnvelope);
define_key(AVAudioPlayer_key_deviceStartTime);
define_key(AVAudioPlayer_key_currentVolume);
define_key(AVAudioPlayer_key_fadeFromVolume);
define_key(AVAudioPlayer_key_fadeToVolume);
define_key(AVAudioPlayer_key_fadeTime);
define_key(AVAudioPlayer_key_segueTime);

然后在你的 normal.h 文件(你的@interface、@protocol 等被声明的地方)放置这 3 行(当然替换你的头文件)

#define define_key(x) extern NSString * const x; 
#include "playFromConsts.h"
#undef define_key

最后在与“@interface .h”文件配对的 .m 文件中,放置以下 3 行:

#define define_key(x) NSString * const x = @#x; 
#include "playFromConsts.h"
#undef define_key

注意“#include”而不是“#import”——我们实际上确实想多次包含这个文件。

这将完成所有肮脏的工作,并确保键是 NSString * const。

尾随; 是可选的,因为它包含在宏中,但我个人更喜欢它。

于 2011-11-21T08:39:02.777 回答
1

所以毕竟。我想出了3个文件。

常量.h

#define def_key(name) extern NSString *const name
#define def_int(name, value) extern int const name
#define def_type(type, name, value) extern type const name

#include "ConstantsDefs.h"

常数.m

#import "Constants.h"

#undef def_key 
#define def_key(name) NSString *const name = @#name

#undef def_int
#define def_int(name, value) int const name = value

#undef def_type
#define def_type(type, name, value) type const name = value

#include "ConstantsDefs.h"

常量定义文件.h

def_key(kStringConstant);
def_int(kIntConstant, 313373);
def_type(float, kFloatConstant, 313373.0f);
于 2012-02-11T09:04:16.213 回答
0

这取决于您的软件设计。假设您有一个作业管理软件,并且您的“默认值”之一是可以保存各种项目的目录列表。

对于每个作业,您可以有一个 storagefile 成员,它是一个单例,在启动时加载用户首选位置。

或者,您可以拥有一个名为 User Preferences 的全局变量的 Storagefile 成员。仍然可以是单例,但在这种情况下并不重要。

对我来说,复杂的默认值(数十种不同类型的类)应该驻留在它们自己的模型可访问的“空间”中。

但是,可能有一些首选项对如何设置作业很重要,因此这些首选项需要存储在作业对象中,这样当您在另一个用户的应用程序中打开它时,它就会按预期工作。

同样,这取决于您的设计。

于 2008-12-03T18:51:01.970 回答