NSResponder と responder chain の実装

NSResponder と responder chain の実装


ここでは、Chain of Responsibility の実際に使われている例ということで、NSResponder と responder chain の説明をしよう。別に Cocoa の宣伝をしたいわけじゃないんだけど、この実装はメッセージ送信の利点をうまく使っていると思うんで。

NSResponder と responder chain とは

まずは、軽く背景を説明しよう。NSResponder と responder chain ってのは、現在の Mac OS X のフレームワークである Cocoa で使われているものなんだ。その由来は、NeXT の Foundation と AppKit にさかのぼることができる。Cocoa フレームワークは、Objective-C で使うことを前提に設計されている。だから、この説明も Objective-C を使うよ。いちおう、現在は Java の API も提供されていて、Cocoa-Java って呼ばれているんだ。ただ Java の特性を生かしたものというよりは、Objective-C の API にはめあわせた、っていう感じだね。

で、responder chain の仕事は、アプリケーション内でのイベントの伝達だ。複数のオブジェクトがチェインとして連なり、イベントが投げられたら、処理できるオブジェクトまで順々に投げられていくんだ。つまり、GoF 本で区分されるところの Chain of Responsibility パターンっていうことね。実際、GoF 本で responder chain は、このパターンの元ネタとして紹介されている。名前いっしょだし。

NSResponder クラスってのは、この responder chain を形成するために使われるクラスだ。主な用途は、チェイン上の次のオブジェクトを探すことと、デフォルトのイベントを処理するメソッドを提供すること。NSResponder を継承することで、これらの機能が使える。でも、responder chain に加わるためには NSResponder を継承しなくてはいけない、ということはない。NSWindow や NSApplication の delegate として加わることができるんだ。


イベントメソッドとアクションメソッド


responder chain を使ったイベントの伝達は、2 通りあると捉えることができる。1 つは、イベントメソッドと呼ばれているものを使う方法。これには、マウスイベントのための mouseDown: とか、キーボードイベントのための keyDown: とかがある。このタイプに属するメソッドは、NSResponder でデフォルトの実装がされていて、その中で次のオブジェクトへのリクエストの伝達をやっているんだ。また、リクエストは NSEvent というクラスであり、呼び出されるメソッドの情報は含まない。このイベントを受け取るには、メソッドを上書きしてやるんだ。

もう 1 つは、アクションメソッドと呼ばれるものを使う方法。この場合は、リクエストは Objective-C のセレクタになる。NSApplication というクラスが、この伝達を管理するんだ。NSApplication は、まず responder chain の先頭のオブジェクトが渡されたセレクタを処理できるかどうか調べる。処理できるならメソッドを呼び出し、できないならチェインをたぐり、次のオブジェクトを呼び出すことになる。リクエストがセレクタなので、ユーザが簡単にアクションを追加できる。


具体的な例


具体的な例を示そう。responder chain として、NSButton -> NSView -> MyWindow という 3 つのクラスのインスタンスが連なっているものを考える。MyWindow は NSWindow を継承したもので、独自のメソッドをオーバーライドしているとするよ。

まず、イベントメソッドの場合。たとえばマウスダウンイベントが発生したとすると、それは responder chain の先頭のオブジェクトに送られるんだ。この場合、NSButton に送られる。そして、対応するすでに決まっているハンドラである mouseDown: が呼び出される。デオフォルトの実装では、ただチェインの次のオブジェクトへイベントを送るだけで。イベントが送られていって、mouseDown: をオーバーライドしている MyWindow までいくと、そこで処理されるんだ。

eventMethod.jpg

次に、アクションメソッドの場合。たとえば、selectAll: というアクションが発生したとしよう。すると、それは NSApplication に送られる。NSApplication は、responder chain の先頭から、selectAll: を処理できるかどうか、respondsToSelector: を使って聞いてまわるんだ。順々にたどっていき、MyWindow が処理できると分かったら、performSelector: を用いてそのメソッドを呼び出して、処理させる。

actionMethod.jpg

Cocoa では、こういう 2 種類の responder chain があるんだ。前者の利点は、パフォーマンスがよくなること。後者の利点は、リクエストの追加が NSResponder や NSApplication の変更なしに行えることだ。Mac OS X の GUI 設計環境である Interface Builder では、このアクションメソッドタイプを積極的に使っている。

イベントメソッドタイプはは、ほとんどの言語で普通に実装できる。でも、アクションメソッドタイプの方は難しい。指定されたアクションがそのオブジェクトで処理できるかどうか、処理できるならば対応するメソッドを呼び出す、という仕組みが必要になるからだ。メッセージ送信機構を備えている言語なら、かなり直接的に実現できる。ない場合は、それに比べて大掛かりな仕組みが必要なってしまうんだ。