web-dev-qa-db-ja.com

Cocoa / Objective-Cのグローバル変数?

202ページのMac OS XのCocoaプログラミング、第3版、によると(13章):

アプリケーションのいくつかのクラスで、デフォルトの登録、読み取り、設定を行います。常に同じ名前を使用するようにするには、これらの文字列を1つのファイルで宣言してから、そのファイルを、名前を使用するファイルに#importするだけです。これを行うにはいくつかの方法があります。たとえば、Cプリプロセッサの#defineコマンドを使用できますが、ほとんどのCocoaプログラマはこの目的でグローバル変数を使用します。

これは本当に正しいベストプラクティスですか?グローバル変数?それは私には正気ではないようです。これまでに教えられたことのすべてに対抗しています。

これらを定義したシンプルなシングルトンクラスの方が優れたデザインでしょうか?それとも、グローバル化するのが本当に正しいベストプラクティスなのでしょうか?多くの人がシングルトンをきれいな服を着たグローバルであると考えていることを考えると、どちらよりも良いパターンはありますか?

19
John Rudy

グローバル変数またはシングルトンは、ここで同じことを達成します。どちらも、Cocoaで「キー」名を変更するために使用できます。これにより、コンパイラエラーのスペルが間違っていても、コンパイラエラーはスローされません。それが主な目的です。グローバル変数は、タイピングが少なくて済むので、少しわかりやすくなっています。

これを行う代わりに:

[myArray setObject:theObject forKey:MyGlobalVariableKeyName];

あなたは次の線に沿って何かをしなければならないでしょう:

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

グローバル変数は、基本的に同じ効果のために型付けが少なくなります。

18
Grant Limberg

明確にするために、インライン文字列定数(リファクタリングが難しく、コンパイル時のチェックがない)または#defines(コンパイル時のチェックがない)ではなく、immutableグローバル変数を作成することをお勧めします。これがあなたがそうするかもしれない方法です...

myConstants.h内:

extern NSString * const MyStringConstant;

myConstants.m内:

NSString * const MyStringConstant = @"MyString";

その後、他の.mファイルで:

#import "MyConstants.h"

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

このようにして、文字列定数のスペルを間違えていないことをコンパイル時にチェックし、定数を比較するときに文字列の等価性ではなくポインタの等価性をチェックできます。定数には実行があるため、デバッグがより簡単になります。 -time文字列値。

[1]この使用法では、基本的にポインター値を定数として使用しています。たまたまそうした特定の整数は、デバッガで使用できる文字列も指している

63
Barry Wark

それをグローバル変数と呼ぶことは技術的には正しいですが誤解を招きます。

これはグローバル定数です-スコープはグローバルですが定数なので、グローバル変数が悪いという意味では悪くありません。

グローバル定数がどのように一般的、安全、かつ多数であるかを示すために、グローバル定数の次の例を検討してください:

  • プログラムのすべてのクラス
  • #define
  • すべての列挙型
  • Cocoaによって宣言されたほとんどすべての名前(NSAppなどのまれなグローバル変数を除く)。

グローバル定数について心配する必要があるのは、名前が一般的すぎる場合のみです(グローバル名前空間を汚染する可能性があります)。したがって、何かと競合する可能性のある名前は使用しないでください(常にプレフィックスを使用し、常にNSKeyValueObservingOptionNewのように名前をタスク固有にします)。

18
Matt Gallagher

コンパイル時に設定され、変更されない定数グローバルは、私には受け入れられます。文字列をハードコーディングする場合も同じで、コンパイラーによって隠されます。ペストのような可変グローバルは避けます。

Apple自体も同じ手法を使用しています。定義すると予想される定数の多くは実際には定数です。ヘッダーに到達可能でフレームワークに到達できない場合、リンクエラーが発生します。

3
user28709

@Barry Warkと@Matt Gallagherの優れた回答と私の最初の回答(この回答の最後を参照)に基づいて構築すると、3番目のアプローチがあります。これは、変数名を一度だけ入力することを保証するマクロ/インクルードの組み合わせを使用することです。したがって、.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

「#import」ではなく「#include」に注意してください。実際には、このファイルを複数回インクルードしたいのです。

これはすべてのダーティーな作業を行い、キーがNSString * constであることを確認します。

末尾;マクロに含まれているのでオプションですが、個人的には好みです。

1
unsynchronized

結局のところ。私は3つのファイルを思いつきました。

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"

定数.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

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

それはソフトウェアの設計に依存します。ジョブ管理ソフトウェアがあり、「デフォルト」の1つが、さまざまなアイテムを保存できるディレクトリのリストであるとします。

ジョブごとに、起動時にユーザーが希望する場所をロードするシングルトンであるstoragefileメンバーを持つことができます。

または、ユーザー設定と呼ばれるグローバル変数のStoragefileメンバーを持つこともできます。シングルトンでもかまいませんが、この場合は問題になりません。

私にとって、複雑なデフォルト(数十の異なるタイプのクラス)は、モデルがアクセスできる独自の「スペース」に常駐する必要があります。

ただし、ジョブの設定方法に重要な設定がある可能性があるため、それらの設定をジョブオブジェクトに保存して、別のユーザーのアプリケーションで開いたときに意図したとおりに機能する必要があります。

再びそれはあなたのデザインに依存します。

0
RS Conley