取得したいインスタンス変数を文字列で指定する : Objective-C プログラミング

PROGRAM


取得したいインスタンス変数を文字列で指定する

Objective-C では、インスタンス変数名を文字列を使って指定して、その値を取得することができます。

たとえば、クラスの中にインスタンス変数として、次のようなものが定義されていたとします。これの末尾の数字をたとえば変数 index で受け取って、それを使って取得するインスタンス変数を切り替える方法を考えます。

// 例えば、このようなインスタンス変数が定義されているとします。

@interface TEST : UIViewController

{

IBOutlet UILabel* label1;

IBOutlet UILabel* label2;

IBOutlet UILabel* label3;

IBOutlet UILabel* label4;

IBOutlet UILabel* label5;

}

このようなとき、いちばん簡単な方法は KVC を使ってアクセスする方法です。通常は KVC を使うのが簡単ですが、そのほかにも Objective-C Runtime の機能を使用してアクセスする方法もあるので、ここではその 2 つについて紹介します。

KVC を使用してインスタンス変数の値を取得する

Objective-C では KVC の -valueForKey: というメソッドを使って、文字列変数で指定したインスタンス変数を簡単に取得できます。

// 変数名を文字列で組み立てます。ここの index には ラベル番号が指定されているものとします。

NSString *attributeName = [[NSString alloc] initWithFormat:@"label%d", index];

 

// 自身のインスタンス (self) から、インスタンス変数を文字列で指定して取得します。

UILabel *result = [self valueForKey:attributeName];

このようにすることで、文字列変数に変数名を自由に作って、その名前のインスタンス変数の値を取得することが簡単にできます。

このとき、インスタンス変数が int などのプリミティブ型だった場合は、NSNumber 型の値として取得されます。ただし、プリミティブ型でも char* 型などのポインターの場合は "this class is not key value coding-compliant for the key ***" というメッセージで "NSUnknownKeyException" 例外エラーが発生します。

Objective-C Runtime の機能を使用する

クラス内のインスタンス変数を、名前を使って取得するには objc_getInstanceVariable という関数を使用します。

そのために、まずは次のようにして、objc/runtime.h ヘッダーと stdio.h ヘッダーを組み込みます。

// objc_getInstanceVariable を使用するために objc/runtime.h を組み込みます。

#import <objc/runtime.h>

 

// 今回は snprintf 関数で C 文字列を組み立てるので、stdio.h も組み込みます。

#import <stdio.h>

そうしたら、インスタンス変数名を C 文字列で組み立てて、それを使って値を取得することができるようになります。このとき、ARC を有効化しているかでコーディングの仕方が変わってくるので、ここではその両方について記しておきます。

なお、これらの方法を使って値を取得するときは、インスタンス変数が Objective-C のオブジェクトである必要があるようです。KVC を使った方法のように、int などのプリミティブ型を取得しようとすると EXC_BAD_ACCESS エラーになるので注意してください。

 

Non-ARC 環境での取得方法

まず、ARC を有効化していない場合は object_getInstanceVariable 関数を使って、文字列からインスタンス変数を取得します。

// 取得する値は、この変数に格納します。

UILabel* result;

 

// 変数名を組み立てるバッファーを確保します。BUFFER_SIZE は必要なサイズを自分で決めておきます。

char* attributeName = (char*)malloc(BUFFER_SIZE);

 

// 変数名を C 文字列で組み立てます。ここの index には ラベル番号が指定されているものとします。

snprintf(attributeName, BUFFER_SIZE, "label%d", index);

 

// 自分のインスタンス (self) から、先ほどの文字列で指定したインスタンス変数を取得します。

object_getInstanceVariable(self, attributeName, (void**)&result);

 

// 変数名を組み立てるのに使ったバッファーは、最後に解放しておきます。

free(attributeName);

これで、変数名 result に、C 文字列で指定したインスタンス変数の値を取得することができます。

 

ARC 環境での取得方法

ARC (Automatic Reference Counting) 環境で、文字列を使ってインスタンス変数を取得したい場合は object_getInstanceVariable 関数を利用できないようになっています。

余談ですけど、Xcode 4.3.3 では実行することまではできたのですが、実行すると "C-style cast from 'UILabel *__strong *' to 'void **' casts away qualifiers" というエラーが発生していました。これは ARC でおなじみの __bridge キャスト周りのコンパイルエラーですけど、これがどうやら object_getInstanceVariable の内部的な都合で発生している様子です。

 

ARC 環境で文字列からインスタンス変数の値を取得したい場合は、次のように object_getIvar 関数を使って取得する必要があります。

// 取得する値は、この変数に格納します。

UILabel* result;

 

// 変数名を組み立てるバッファーを確保します。BUFFER_SIZE は必要なサイズを自分で決めておきます。

char* attributeName = (char*)malloc(BUFFER_SIZE);

 

// 変数名を C 文字列で組み立てます。ここの index には ラベル番号が指定されているものとします。

snprintf(attributeName, BUFFER_SIZE, "label%d", index);

 

// 自分のインスタンス (self) から、先ほどの文字列で指定したインスタンス変数を取得します。

Ivar ivar = class_getInstanceVariable(object_getClass(self), attributeName);

 

if (ivar)

{

result = (UILabel*)object_getIvar(self, ivar);

}

else

{

result = nil;

}

 

// 変数名を組み立てるのに使ったバッファーは、最後に解放しておきます。

free(attributeName);

このように、class_getInstanceVariable 関数を使ってインスタンス変数の情報を取得して、そこから object_getIvar 関数を使って、インスタンス変数の値を取得することができます。

[ もどる ]