前回は、Cocoaでメニューなどのアクションを処理するためにResponder Chainが使われている事を説明した。今回は、そのチェーンがどのようにたぐられていくのかを説明しよう。GoF本で取り上げられているchain of responsibilityパターンとは、少し異なるものとなる。

もう1つのチェーンのたぐり方

CocoaのResponder Chainでは、NSResponderクラスが要求を処理するチェーンを形成する。このとき、要求はどのようにこのチェーンを渡っていくのだろうか? GoF本のパターンによれば、要求を処理するクラス自身、つまりここで言えばNSResponderが、自分で処理できない要求を次のオブジェクトへと渡していく。

CocoaのResponder Chainは、これだけではない。2種類の要求の伝播が用意されている。1つは、GoF本のようなNSResponder自身が行うもの。もう1つは、外部のクラスが行うものだ。

外部のクラスによるチェーンのたぐり方を考えてみよう。まず、あるチェーンの先頭オブジェクトを取り出す。そして、そのオブジェクトが要求を処理できるかどうか判定する。処理できるなら、そのオブジェクトに処理をまかせて終了する。できないならば、チェーンをたぐって次のオブジェクトを取り出す。これをチェーンの最後まで繰り返す。

擬似的なコードを書くならば、次のようになるだろう。この連載の第90回で紹介したコードを利用している。

List 1.

- (void)sendRequest:(SEL)selector
{
    // Get first responder
    id responder;
    responder = [self firstResponder];

    // Traverse responder chain
    while (!responder) {
        // Handle request
        if ([responder respondsToSelector:selector]) {
            [self performSelector:selector];
            break;
        }

        // Get next responder
        responder = [responder nextResponder];
    }
}

sendRequest:というメソッドで、チェーンをたぐり、要求を処理している。まず、チェーンの先頭をFirst Responderとして取得する。そして、そのオブジェクトが要求を処理できるかどうか、repondsToSelector:を使ってチェック。処理できるならば、performSelector:で実行。できないならば、nextResponderで次のオブジェクトをとりだしている。

注目してほしいのは、このメソッドはResponderのクラスが実装する必要がない、ということだ。チェーンを形成するものとは別に、チェーンをたぐるクラスが登場する。この手法の利点は、特殊な処理を追加しやすい事だ。たとえば、あるチェーンが終わったら別のチェーンを探索する、またはチェーンの中にResponder以外のクラスが登場したときに対応する、などが考えられるだろう。

たぐるのはNSApplication

Cocoaでは、このように2種類のResponder Chainのたぐり方が存在する。ターゲット・アクションの処理で使われるのは後者の方、外部のクラスがチェーンをたぐる手法だ。NSApplicationというクラスがこれを担当する。

NSApplicationの、sendAction:to:from:というメソッドで、チェーンの探索を開始する。

List 2. NSApplication.h

- (BOOL)sendAction:(SEL)sction to:(id)target from:(id)sender

第一引数がアクション、第二引数がターゲット、第三引数がアクションの送り手となる。ここに、ターゲット・アクションの登場人物がすべてそろったことになる。

このメソッドで、First Responderを取り出し、アクションを送れるかどうかチェックしていくことになる。詳しくは後述しよう。

このチェーンに含まれるのは、基本的にはNSResponderということになる。アクションに応答できるかどうかの確認とその処理は、respondsToSelector:とperformSelector:を使ってもいいのだが、NSResponderでは特別なものも用意している。次のメソッドだ。

List 3. NSResponder.h

- (BOOL)tryToPerform:(SEL)action with:(id)object
- (void)doCommandBySelector:(SEL)selector

tryToPerform:with:は、指定したセレクタとオブジェクトに対する処理を実行できるかどうか調べるもの。doCommandBySelector:は、指定したセレクタを実行するものだ。これらも使って、チェーンに送られた要求を処理できるかどうか調べることになる。

次回は、アプリケーションで形成されるチェーンについて説明しよう。