2011/02/06

メソッドforwardInvocation:を利用したメッセージ転送について

Objective-Cでは、定義していないメッセージをオブジェクトへ送信すると、通常は未定義なセレクタとして例外NSInvalidArgumentExceptionが発生します。しかし、クラスにメソッドforwardInvocation:を定義しておくと、エラー報告前にこのメソッドが呼ばれるようになります。そのため、メソッドforwardInvocation:の実装次第で、メッセージがなかったときのデフォルトの挙動を定義したり、エラー回避などを行えるようになります。

この記事では、forwardInvocation:を利用したメッセージ転送や多重継承について述べます。なお、詳細については、Appleの「Objective-C Runtime Programming Guide」の「Message Forwarding」の章や、サンプルコード「ForwardInvocation」を参照してください。

メッセージ転送の例

メソッドforwardInvocation:は名前の通りメッセージを他のオブジェクトに転送するのが一般的な使いかたです。例えば次のようにメソッドが複数あるオブジェクトのために転送メソッドを複数書く必要がある場合には、forwardInvocation:を使うとよいでしょう。

@interface Base : NSObject { }
- (void)foo;
- (void)bar;
- (void)baz;
@end

@implementation Base
- (void)foo {
    NSLog(@"%@: Foo", self);
}
- (void)bar {
    NSLog(@"%@: Bar", self);
}
- (void)baz {
    NSLog(@"%@: Baz", self);
}
@end

転送のためのクラスは次のように定義します。なお、methodSignatureForSelector:を定義しているのは、これがない場合とforwardInvocation:が呼ばれないからです。また、メッセージに対処できないときは通常はsuperのforwardInvocation:を呼び出すとよいみたいです。

@interface ForwardObject : NSObject {
    id _target;
}
@end

@implementation ForwardObject
- (id)initWithTarget:(id)target {
    if (self = [super init]) {
        _target = target;
    }
    return self;
}
- (void)forwardInvocation:(NSInvocation*)inv {
    if ([_target respondsToSelector:[inv selector]]) {
        [inv invokeWithTarget:_target];
    } else {
        [super doesNotRecognizeSelector:[inv selector]];
    }
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
    return [_target methodSignatureForSelector:sel];
}
@end

上記のクラスForwardObjectを用いて次のように呼び出すことにします。

Base* base = [[Base alloc] init];
[base foo];

ForwardObject* obj = [[ForwardObject alloc] initWithTarget:base];
[obj foo];
[obj bar];

その結果は次のようになります。ForwordObjectが持っていないメソッドが呼び出せていることがわかります。

<Base: 0x4e17d90>: Foo
<Base: 0x4e17d90>: Foo
<Base: 0x4e17d90>: Bar

また、次のようにNSTimerを利用することで、ターゲットへのすべてのメッセージを5秒遅延させることができます (意味があるかどうかわかりませんが)。

@interface LazyObject : ForwardObject {}
@end

@implementation LazyObject
- (void)forwardInvocation:(NSInvocation*)inv {
    if ([_target respondsToSelector:[inv selector]]) {
        [inv setTarget:_target];
        [NSTimer scheduledTimerWithTimeInterval:5 invocation:inv repeats:NO];
    } else {
        [super doesNotRecognizeSelector:[inv selector]];
    }
}
@end

多重継承

先ほどの例とほとんどコードは変わりませんが、メソッドforwardInvocation:を利用すると多重継承が実現できます。C++の多重継承と違うのは、ダイヤモンド継承などで同名のメソッドがある場合には継承が優先されることです。

さらに、respondsToSelector:、isKindOfClass:、conformsToProtocol:、instancesRespondToSelector:などのメソッドを定義するとより多重継承らしく見えるようになります。

@interface Derived : Base {
    SubBase* _subBase;
}
@end

@implementation Derived
- (id)init {
    if (self = [super init]) {
        _subBase = [[SubBase alloc] init];
    }
    return self;
}
- (void)forwardInvocation:(NSInvocation*)inv {
    if ([_subBase respondsToSelector:[inv selector]]) {
        [inv invokeWithTarget:_subBase];
    } else {
        [super doesNotRecognizeSelector:[inv selector]];
    }
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
    return [_subBase methodSignatureForSelector:sel];
}
- (void)dealloc {
    [_subBase release];
    [super dealloc];
}
@end

まとめ

forwardInvocation:を利用したメッセージ転送や多重継承について述べました。

関連項目

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。