— boreal-kiss.com

-[NSObject performSelector:]の返り値

まとめ

  • -[NSObject performSelector:]の返り値として不適切なものが存在する。
  • それら不適切なものを扱う場合の対処法について。

イントロダクション

NSObjectのインスタンスメソッド

- (void)performSelector:(SEL) aSelector

はvoid型となっているが、「引数にセットされたメソッドの返り値」を自分の返り値に持つことができる。ただしその場合、-[NSObject performSelector:]の返り値はid型(オブジェクトポインタ)になるので適切にキャストしてやる必要がある。例えば、以下のようなNSStringを返すメソッドを持つTest1クラスを考える。

@implementation Test1
 
-(NSString *)stringValue{
	return @"Hello";
}
 
@end

このTest1クラスに-[NSObject performSelector:]を使って-[Test1 stringValue]を呼び、NSStringにキャストして返り値を受け取るとたしかに@”Hello”が返ってきていることがわかる。

Test1 *test1 = [[Test1 alloc] init];
NSString *stringValue = (NSString *)[test1 performSelector:@selector(stringValue)];
NSLog(@"%@", stringValue);// Hello

本題

-[NSObject performSelector:]の返り値はid型であり、id型はオブジェクトへのポインタであると説明した。つまり、-[NSObject performSelector:]は「オブジェクトではないものを返り値とするメソッド」の返り値を受け取ることができない可能性があることがわかる。例を見てみよう。Test2クラスに以下のようなメソッドがあるとする。どのメソッドの返り値もオブジェクトではない。

@implementation Test2
 
-(BOOL)boolValue{
	return YES;
}
 
-(int)intValue{
	return 10;
}
 
-(float)floatValue{
	return 2.0;
}
 
-(CGPoint)point{
	return CGPointMake(100.0, 100.0);
}
 
@end

先ほどと同様にこれらのメソッドを-[NSObject performSelector:]を使って呼び出し、返り値を確認すると以下のような結果になった。

Test2 *test2 = [[Test2 alloc] init];
 
//warning: cast from pointer to integer of different size
BOOL boolValue = (BOOL)[test2 performSelector:@selector(boolValue)];
NSLog(@"%i", boolValue);// 1
 
int intValue = (int)[test2 performSelector:@selector(intValue)];
NSLog(@"%i", intValue);// 10
 
//error: pointer value used where a floating point value was expected
float floatValue = (float)[test2 performSelector:@selector(floatValue)];
NSLog(@"%f", floatValue);
 
//error: conversion to non-scalar type requested
CGPoint point = (CGPoint)[test2 performSelector:@selector(point)];
NSLog(@"%@", NSStringFromCGPoint(point));

結果をまとめると以下のようになる。

  • 返り値BOOLについては警告が出たが返り値の受け取りができた。
  • 返り値intについては警告は出ず返り値の受け取りができた。
  • 返り値floatについてはコンパイルエラーが生じ実行できなかった。
  • 返り値CGPointについてはコンパイルエラーが生じ実行できなかった。

対処法

上記のテスト結果から、-[NSObject performSelector:]から「オブジェクトではないものを返り値とするメソッド」の返り値を受け取るためには何かしらの工夫をしなければならないことがわかった(int型・BOOL型などの例外もある)。以下に二種類の対処法を挙げておく。

  1. -[NSObject performSelector:]で呼び出すメソッドの返り値をオブジェクトにしておく。
  2. -[NSObject performSelector:]の代わりに[NSInvocation invoke]を用いる。

順に見てみよう。

対処法1

二つ目の対処法より簡単である。返り値int, BOOL, floatについてはNSNumberオブジェクトによるラッピング、返り値CGPoint(CGRect, CGSizeなども含む)についてはNSStringオブジェクトに変換することで簡単に対処できる。以下に、Test2クラスの4メソッドの返り値を全てオブジェクトに変更したTest3クラスを挙げる。

@implementation Test3
 
-(NSNumber *)boolValue{
	return [NSNumber numberWithBool:YES];
}
 
-(NSNumber *)intValue{
	return [NSNumber numberWithInt:10];
}
 
-(NSNumber *)floatValue{
	return [NSNumber numberWithFloat:2.0];
}
 
-(NSString *)point{
	return NSStringFromCGPoint(CGPointMake(100.0, 100.0));
}
 
@end

この場合、各メソッドの返り値はオブジェクトであるため、それらオブジェクトから必要な値を復元してやればよい。

Test3 *test3 = [[Test3 alloc] init];
 
BOOL boolValue = [[test3 performSelector:@selector(boolValue)] boolValue];
NSLog(@"%i", boolValue2);// 1
 
int intValue = [[test3 performSelector:@selector(intValue)] intValue];
NSLog(@"%i", intValue2);// 10
 
float floatValue = [[test3 performSelector:@selector(floatValue)] floatValue];
NSLog(@"%f", floatValue2);// 2.000000
 
CGPoint point = CGPointFromString([test3 performSelector:@selector(point)]);
NSLog(@"%@", NSStringFromCGPoint(point2));// {100, 100}

対処法2

-[NSObject performSelector:]の代わりにNSInvocationを用いると、返り値がオブジェクトであろうとなかろうと受け取ることが可能になる。例えば、-[Test2 floatValue]の返り値を受け取る場合には、以下のような手続きをとる。

Test2 *test2 = [[Test2 alloc] init];
 
float floatValue;
NSMethodSignature *aSigniture 
        = [[test2 class] instanceMethodSignatureForSelector:@selector(floatValue)];
NSInvocation *anInvocation 
        = [NSInvocation invocationWithMethodSignature:aSigniture];
[anInvocation setSelector:@selector(floatValue)];
[anInvocation setTarget:test2];
[anInvocation invoke];
[anInvocation getReturnValue:&floatValue];
NSLog(@"%f", floatValue);// 2.000000

NSInvocationを用いると汎用性は高くなるが、一般的にソースが煩雑になる。

参考

0 comments
Submit comment