Cocoaの提供するResponder Chainについて、もっと詳しく踏み込んでみよう。
まず今回は、ターゲット・アクションのためのResponder Chainについて紹介する。
GUIアプリケーションでメニューを実装するときの問題点
このタイプのResponder Chainが活躍するのは、GUIアプリケーションにおけるメニューの処理などである。
メニューを実装するときのことを考えてみよう。基本的に、メニューを処理するという事は、最終的にはあるオブジェクトのあるメソッドを呼び出す事に帰着する。たとえば、「開く」メニューであれば、書類を処理するクラスの、openDocumentといった名前のメソッドを呼び出すだろう。これは、Cocoaであっても、他のフレームワークであっても、それほど変わらないと思う。
だが、メニューは常に固定のメソッドを呼び出せばいいのかというと、そうではない。たとえば、「コピー」メニューがある。このコマンドは、状況によって呼び出すメソッドが変わってくる。ドローツールのようなアプリケーションを考えてみよう。もしテキストツールを使ってテキストを入力中なら、文字をコピーするために、コピーコマンドはテキストフィールドオブジェクトによって処理されるだろう。だが、そのテキストをドローオブジェクトとして選択している場合は、今度はドローのキャンバスを管理しているオブジェクトにコピーコマンドを送り、ドローオブジェクトのコピーを行うべきである。つまり、コピーコマンドは、動的にその送り先を変えるべきである。
もう1つの例を考えてみよう。今度は「検索」メニューだ。ウィンドウの内容を検索するために、新しい検索ウィンドウを開いたり、ウィンドウの内部に検索フィールドを表示したりする。このメニューは、誰が処理するべきだろうか。まず考えられるのはウィンドウ自身である。するとコマンドの送り先は、「最前面にあるウィンドウ」ということになり、これもまた送り先が動的に変化する(この制約は、メニューとウィンドウが切り離されている、Macのデスクトップに特有の問題とも言える)。
また検索ウィンドウはそれぞれのドキュメントウィンドウに隷属せず、アプリケーション全体で1つの検索ウィンドウを開きたい、というときもあるだろう。この場合、検索コマンドはアプリケーションを表すオブジェクトに送られるべきであろう。
整理してみよう。メニューの処理を実装するときの、要求は2つだ。まず、メニューを処理する対象は、動的に変更すること。コピーコマンドは、テキストフィールドがアクティブならばテキストフィールドに、ドローオブジェクトがアクティブならばドローオブジェクトまたはドローキャンバスに送られる。つまり、現在フォーカスの当たっているオブジェクトが起点になる。
だが、フォーカスの当たっているオブジェクトが、常にメニューを処理できるとは限らない。ボタンオブジェクトは、コピーに反応しないだろう。また、検索コマンドのように、ウィンドウやアプリケーションレベルで処理させたいものもある。したがって、フォーカスされたオブジェクトを起点に、メニューを処理できるオブジェクトを探す必要がある。これが、2つめの要求だ。
もう、お分かりだろう。動的に要求の送り先は変化し、フォーカスの当たっているオブジェクトを起点に要求を処理できるオブジェクトを探す。Chain of Responsibilityパターンが必要とされる条件にぴったりだ。
First Responder
「フォーカスの当たっているオブジェクト」に関して、1つの用語を紹介しておこう。
Cocoaでは、ウィンドウ上で現在フォーカスが当たっているオブジェクトのことを、First Responderと呼ぶ。First Responderは、基本的には、NSResponderを継承しているオブジェクトとなる。
Cocoaでウィンドウを表すNSWindowには、First Responderを取得/設定するためのメソッドがある。
List 1. NSWindow.h
- (NSResponder*)firstResponder;
- (BOOL)makeFirstResponder:(NSResponder*)responder;
firstResponderで取得し、makeFirstResponder:で設定する。makeFirstResponder:を呼んでも、First Responderの設定が失敗することもある。これは、指定されたオブジェクト自身が、First Responderになることができるかどうか決定できるからである。これは、後で説明しよう。
First Responderは、nilの場合もある。これは、ウィンドウのどこにもフォーカスが当たっていない状態を表す。
First Responderに関するメソッド
First Responderに関する、NSResponderのメソッドも紹介しておこう。以下のメソッドが定義されている。
List 2. NSResponder.h
- (BOOL)acceptsFirstResponder;
- (BOOL)becomeFirstResponder;
- (BOOL)resignFirstResponder;
重要なのは、1つめにある、acceptsFirstResponderメソッドだ。このメソッドは、このオブジェクトがFirst Responderになれるかどうかを表す。GUIの部品であっても、フォーカスの当たる必要のないものもあるだろう。コマンドを処理する必要だってないものもある。その場合は、このメソッドでNOを返せばいい。また、動的にYESとNOを切り替えることもできる。
残りの2つは、通知に使われる。あるオブジェクトがFirst ResponderになったときにはbecomeFirstResponderが呼ばれる。その反対で、First ResponderでなくなったときにはresignFirstResponderが呼ばれる。
今回は、メニュー処理におけるResponder Chainの必要性と、チェーンの起点となるFirst Responderの紹介をした。次回は、実際に形成されるチェーンと、チェーンをどのようにたぐるかを説明しよう。