Cocoa / Objective-C中的全局变量?

时间:2021-08-06 05:38:35

According to Cocoa Programming for Mac OS X, 3rd Edition, on page 202 (chapter 13):

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

You will be registering, reading, and setting defaults in several classes in your application. To make sure that you always use the same name, you should declare those strings in a single file and then simply #import that file into any file in which you use the names. There are several ways to do this. For example, you could use the C preprocessor’s #define command, but most Cocoa programmers use global variables for this purpose.

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

Is this really the correct best practice? Global variables? That seems insane to me – counter to everything I’ve ever been taught.

这真的是正确的最佳做法吗?全局变量?这对我来说似乎很疯狂 - 反对我曾经教过的一切。

Would a better design be a simple Singleton class with these defined? Or is it really the correct best practice to go global? Is there a better pattern than either, given that many people consider Singletons to be globals in a pretty dress?

更好的设计是一个简单的Singleton类,这些定义了吗?或者走向全球是否真的是最佳做法?考虑到很多人认为单身人士穿着漂亮的连衣裙是全球性的吗?

7 个解决方案

#1


18  

Global variables or a singleton will accomplish the same thing here. Both can be used to turn 'key' names in Cocoa that won't throw a compiler error if it's misspelled into a compiler error. That's the main purpose. Global variables are a bit easier though seeing as it requires less typing.

全局变量或单例将在这里完成相同的事情。两者都可以用来在Cocoa中转换'key'名称,如果拼写错误导致编译器错误,则不会引发编译器错误。这是主要目的。全局变量虽然看起来更容易,因为它需要更少的输入。

Instead of doing this:

而不是这样做:

[myArray setObject:theObject forKey:MyGlobalVariableKeyName];

You'd have to do something along the lines of:

你必须做一些事情:

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

Global variables are essentially less typing for the same effect.

对于相同的效果,全局变量基本上不那么打字。

#2


63  

Just to be clear, the recommendation is to create immutable global variables instead of in-line string constants (hard to refactor and no compile-time checking) or #defines (no compile-time checking). Here's how you might do so...

为了清楚起见,建议创建不可变的全局变量而不是内联字符串常量(难以重构和无编译时检查)或#defines(无编译时检查)。这是你怎么做的......

in MyConstants.h:

在MyConstants.h中:

extern NSString * const MyStringConstant;

in MyConstants.m:

在MyConstants.m中:

NSString * const MyStringConstant = @"MyString";

then in any other .m file:

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

#import "MyConstants.h"

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

This way, you gain compile-time checking that you haven't mis-spelled a string constant, you can check for pointer equality rather than string equality[1] in comparing your constants, and debugging is easier, since the constants have a run-time string value.

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

[1] In this use, you are essentially using the pointer values as the constants. It just so happens that those particular integers also point to strings that can be used in the debugger

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

#3


18  

Calling it a global variable is technically correct but misleading.

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

It is a global constant -- global in scope but constant and therefore not bad in the sense that global variables are bad.

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

To show how global constants are common, safe and numerous, consider these examples of global constants:

为了说明全局常量如何通用,安全和众多,请考虑以下全局常量示例:

  • Every class in your program
  • 你的课程中的每个班级
  • Every #define
  • 每个#define
  • Every enum
  • 每个枚举
  • Almost every name declared by Cocoa (excluding rare global variables like NSApp).
  • 几乎每个Cocoa声明的名称(不包括罕见的全局变量,如NSApp)。

The only time you should worry about global constants is when their names are too generic (they may pollute the global namespace). So don't use names that are likely to conflict with anything (always use a prefix and always make the name task-specific like NSKeyValueObservingOptionNew).

你应该担心全局常量的唯一时间是它们的名称过于通用(它们可能会污染全局命名空间)。所以不要使用可能与任何东西冲突的名称(总是使用前缀并始终使名称特定于任务,如NSKeyValueObservingOptionNew)。

#4


3  

Constant globals that are set at compile time and never change are acceptable to me. If you hard code a string, it's the same thing, just hidden by the compiler. I'll avoid mutable globals like the plague.

在编译时设置但永不改变的常量全局变量对我来说是可以接受的。如果您对字符串进行硬编码,那么它就是相同的东西,只是被编译器隐藏了。我会避免像瘟疫这样的可变全局变量。

Remember, Apple itself uses the same technique. Many of the constants I expected to be defines are actually constants. You'll get link errors if the headers are reachable but the framework is not.

请记住,Apple本身使用相同的技术。我期望定义的许多常量实际上是常量。如果标题可访问但框架不可用,则会出现链接错误。

#5


1  

building on @Barry Wark's and @Matt Gallagher's excellent answers, and my initial response (see end of this answer) there is a third approach, and that is to use a macro/include combination that ensures you only type the variable name once, and therefore it is included in both the .h and .m files simultaneously.

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

< EDIT >

<编辑>

"there is always another way..."

“总有另一种方式......”

After thinking about how to make it even simpler, without involving an additional header file, here is a more concise approach using nested macros.

在考虑如何使其更简单,而不涉及额外的头文件之后,这是使用嵌套宏的更简洁的方法。

in .h file

在.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);

in .m file

在.m文件中

myKeyDefineKeys(defineKeysIn_m_File);

implementation note

实施说明

You can use this more than once in multiple headers, however you need to change the name of "myKeyDefineKeys" to be unique, I suggest giving it the same prefix as the keys you are defining - for the sake of an example I have used "myKey" throughout.

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

In another file I might use "myOtherKeyDefineKeys".

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

Also don't mess with the defineKeysIn_h_File and defineKeysIn_m_File macros or you will get a warning the definition has changed.

另外,不要乱用defineKeysIn_h_File和defineKeysIn_m_File宏,否则您将收到定义已更改的警告。

< END EDIT >

ORIGINAL ANSWER, STILL VALID, BUT WITHOUT REFINEMENTS

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

First, make a vanilla.h file and remove the default #ifdef etc,and enter your keys as below: (This is a cut and paste from a category I wrote to extend AVAudioPlayer)

首先,创建一个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);

Then in your normal.h file (where your @interface, @protocol etc is declared) place these 3 lines (substituting your header file of course)

然后在你的normal.h文件中(声明你的@interface,@ prototocol等)放置这3行(当然替换你的头文件)

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

finally in your .m file, that is paired with your "@interface .h" file, place these 3 lines:

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

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

note the "#include" and not "#import" - we actually do want to include this file more than once.

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

this will do all the dirty work, and ensure the keys are NSString * const.

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

the trailing ; is optional, as it's included in the macro, however i personally prefer it.

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

#6


1  

So after all. I came up with 3 files.

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

Constants.h

Constants.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"

Constants.m

Constants.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"

ConstantsDefs.h

ConstantsDefs.h

def_key(kStringConstant);
def_int(kIntConstant, 313373);
def_type(float, kFloatConstant, 313373.0f);

#7


0  

It depends on the design of your software. Suppose you have a job management software and one of your "defaults" is a list of directories in which various items can be saved.

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

For each Job you can have a storagefile member that is a singleton that load up the user preferred locations at startup.

对于每个作业,您可以拥有一个存储文件成员,该成员是在启动时加载用户首选位置的单例。

Or you could have a Storagefile member of a global variable called User Preferences. Still could be a singleton but doesn't really matter in this case.

或者,您可以拥有名为“用户首选项”的全局变量的Storagefile成员。仍然可能是单身人士,但在这种情况下并不重要。

For me complex defaults (dozens of different types of classes) should reside in their own "space" accessible to model.

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

However there may be preferences that are important to how a Job is setup so those preference need to be stored in the Job Object so when you open it in another user's application it works as intended.

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

Again it depends on your design.

这又取决于你的设计。

#1


18  

Global variables or a singleton will accomplish the same thing here. Both can be used to turn 'key' names in Cocoa that won't throw a compiler error if it's misspelled into a compiler error. That's the main purpose. Global variables are a bit easier though seeing as it requires less typing.

全局变量或单例将在这里完成相同的事情。两者都可以用来在Cocoa中转换'key'名称,如果拼写错误导致编译器错误,则不会引发编译器错误。这是主要目的。全局变量虽然看起来更容易,因为它需要更少的输入。

Instead of doing this:

而不是这样做:

[myArray setObject:theObject forKey:MyGlobalVariableKeyName];

You'd have to do something along the lines of:

你必须做一些事情:

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

Global variables are essentially less typing for the same effect.

对于相同的效果,全局变量基本上不那么打字。

#2


63  

Just to be clear, the recommendation is to create immutable global variables instead of in-line string constants (hard to refactor and no compile-time checking) or #defines (no compile-time checking). Here's how you might do so...

为了清楚起见,建议创建不可变的全局变量而不是内联字符串常量(难以重构和无编译时检查)或#defines(无编译时检查)。这是你怎么做的......

in MyConstants.h:

在MyConstants.h中:

extern NSString * const MyStringConstant;

in MyConstants.m:

在MyConstants.m中:

NSString * const MyStringConstant = @"MyString";

then in any other .m file:

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

#import "MyConstants.h"

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

This way, you gain compile-time checking that you haven't mis-spelled a string constant, you can check for pointer equality rather than string equality[1] in comparing your constants, and debugging is easier, since the constants have a run-time string value.

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

[1] In this use, you are essentially using the pointer values as the constants. It just so happens that those particular integers also point to strings that can be used in the debugger

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

#3


18  

Calling it a global variable is technically correct but misleading.

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

It is a global constant -- global in scope but constant and therefore not bad in the sense that global variables are bad.

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

To show how global constants are common, safe and numerous, consider these examples of global constants:

为了说明全局常量如何通用,安全和众多,请考虑以下全局常量示例:

  • Every class in your program
  • 你的课程中的每个班级
  • Every #define
  • 每个#define
  • Every enum
  • 每个枚举
  • Almost every name declared by Cocoa (excluding rare global variables like NSApp).
  • 几乎每个Cocoa声明的名称(不包括罕见的全局变量,如NSApp)。

The only time you should worry about global constants is when their names are too generic (they may pollute the global namespace). So don't use names that are likely to conflict with anything (always use a prefix and always make the name task-specific like NSKeyValueObservingOptionNew).

你应该担心全局常量的唯一时间是它们的名称过于通用(它们可能会污染全局命名空间)。所以不要使用可能与任何东西冲突的名称(总是使用前缀并始终使名称特定于任务,如NSKeyValueObservingOptionNew)。

#4


3  

Constant globals that are set at compile time and never change are acceptable to me. If you hard code a string, it's the same thing, just hidden by the compiler. I'll avoid mutable globals like the plague.

在编译时设置但永不改变的常量全局变量对我来说是可以接受的。如果您对字符串进行硬编码,那么它就是相同的东西,只是被编译器隐藏了。我会避免像瘟疫这样的可变全局变量。

Remember, Apple itself uses the same technique. Many of the constants I expected to be defines are actually constants. You'll get link errors if the headers are reachable but the framework is not.

请记住,Apple本身使用相同的技术。我期望定义的许多常量实际上是常量。如果标题可访问但框架不可用,则会出现链接错误。

#5


1  

building on @Barry Wark's and @Matt Gallagher's excellent answers, and my initial response (see end of this answer) there is a third approach, and that is to use a macro/include combination that ensures you only type the variable name once, and therefore it is included in both the .h and .m files simultaneously.

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

< EDIT >

<编辑>

"there is always another way..."

“总有另一种方式......”

After thinking about how to make it even simpler, without involving an additional header file, here is a more concise approach using nested macros.

在考虑如何使其更简单,而不涉及额外的头文件之后,这是使用嵌套宏的更简洁的方法。

in .h file

在.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);

in .m file

在.m文件中

myKeyDefineKeys(defineKeysIn_m_File);

implementation note

实施说明

You can use this more than once in multiple headers, however you need to change the name of "myKeyDefineKeys" to be unique, I suggest giving it the same prefix as the keys you are defining - for the sake of an example I have used "myKey" throughout.

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

In another file I might use "myOtherKeyDefineKeys".

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

Also don't mess with the defineKeysIn_h_File and defineKeysIn_m_File macros or you will get a warning the definition has changed.

另外,不要乱用defineKeysIn_h_File和defineKeysIn_m_File宏,否则您将收到定义已更改的警告。

< END EDIT >

ORIGINAL ANSWER, STILL VALID, BUT WITHOUT REFINEMENTS

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

First, make a vanilla.h file and remove the default #ifdef etc,and enter your keys as below: (This is a cut and paste from a category I wrote to extend AVAudioPlayer)

首先,创建一个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);

Then in your normal.h file (where your @interface, @protocol etc is declared) place these 3 lines (substituting your header file of course)

然后在你的normal.h文件中(声明你的@interface,@ prototocol等)放置这3行(当然替换你的头文件)

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

finally in your .m file, that is paired with your "@interface .h" file, place these 3 lines:

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

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

note the "#include" and not "#import" - we actually do want to include this file more than once.

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

this will do all the dirty work, and ensure the keys are NSString * const.

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

the trailing ; is optional, as it's included in the macro, however i personally prefer it.

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

#6


1  

So after all. I came up with 3 files.

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

Constants.h

Constants.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"

Constants.m

Constants.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"

ConstantsDefs.h

ConstantsDefs.h

def_key(kStringConstant);
def_int(kIntConstant, 313373);
def_type(float, kFloatConstant, 313373.0f);

#7


0  

It depends on the design of your software. Suppose you have a job management software and one of your "defaults" is a list of directories in which various items can be saved.

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

For each Job you can have a storagefile member that is a singleton that load up the user preferred locations at startup.

对于每个作业,您可以拥有一个存储文件成员,该成员是在启动时加载用户首选位置的单例。

Or you could have a Storagefile member of a global variable called User Preferences. Still could be a singleton but doesn't really matter in this case.

或者,您可以拥有名为“用户首选项”的全局变量的Storagefile成员。仍然可能是单身人士,但在这种情况下并不重要。

For me complex defaults (dozens of different types of classes) should reside in their own "space" accessible to model.

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

However there may be preferences that are important to how a Job is setup so those preference need to be stored in the Job Object so when you open it in another user's application it works as intended.

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

Again it depends on your design.

这又取决于你的设计。