簡単に言えば、別のファイルから静的変数にアクセスすることはできないということです。これは、別の場所から関数ローカル変数を参照しようとするのとまったく同じ問題です。名前は利用できません。C では、オブジェクトの「可視性」には 3 つの段階があります*。これは「リンケージ」と呼ばれます: 外部 (グローバル)、内部 (単一の「翻訳単位」に制限されます。大まかに言えば、単一のファイル)、および "いいえ」(関数ローカル)。変数を として宣言するとstatic
、内部リンケージが与えられます。他のファイルは名前でアクセスできません。それを公開するには、何らかのアクセサ関数を作成する必要があります。
拡張された答えは、クラスレベルの変数をシミュレートするためにとにかく実行できる ObjC ランタイム ライブラリのトリックがいくつかあるため、条件付きでコンパイルできる、ある程度一般化されたテスト専用コードを作成できるということです。ただし、特に簡単ではありません。
始める前に、1 つのメソッドを個別に実装する必要があることに注意してください。リンケージの制限のため、それを回避する方法はありません。
ステップ 1、メソッドを宣言します。1 つはセットアップ用、次に のvalueForKey:
ようなアクセス用のセットです。
// ClassVariablesExposer.h
#if UNIT_TESTING
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#define ASSOC_OBJ_BY_NAME(v) objc_setAssociatedObject(self, #v, v, OBJC_ASSOCIATION_ASSIGN)
// Store POD types by wrapping their address; then the getter can access the
// up-to-date value.
#define ASSOC_BOOL_BY_NAME(b) NSValue * val = [NSValue valueWithPointer:&b];\
objc_setAssociatedObject(self, #b, val, OBJC_ASSOCIATION_RETAIN)
@interface NSObject (ClassVariablesExposer)
+ (void)associateClassVariablesByName;
+ (id)classValueForName:(char *)name;
+ (BOOL)classBOOLForName:(char *)name;
@end
#endif /* UNIT_TESTING */
これらのメソッドは意味的に、カテゴリというよりはプロトコルに似ています。最初のメソッドはすべてのサブクラスでオーバーライドする必要があります。これは、関連付ける変数が当然異なり、リンケージの問題があるためです。objc_setAssociatedObject()
変数を参照する場所への実際の呼び出しは、変数が宣言されているファイル内にある必要があります。
ただし、このメソッドをプロトコルに配置するには、クラスに追加のヘッダーが必要になります。プロトコル メソッドの実装はメイン実装ファイルに入れる必要がありますが、ARC と単体テストでは、クラスが準拠している宣言を確認する必要があるためです。プロトコル。面倒。もちろん、このNSObject
カテゴリをプロトコルに準拠させることはできますが、「不完全な実装」の警告を回避するには、とにかくスタブが必要です。このソリューションの開発中にこれらのことをそれぞれ行い、不要であると判断しました。
2 番目のセットであるアクセサーは、次のように見えるため、カテゴリ メソッドとして非常にうまく機能します。
// ClassVariablesExposer.m
#import "ClassVariablesExposer.h"
#if UNIT_TESTING
@implementation NSObject (ClassVariablesExposer)
+ (void)associateClassVariablesByName
{
// Stub to prevent warning about incomplete implementation.
}
+ (id)classValueForName:(char *)name
{
return objc_getAssociatedObject(self, name);
}
+ (BOOL)classBOOLForName:(char *)name
{
NSValue * v = [self classValueForName:name];
BOOL * vp = [v pointerValue];
return *vp;
}
@end
#endif /* UNIT_TESTING */
完全に一般的ですが、それらの使用が成功するかどうかは、上記のマクロの使用に依存します。
次に、クラスを定義し、そのセットアップ メソッドをオーバーライドして、クラス変数を取得します。
// Milliner.h
#import <Foundation/Foundation.h>
@interface Milliner : NSObject
// Just for demonstration that the BOOL storage works.
+ (void)flipWaterproof;
@end
// Milliner.m
#import "Milliner.h"
#if UNIT_TESTING
#import "ClassVariablesExposer.h"
#endif /* UNIT_TESTING */
@implementation Milliner
static NSString * featherType;
static BOOL waterproof;
+(void)initialize
{
featherType = @"chicken hawk";
waterproof = YES;
}
// Just for demonstration that the BOOL storage works.
+ (void)flipWaterproof
{
waterproof = !waterproof;
}
#if UNIT_TESTING
+ (void)associateClassVariablesByName
{
ASSOC_OBJ_BY_NAME(featherType);
ASSOC_BOOL_BY_NAME(waterproof);
}
#endif /* UNIT_TESTING */
@end
単体テスト ファイルがカテゴリのヘッダーをインポートしていることを確認してください。この機能の簡単なデモンストレーション:
#import <Foundation/Foundation.h>
#import "Milliner.h"
#import "ClassVariablesExposer.h"
#define BOOLToNSString(b) (b) ? @"YES" : @"NO"
int main(int argc, const char * argv[])
{
@autoreleasepool {
[Milliner associateClassVariablesByName];
NSString * actualFeatherType = [Milliner classValueForName:"featherType"];
NSLog(@"Assert [[Milliner featherType] isEqualToString:@\"chicken hawk\"]: %@", BOOLToNSString([actualFeatherType isEqualToString:@"chicken hawk"]));
// Since we got a pointer to the BOOL, this does track its value.
NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"]));
[Milliner flipWaterproof];
NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"]));
}
return 0;
}
プロジェクトを GitHub にアップしました: https://github.com/woolsweater/ExposingClassVariablesForTesting
もう 1 つの注意点は、アクセスできるようにする各 POD タイプにはclassIntForName:
、classCharForName:
、 などの独自のメソッドが必要になることです。
これは機能し、私は常に ObjC をいじるのを楽しんでいますが、単純に巧妙すぎるかもしれません。これらのクラス変数が 1 つまたは 2 つしかない場合、最も簡単な提案は、それらのアクセサーを条件付きでコンパイルすることです (Xcode コード スニペットを作成します)。ここでの私のコードは、1 つのクラスに多くの変数がある場合にのみ、おそらく時間と労力を節約できます。
とはいえ、もしかしたら使い道があるかもしれません。少なくとも楽しい読書だったと思います。
*単に「リンカに知られているもの」を意味します -- 関数、変数、構造体など -- ObjC または C++ の意味ではありません。