2013年11月10日日曜日

文字列を使ってクラスを生成、メソッドを呼び出してみよう

Objective-Cのクラスをインスタンス化するのに、クラス名の文字列から生成したい場合があります。

以下のようなMyClassという独自クラスがあったとします。
// MyClass.h
@interface MyClass : NSObject

- (void)printMessage:(NSString*)text;

@end

// MyClass.m
@implementation MyClass

- (id)init
{
    if ((self = [super init])) {
        NSLog(@"init: %@",self);
    }
    return self;
}

- (void)printMessage:(NSString*)text
{
    NSLog(@"Message: %@",text);
}

@end


文字列からインスタンス生成

まず、このクラス名の文字列を元に、インスタンスを生成してみます。
// 非ARCの場合
Class c = NSClassFromString(@"MyClass");
id obj = nil;
if (c)
  obj = [[[c alloc] init] autorelease];
}

// ARCの場合
Class c = NSClassFromString(@"MyClass");
id obj = nil;
if (c) {
  obj = [[c alloc] init];
}
なぜ、わざわざ非ARCとARCのコードを書いたのかは後で解説します。

文字列からメソッド呼び出し

MyClassのprintMessage:メソッドを呼び出してみます。
NSObjectクラスのperformSelectorを使用しますが、printMessage:は1つのオブジェクトを受け取るメソッドなのでperformSelector:withObject:を使用します。
// 非ARCの場合
SEL method = NSSelectorFromString(@"printMessage:");  // :を忘れずに!
if ([obj respondsToSelector:method]) {
    [obj performSelector:method withObject:@"hello!"];
}

// ARCの場合
SEL method = NSSelectorFromString(@"printMessage:");  // :を忘れずに!
if ([obj respondsToSelector:method]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [obj performSelector:method withObject:@"hello!"];
#pragma clang diagnostic pop
}

さてここで、非ARCとARCのコードに違いが出てきました。
performSelector:withObject:メソッドはid型オブジェクトを返す事ができます。
ただしprintMessage:の返り値はvoidで、何も返さないはずです。

文字列から生成したメソッド名では静的にメソッドを特定できないので、これらの事がARCプロジェクトの場合にコンパイラが理解できず、"performSelector may cause a leak because its selector is unknown"というワーニングを吐き出します。

何も値を返さないなら、このワーニングを無視してしまって構わないのですが、ワーニングを抑制したい場合は、先の一連の#pragma文を使用します。

または、ビルドセッティングのOther Warning Flagsに-Wno-arc-performSelector-leaksを設定することで、プロジェクト全体で同様の効果が得られますが、場所によってはこのワーニングがバグの発見に寄与する可能性もありますので、あまりお勧めは出来ません。
otherwarning.png

関連記事

0 件のコメント:

コメントを投稿

Related Posts Plugin for WordPress, Blogger...