Objective-Cのmessage forwarding

Thursday, March 22nd, 2012

selectorに続いて、関連の深いmessage forwardingについて。
あるオブジェクトから他のオブジェクトに処理の委譲をしたい場合、selectorを使うと以下のように書けます。

@interface DelegateClass : NSObject
-(void)hello;
@end

@implementation DelegateClass
-(void)hello
{
    NSLog(@"Hello");
}
@end

@interface MyClass : NSObject
@property(strong, nonatomic) DelegateClass *obj;
-(void)hello;
@end

@implementation MyClass
@synthesize obj;
-(id)init
{
    obj = [[DelegateClass alloc]init];
    return self;
}
-(void)hello
{
    if([obj respondsToSelector: @selector(hello)]) {
        [obj hello];
    }
}
@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        SEL aSelector = @selector(hello);
       
        MyClass *obj1 = [[MyClass alloc] init];
        [obj1 performSelector: aSelector];
    }
    return 0;
}

ここではMyClassのhelloメソッドの処理を、DelegateClassへ委譲しています。NSObjectのrespondsToSelectorで、そのオブジェクトがselectorに対応したメソッドを実装しているかを確認できます。
この方法だと、委譲するメソッドの数が増えるとMyClassにも同じようなことをいくつも書かなければいけないという問題があります。
ここで、message forwardingを使うと、2つのメソッド(forwardInvocationとmethodSignatureForSelector)ですべての処理を委譲できるようになります。

@interface DelegateClass : NSObject
-(void)hello;
@end

@implementation DelegateClass
-(void)hello
{
    NSLog(@"Hello");
}
@end

@interface MyClass2 : NSObject
@property(strong, nonatomic) DelegateClass *obj;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
@end

@implementation MyClass2
@synthesize obj;
-(id)init
{
    obj = [[DelegateClass alloc]init];
    return self;
}
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    if ([obj respondsToSelector: aSelector]) {
        return [obj methodSignatureForSelector: aSelector];
    }
    return [super methodSignatureForSelector: aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation;
{
    if([obj respondsToSelector: [anInvocation selector]]) {
        [anInvocation invokeWithTarget: obj];
    }
    else {
        [super forwardInvocation:anInvocation];
    }
}
@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        SEL aSelector = @selector(hello);
       
        MyClass2 *obj2 = [[MyClass2 alloc] init];
        [obj2 performSelector: aSelector];
    }
    return 0;
}

MyClass2にselectorでメッセージングした際、もしそのselectorに対応できるメソッドが無い場合、まずmethodSignatureForSelectorが呼び出されます。ここで、DelegateClassの対応するメソッド(今回はhelloメソッド)のメソッドシグネチャーを返します。次にforwardInvocationが呼び出されるので、実際にDelegateClassのオブジェクトのhelloメソッドを呼び出します。このやり方ですとDelegateClassに新しいメソッドが追加されてもMyClass2への変更はいらなくできます。