Objective-Cの定数


1002

Cocoaアプリケーションを開発していNSStringて、設定のキー名を格納する方法として定数sを使用しています。

必要に応じてキーを簡単に変更できるため、これは良い考えだと理解しています。
さらに、それは全体を「ロジックからデータを分離する」という概念です。

とにかく、これらの定数をアプリケーション全体で一度定義する良い方法はありますか?

簡単でインテリジェントな方法があると確信していますが、現在のところ、私のクラスは使用するクラスを再定義しているだけです。


7
OOPは、ロジックを使用てデータグループ化することです。あなたが提案しているのは、プログラムを簡単に変更できるようにする優れたプログラミング手法です。
Raffi Khatchadourian、2011

回答:


1287

次のようなヘッダーファイルを作成する必要があります

// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc.

(コードがC / C ++混合環境または他のプラットフォームで使用されない場合は、extern代わりに使用できFOUNDATION_EXPORTます)

このファイルは、定数を使用する各ファイル、またはプロジェクトのプリコンパイル済みヘッダーに含めることができます。

これらの定数を.mファイルで次のように定義します

// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";

Constants.mをアプリケーション/フレームワークのターゲットに追加して、最終製品にリンクされるようにする必要があります。

#define'd 定数の代わりに文字列定数を使用する利点はstringInstance == MyFirstConstant、文字列比較([stringInstance isEqualToString:MyFirstConstant])よりも高速なポインター比較()を使用して等価性をテストできることです(そして、読みやすく、IMO)。


67
整数定数の場合は次のようになります。extern int const MyFirstConstant = 1;
Dan Morgan、

180
全体的に素晴らしい答えですが、1つの明白な警告があります。Objective-Cで==演算子を使用して文字列の等価性をテストするのは、メモリアドレスをテストするためです。これには常に-isEqualToString:を使用してください。MyFirstConstantと[NSString stringWithFormat:MyFirstConstant]を比較することで、簡単に別のインスタンスを取得できます。文字列のインスタンスについて、リテラルであっても仮定しないでください。(いずれの場合も、#defineは「プリプロセッサディレクティブ」であり、コンパイル前に置換されるため、コンパイラはどちらの方法でも文字列リテラルを最後に認識します。)
Quinn Taylor

74
この場合、定数シンボルとして本当に使用されている(つまり、@ "MyFirstConstant"を含む文字列の代わりにシンボルMyFirstConstantが使用されている)場合、==を使用して定数との等価性をテストしても問題ありません。この場合、文字列の代わりに整数を使用することもできますが(実際には、ポインターとして整数を使用しています)、定数文字列を使用すると、定数の値が人間が読める意味を持つため、デバッグが少し簡単になります。 。
Barry Wark、

17
「Constants.mをアプリケーション/フレームワークのターゲットに追加して、最終製品にリンクされるようにする必要があります。」の+1 私の正気を救った。@ amok、Constants.mで「情報を取得」を実行し、「ターゲット」タブを選択します。関連するターゲットがチェックされていることを確認してください。
PEZ、

73
@Barry:Cocoaでは、の代わりにでNSStringプロパティを定義する多くのクラスを見てきました。そのため、それらは定数の別のインスタンスを保持している可能性があり(そうする必要があります)、直接メモリアドレスの比較は失敗します。また、の合理的に最適化された実装では、文字比較の要点に入る前にポインタの等価性をチェックすると思います。copyretainNSString*-isEqualToString:
Ben Mosher、2011年

280

最も簡単な方法:

// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"

より良い方法:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";

2番目の利点の1つは、定数の値を変更してもプログラム全体が再構築されないことです。


12
定数の値を変更するべきではないと思いました。
ruipacheco 2009

71
Andrewは、アプリケーションの実行中ではなく、コーディング中に定数の値を変更することを言及しています。
Randall

7
実行することで付加価値はありますかextern NSString const * const MyConstant、つまり、単なる定数ポインターではなく、定数オブジェクトへの定数ポインターにするのですか?
Hari Karam Singh 2012

4
ヘッダーファイルでこの宣言を使用するとどうなりますか?static NSString * const kNSStringConst = @ "const value"; .hファイルと.mファイルで別々に宣言して初期化しないことの違いは何ですか?
karim

4
@Dogweather-コンパイラだけが答えを知っている場所。IE、アプリケーションのビルドをコンパイルするために使用されたコンパイラーをaboutメニューに含めたい場合、コンパイルされたコードが他の方法で知ることができないため、そこに配置できます。他の場所はあまり考えられません。マクロは多くの場所で使用すべきではありません。#define MY_CONST 5およびその他の場所で#define MY_CONST_2 25があった場合はどうなりますか。その結果、5_2をコンパイルしようとすると、コンパイラエラーが発生する可能性があります。定数には#defineを使用しないでください。定数にはconstを使用します。
ArtOfWarfare 2013

190

言及すべきことが1つあります。非グローバル定数が必要な場合は、staticキーワードを使用する必要があります。

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

staticキーワードがあるため、このconstはファイルの外からは見えません。


@QuinnTaylorによるマイナー修正:静的変数はコンパイルユニット内に表示されます。通常、これは(この例のように)単一の.mファイルですが、コンパイル後にリンカーエラーが発生するため、他の場所に含まれているヘッダーで宣言するとかみつく可能性があります


41
マイナー修正:静的変数はコンパイルユニット内に表示されます。通常、これは(この例のように)単一の.mファイルですが、コンパイル後にリンカーエラーが発生するため、他の場所に含まれているヘッダーで宣言するとかみつく可能性があります。
クイン・テイラー、

staticキーワードを使用しない場合、プロジェクト全体でkNSStringConstを使用できますか?
Danyal Aytekin

2
はい、チェックしました... staticをオフにすると、Xcodeは他のファイルでオートコンプリートを提供しませんが、同じ名前を2つの異なる場所に配置しようとしましたが、Quinnのリンカーエラーを再現しました。
Danyal Aytekin

1
ヘッダーファイルのstaticはリンカーの問題を引き起こしません。ただし、ヘッダーファイルを含む各コンパイルユニットは独自の静的変数を取得するため、100個の.mファイルからヘッダーを含めると、100個が取得されます。
gnasher729 2014

@kompozer .mファイルのどの部分にこれを配置しますか?
バジルブルク2014年

117

受け入れられた(そして正しい)答えは、「この[Constants.h]ファイルをプロジェクトのプリコンパイル済みヘッダーに含めることができる」と述べています。

初心者として、これ以上の説明なしでこれを行うのは困難でした-方法は次のとおりです:YourAppNameHere-Prefix.pchファイル(これはXcodeのプリコンパイル済みヘッダーのデフォルト名です)で#ifdef __OBJC__ブロック内に Constants.h インポートします。

#ifdef __OBJC__
  #import <UIKit/UIKit.h>
  #import <Foundation/Foundation.h>
  #import "Constants.h"
#endif

また、Constants.hファイルとConstants.mファイルには、承認された回答に記載されている内容以外は絶対に何も含めないでください。(インターフェースや実装はありません)。


私はこれを行いましたが、一部のファイルはコンパイル時にエラーをスローします "未宣言の識別子 'CONSTANTSNAME'を使用します。 xcodeとビルド、それでも問題...何かアイデアはありますか?
J3RM '30

50

私は通常、Barry WarkとRahul Guptaが投稿した方法を使用しています。

ただし、.hファイルと.mファイルの両方で同じ単語を繰り返すのは好きではありません。次の例では、両方のファイルで行がほとんど同じであることに注意してください。

// file.h
extern NSString* const MyConst;

//file.m
NSString* const MyConst = @"Lorem ipsum";

したがって、私がやりたいことは、いくつかのCプリプロセッサー機構を使用することです。例を通して説明しましょう。

マクロを定義するヘッダーファイルがありますSTR_CONST(name, value)

// 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ペアで、次のようにします。

// myfile.h
#import <StringConsts.h>

STR_CONST(MyConst, "Lorem Ipsum");
STR_CONST(MyOtherConst, "Hello world");

// myfile.m
#define SYNTHESIZE_CONSTS
#import "myfile.h"

et voila、私は.hファイルにのみ定数に関するすべての情報を持っています。


うーん、少し注意が必要ですが、ヘッダーファイルがプリコンパイル済みヘッダーにインポートされている場合は、この方法を使用できません。既にコンパイルされているため、.hファイルは.mファイルに読み込まれません。しかし、方法はあります-私の回答を参照してください(コメントに素敵なコードを入れることができないため)
Scott Little

これは機能しません。#define SYNTHESIZE_CONSTSを#import "myfile.h"の前に配置すると、NSString * ...が.hと.mの両方で実行されます(アシスタントビューとプリプロセッサを使用して確認されます)。再定義エラーが発生します。#import "myfile.h"の後に配置すると、両方のファイルでNSString * ...をexternします。次に、「未定義のシンボル」エラーをスローします。
ヒ素14年

28

私自身は、次のような設定に使用される定数NSStringの宣言専用のヘッダーを持っています。

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;

次に、それらを添付の.mファイルで宣言します。

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

このアプローチは私に役立ちました。

編集:文字列が複数のファイルで使用されている場合、これが最もよく機能することに注意してください。1つのファイルだけが使用する場合#define kNSStringConstant @"Constant NSString"は、文字列を使用する.mファイルで行うことができます。


25

@Krizzの提案をわずかに変更したため、定数ヘッダーファイルがPCHに含まれる場合でも正常に機能しますが、これはかなり正常です。オリジナルはPCHにインポートされるため、.mファイルに再ロードされず、シンボルが表示されず、リンカーは不満を感じます。

ただし、次の変更により機能します。少し複雑ですが、機能します。

あなたは必要があります3つのファイル、.h定数の定義を持っているファイル、.hファイルと.m私が使用します、ファイルをConstantList.hConstants.hそしてConstants.mそれぞれ。の内容Constants.hは単純です:

// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"

そしてConstants.mファイルは次のようになります:

// Constants.m
#ifdef STR_CONST
    #undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include "ConstantList.h"

最後に、ConstantList.hファイルには実際の宣言が含まれており、それがすべてです。

// ConstantList.h
STR_CONST(kMyConstant, "Value");

注意すべき点がいくつかあります。

  1. マクロを使用した、マクロを.mファイルに再定義する必要がありました。 #undef

  2. これを適切に機能させ、以前にプリコンパイルされた値がコンパイラに表示されないようにするために、#include代わりにを使用する必要もありました#import

  3. これには、値が変更されるたびにPCH(およびおそらくプロジェクト全体)の再コンパイルが必要になります。これは、通常どおりに値が分離(および複製)されている場合には当てはまりません。

それが誰かに役立つことを願っています。


1
#includeを使用すると、この問題が解決しました。
ラムセル2013

受け入れられた回答と比較した場合、これにはパフォーマンス/メモリの損失がありますか?
Gyfis、2015年

認められた回答と比較したパフォーマンスへの回答では、何もありません。コンパイラの観点から見ると、これは事実上まったく同じです。最終的には同じ宣言になります。extern上記をに置き換えた場合も、まったく同じですFOUNDATION_EXPORT
Scott Little


12

Abizerが言ったように、それをPCHファイルに入れることができます。それほど汚くないもう1つの方法は、すべてのキーのインクルードファイルを作成し、キーを使用しているファイルにそのファイルをインクルードするか、PCHにインクルードすることです。それらを独自のインクルードファイルに含めると、少なくともこれらすべての定数を検索および定義するための1つの場所が提供されます。


11

グローバル定数のようなものが必要なら; 簡単で汚い方法は、定数宣言をpchファイルに入れることです。


7
.pchを編集することは、通常、最善の方法ではありません。実際に変数を定義する場所、ほとんどの場合.mファイルを見つける必要があるため、対応する.hファイルで変数を宣言する方が理にかなっています。プロジェクト全体で必要な場合は、Constants.h / mペアを作成するという一般に受け入れられている答えが適切です。通常、定数は、使用される場所に基づいて、可能な限り階層の下に配置します。
クイン・テイラー、

8

クラスメソッドを使用してみてください。

+(NSString*)theMainTitle
{
    return @"Hello World";
}

時々使います。


6
クラスメソッドは定数ではありません。これは実行時にコストがかかり、常に同じオブジェクトを返すとは限りません(そのように実装した場合はそうなりますが、必ずしもそのように実装しているわけではありません)。つまりisEqualToString:、比較に使用する必要があります。実行時の追加コスト。定数が必要な場合は、定数を作成します。
Peter Hosey、

2
@Peter Hosey、あなたのコメントは正しいですが、Rubyのような「高レベル」言語では、LOCごとに1回以上のパフォーマンスヒットがあり、心配することはありません。あなたが正しくないと言っているのではなく、異なる「世界」で標準がどのように異なるかについてコメントしているだけです。
Dan Rosenstark、2011年

1
Rubyで真。一般的なアプリでは、人がコーディングするパフォーマンスのほとんどはまったく必要ありません。
Peter DeWeese、2011年

8

名前空間定数が必要な場合は、構造体、金曜日のQ&A 2011-08-19:名前空間定数と関数を活用できます。

// 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
すばらしいことです。ただし、ARCでは、構造体宣言のすべての変数の前に__unsafe_unretained修飾子を付ける必要があります。
Cemen 2015

7

シングルトンクラスを使用しているので、必要に応じて、クラスをモックして定数を変更できます。定数クラスは次のようになります。

#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]毎回入力する手間が省けます):

#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

1

NSString.newLine;目的cからこのようなものを呼び出し、静的定数にしたい場合は、次のようなものをすばやく作成できます。

public extension NSString {
    @objc public static let newLine = "\n"
}

また、読みやすい定数定義があり、タイプのコンテキストにバインドされている間、選択したタイプ内から使用できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.