|
|
Log In | Not a Member? |
Contact ADC |
| < 前ページ次ページ > |
受け取ったメッセージを処理しないオブジェクトに、メッセージを送信するとエラーになります。しかし、エラーを報告する前に、ランタイムシステムは、受信オブジェクトに対してメッセージを処理するチャンスをもう一度与えます。ランタイムシステムは、受信オブジェクトに対して、forwardInvocation:メッセージに唯一の引数としてNSInvocationオブジェクトを指定して送信します。NSInvocationオブジェクトは、オリジナルのメッセージとそのメッセージに渡された引数をカプセル化します。
forwardInvocation:メソッドを実装して、メッセージに対するデフォルトの応答を用意したり、他の方法でエラーを回避することができます。その名前が示すように、forwardInvocation:は、一般に、メッセージを別のオブジェクトに転送するのに使用します。
転送の有効範囲と目的を確認するための、次のようなシナリオを想定します。まず、negotiateというメッセージに応答できるオブジェクトを設計して、その応答に別のオブジェクトの応答を含めるとします。これを実現するには、実装するnegotiateメソッドの本文のどこかで、別のオブジェクトにnegotiateメッセージを渡します。
さらに一歩進んで、negotiateメッセージに対するオブジェクトの応答を、別のクラスに実装されている応答とまったく同じにしたいとします。これを達成する1つの方法は、自分のクラスで、対象となる別のクラスのメソッドを継承することです。しかし、このような方法を取ることができない可能性もあります。自分のクラスとnegotiateが実装されているクラスが、相応の理由で継承階層の異なる分岐にあるかもしれません。
自分のクラスでnegotiateメソッドを継承できないとしても、対象となる別のクラスのインスタンスに単純にメッセージを渡すメソッドを実装するという方法でその実装を「借用」することはできます。
- negotiate |
{ |
if ( [someOtherObject respondsTo:@selector(negotiate)] ) |
return [someOtherObject negotiate]; |
return self; |
} |
しかし、この方法は少し煩雑になります。対象となる別のオブジェクトに渡すメッセージが複数ある場合は特にそうです。別のクラスから借用するメソッドごとに、メソッドを実装する必要があります。さらに、コードの記述時には想定していなかった転送の必要がある一連のメッセージが出現した場合に対応することができません。その一連のメッセージは実行時のイベントに依存していたり、将来の新しいメソッドやクラスの実装に伴って変わっていく可能性があります。
forwardInvocation:メッセージが提供する第2のチャンスはこの問題に対して、体系的で、静的ではなく動的な解決策をもたらします。それは、次のように機能します。オブジェクトに、メッセージのセレクタに一致するメソッドがないためにメッセージに応答できない場合、ランタイムシステムはforwardInvocation:メッセージを送信してオブジェクトに通知します。すべてのオブジェクトは、NSObjectクラスからforwardInvocation:メソッドを継承しています。しかし、このメソッドのNSObjectバージョンは単にdoesNotRecognizeSelector:を呼び出すだけです。NSObjectのバージョンをオーバーライドして自身のバージョンを実装すれば、forwardInvocation:メッセージによって提供される、メッセージを他のオブジェクトに転送する機会を利用することができます。
メッセージを転送するために、forwardInvocation:メソッドで実行する必要があるのは次のことだけです。
メッセージの送信先の決定
メッセージとオリジナル引数の送信先への送信
メッセージはinvokeWithTarget:メソッドで送信できます。
- (void)forwardInvocation:(NSInvocation *)anInvocation |
{ |
if ([someOtherObject respondsToSelector: |
[anInvocation selector]]) |
[anInvocation invokeWithTarget:someOtherObject]; |
else |
[super forwardInvocation:anInvocation]; |
} |
転送するメッセージの戻り値は、オリジナルの送信元に返されます。id、構造、および倍精度浮動小数点数など、あらゆる型の戻り値は送信元に配信することができます。
forwardInvocation:メソッドは、認識されなかったメッセージを別のレシーバに振り分ける配送センターとして機能できます。あるいは、すべてのメッセージを一箇所に送信する転送センターとしても機能できます。また、あるメッセージを別のメッセージに変換したり、応答やエラーを返さないようにメッセージを単に「飲み込む」こともできます。forwardInvocation:メソッドでは、複数のメッセージを1つの応答に統合することもできます。forwardInvocation:で何をするかは実装者の責任です。しかし、このメソッドが転送チェーンによってオブジェクトどうしを結び付けるために提供する機会は、プログラム設計の可能性を開きます。
注: forwardInvocation:メソッドは、メッセージが名目上のレシーバで既存のメソッドを呼び出さなかった場合にのみ、メッセージを処理することになります。たとえば、negotiateメッセージを別のオブジェクトに転送するオブジェクトは、自身のnegotiateメソッドを持つことはできません。持っていたとしたら、メッセージがforwardInvocation:に到達することはありません。
転送と呼び出しの詳細については、FoundationフレームワークリファレンスのNSInvocationクラス仕様を参照してください。
転送は継承を模倣しており、多重継承の効果の一部をObjective-Cプログラムに適用するために使用することができます。図 12-5に示すように、転送することでメッセージに応答するオブジェクトは、別のクラスに定義されているメソッド実装を借用または「継承」するように見えます。
この図では、Warriorクラスのインスタンスが、Diplomatクラスのインスタンスにnegotiateメッセージを転送します。Warrior(戦士)がDiplomat(外交官)のようにnegotiate(交渉)しているように見えます。Warriorはnegotiateメッセージに応答するように見え、実際にも応答します(もっとも、実際に仕事をしているのはDiplomatです)。
このように、メッセージを転送するオブジェクトは、継承階層の2つの分岐(自身の分岐とメッセージに応答するオブジェクトの分岐)からメソッドを「継承」します。上記の例では、Warriorクラスがそれ自身のスーパークラスだけでなく、Diplomatも継承するように見えます。
転送は、多重継承を評価するプログラマのほとんどのニーズに対応します。ただし、両者の間には重要な違いがあります。多重継承は1つのオブジェクトにさまざまな機能を統合します。オブジェクトが大きく、多面的になる傾向があります。それに対して、転送は個々のオブジェクトに個別の役割を割り当てます。転送は問題をより小さなオブジェクトに分解しますが、メッセージ送信側には意識させない方法でそれらのオブジェクトを関連付けます。
転送は多重継承を模倣するだけでなく、より大きなオブジェクトを代表あるいは「網羅」する軽量のオブジェクトの開発を可能にします。代理オブジェクトは、他のオブジェクトの代理として、メッセージの振り分けを行います。
リモートメッセージングで説明したプロキシは、このような代理の一種です。プロキシでは、リモートレシーバへのメッセージ転送、接続を経由した引数値のコピーや取得など、管理の細部を処理します。しかし、それら以外のことはあまり行いません。リモートオブジェクトの機能を再現することもせず、リモートオブジェクトにローカルアドレス(別のアプリケーションでメッセージを受信できる場所)を知らせるだけです。
他の種類の代理オブジェクトを利用することもできます。たとえば、大量のデータを操作するオブジェクトを利用するとします。このオブジェクトで複雑な画像を作成したり、ディスク上のファイル内容を読み取ります。このオブジェクトの設定には時間がかかる可能性があるため、本当に必要な場合や、システムリソースが一時的に余裕がある場合など、設定を緩やかに行うとします。しかし同時に、アプリケーションの他のオブジェクトが適切に機能するように、少なくともこのオブジェクトのプレースホルダが必要になります。
このような場合、本格的なオブジェクトではなく、軽量の代理オブジェクトを初めに作成します。このオブジェクトは、データに関する問い合わせへの回答など、それ自身でも何らかの処理を行うこともできますが、ほとんどの場合は大きいオブジェクトのために場所を取っておき、時期が来ればメッセージを転送します。代理のforwardInvocation:メソッドは、代理の対象となるオブジェクトに向けて送信されたメッセージを初めて受信すると、そのオブジェクトが存在することを確認し、存在しない場合は作成します。大きいオブジェクトへのメッセージはすべてこの代理オブジェクトを通るため、プログラムの他の部分から見れば、代理オブジェクトと大きいオブジェクトは同じです。
転送は継承を模倣していますが、NSObjectクラスが両者を混同することはありません。respondsToSelector:やisKindOfClass:のようなメソッドは、継承階層のみを参照し、転送チェーンを参照することはありません。たとえば、Warriorオブジェクトはnegotiateメッセージに応答するかどうかを次のように問い合わせたとします。
if ( [aWarrior respondsToSelector:@selector(negotiate)] ) |
... |
negotiateメッセージをエラーなしで受信でき、Diplomatに転送することで、ある意味では応答できたとしても、その答えはNOになります(図 12-5を参照してください)。
多くの場合は、NOが正しい答えです。しかし、そうでない場合もあります。転送を使って代理オブジェクトを設定するか、クラスの機能を拡張する場合、転送メカニズムは継承と同じくらい透過的にするべきでしょう。メッセージの転送先となるオブジェクトの動作を本当に継承しているかのようにオブジェクトを機能させたい場合は、respondsToSelector:およびisKindOfClass:メソッドを再実装して、転送アルゴリズムを含める必要があります。
- (BOOL)respondsToSelector:(SEL)aSelector |
{ |
if ( [super respondsToSelector:aSelector] ) |
return YES; |
else { |
/* ここでは、aSelectorメッセージを別のオブジェクトに転送できるかどうか、 * |
* そのオブジェクトが応答できるかどうかをテストする。 * |
* 可能な場合は、YESを返す。 */ |
} |
return NO; |
} |
respondsToSelector:とisKindOfClass:に加えて、instancesRespondToSelector:も転送アルゴリズムを反映する必要があります。プロトコルを使用する場合、conformsToProtocol:メソッドも同様にリストに加える必要があります。同様に、受信したリモートメッセージを転送するオブジェクトは、転送されたメッセージに最終的に応答するメソッドに関する正確な情報を返すことができる、methodSignatureForSelector:を持つ必要があります。
プライベートコードのどこかに転送アルゴリズムを配置し、forwardInvocation:を含め、これらすべてのメッセージがそのアルゴリズムを呼び出すようにすることも考えられます。
注: これは高度な手法であるため、他の解決策が可能でない状況にのみ適しています。また、継承の代替を意図したものでもありません。この手法を使用する必要がある場合は、転送を行うクラスと転送先のクラスの動作を十分に理解しておく必要があります。
このセクションで説明したメソッドについては、FoundationフレームワークリファレンスのNSObjectクラス仕様でも説明しています。invokeWithTarget:については、FoundationフレームワークリファレンスのNSInvocationクラス仕様を参照してください。
| < 前ページ次ページ > |
Last updated: 2007-10-31
|
Get information on Apple products.
Visit the Apple Store online or at retail locations. 1-800-MY-APPLE Copyright © 2007 Apple Inc. All rights reserved. | Terms of use | Privacy Notice |