メッセージングの仕組み
Objective-C では、メッセージは実行時までメソッド実装にバインドされません。コンパイラは、次のメッセージ式を変換します。
|
上記のメッセージは、メッセージ関数の呼び出し objc_msgSend
に変換されます。この関数は、メッセージに記述されたレシーバ とメソッドの名前(メソッドセレクタ)を 2 つの主要なパラメータとして利用します。
|
メッセージに渡された引数は、objc_msgSend
にも渡されます。
|
メッセージング関数は、動的バインディングに必要なことをすべて実行します。
- まず、セレクタが参照するプロシージャ(メソッド実装)を探します。同じメソッドを別々のクラスでそれぞれ実装できるため、メッセージング関数が探す正確なプロシージャはレシーバのクラスによって決まります。
- 次に、プロシージャを呼び出し、メソッドに指定された引数と一緒に、受信側オブジェクト(そのデータへのポインタ)を渡します。
- 最終的に、プロシージャの戻り値を自身の戻り値として渡します。
注意:メッセージング関数の呼び出しは、コンパイラによって生成されます。自分が作成するコードの中で直接呼び出してはなりません。 |
メッセージングのポイントは、コンパイラが各クラスとオブジェクトに対して構築する構造にあります。あらゆるクラス構造には、次の2つの重要な要素が含まれています。
- スーパークラスへのポインタ。
- クラスのディスパッチテーブル。このテーブルには、メソッドセレクタとそれらが識別するメソッドのクラス固有のアドレスを関連付けるエントリがあります。たとえば、
setOrigin::
メソッドのセレクタはsetOrigin::
(を実装するプロシージャ)のアドレスと関連付けられ、display
メソッドのセレクタはdisplay
のアドレスと関連付けられています。
新しいオブジェクトが作成されると、そのメモリが割り当てられ、そのインスタンス変数が初期化されます。オブジェクトの変数の中で最初のものはクラス構造のポインタです。このポインタは isa
と呼ばれ、クラス、および継承されるすべてのクラスにアクセスする手段をオブジェクトに提供します。
これらのクラスとオブジェクト構造の要素を、図 2-5 に示します。
図 2-5 メッセージングフレームワーク
メッセージをオブジェクトに送信すると、メッセージング関数は、オブジェクトの isa
ポインタをたどってクラス構造に到達し、そこでディスパッチテーブルでメソッドセレクタを調べます。ディスパッチテーブルにセレクタが見つからない場合、objc_msgSend
はポインタをたどってスーパークラスに到達し、そのディスパッチテーブルの中でセレクタを探します。失敗し続けると、objc_msgSend
は NSObject クラスに達するまでクラス階層を遡ることになります。セレクタが見つけると、この関数はテーブルに含まれているメソッドを呼び出し、受信側オブジェクトのデータ構造を渡します。
これが、実行時にメソッド実装が選択される方法です。オブジェクト指向プログラミング特有の言い回しをすれば、メソッドがメッセージへ動的にバインドされるといいます。
メッセージング処理をスピードアップするため、ランタイムシステムはセレクタの使用に伴って、セレクタと、対応するメソッドのアドレスをキャッシュします。クラスごとに個別のキャッシュがあり、クラスで定義したメソッドのセレクタだけでなく、継承したメソッドのセレクタを含むこともできます。ディスパッチテーブルを検索する前に、メッセージングルーチンはまず、受信側オブジェクトのクラスのキャッシュを確認します(一度使用したメソッドは再度使用する可能性があるため)。キャッシュにメソッドセレクタがある場合、メッセージングは関数呼び出しより若干遅いだけです。キャッシュを「ウォームアップ」するのに十分な時間、プログラムが実行されると、そのプログラムが送信するほとんどすべてのメッセージは、対応するキャッシュされたメソッドが見つかります。プログラムの実行に伴い、新しいメッセージに対応するため、キャッシュは動的に成長します。
このセクションの内容:
セレクタ
隠し引数
self と super に対するメッセージ
セレクタ
効率化のため、コンパイル済みのコードでは完全な ASCII 名をメソッドセレクタとして使用しません。その代わりに、コンパイラは各メソッド名をテーブルに書き込み、その名前を実行時にメソッドを示す一意の識別子と組み合わせます。ランタイムシステムによって、各識別子が一意であることが保証されます。2 つのセレクタが同じであることはなく、同じ名前のメソッドはすべて同じセレクタに対応します。コンパイル済みのセレクタは、他のデータと区別するため、特別な型 SEL
に割り当てられます。有効なセレクタは 0 であることはありません。メソッドへの SEL
識別子の割り当てはシステムに任せる必要があります。任意に割り当てても無駄になります。
@selector()
ディレクティブにより、Objective-C のソースコードは完全なメソッド名ではなく、コンパイル済みのセレクタを参照することができます。次の例では、setWidth:height:
のセレクタを、setWidthHeight
変数に代入しています。
|
コンパイル時に @selector()
ディレクティブを使用して、SEL
変数に値を代入するのが最も効率的です。しかし、場合によっては、プログラムで実行時に文字列をセレクタに変換する必要があります。これを実行するには、NSSelectorFromString
関数を使います。
|
逆方向の変換も可能です。NSStringFromSelector
関数はセレクタに対応するメソッド名を返します。
|
このようなランタイム関数については、Cocoa フレームワークリファレンスドキュメントで説明しています。
メソッドとセレクタ
コンパイル済みのセレクタは、メソッド実装ではなく、メソッド名を識別します。たとえば、Rectangle の display
メソッドには、他のクラスで定義された display
メソッドと同じセレクタが対応します。これはポリモーフィズム(多態性)と動的バインディングには不可欠です。これにより、さまざまなクラスに属するレシーバに、同じメッセージを送信することができます。メソッド実装ごとに 1 つのセレクタがあったとしたら、メッセージは関数呼び出しと変わりません。
クラスメソッドと同じ名前のインスタンスメソッドは、同じセレクタに割り当てられます。しかし、それらは別々のドメインに属するため、2 つの間に混乱はありません。クラスでは、display
インスタンスメソッドに加えて、display
クラスメソッドを定義することができます。
メソッドの戻り値と引数の型
メッセージングルーチンはセレクタを通してのみメソッド実装にアクセスできるため、同じセレクタに対応するすべてのメソッドを同等に扱います。ルーチンは、セレクタに基づいてメソッドの戻り型と引数のデータ型を知ります。したがって、静的に型定義されたレシーバに送信されたメッセージを除いて、動的バインディングでは、同じ名前を持つメソッドのすべての実装で、戻り型と引数型が同じである必要があります(コンパイラはクラス型からメソッド実装を知ることができるため、静的に型定義されたレシーバはこのルールの例外です)。
同じ名前のクラスメソッドとインスタンスメソッドは、同じセレクタで表されますが、引数と戻り型が異なる可能性があります。
実行時のメッセージ変更
performSelector:
、performSelector:withObject:
、および performSelector:withObject:withObject:
メソッドは NSObject プロトコルで定義されており、SEL
識別子を初期引数として利用します。3 つのメソッドはすべて、メッセージング関数に直接マップされます。たとえば、次のように記述します。
|
次のように記述しても同じです。
|
これらのメソッドにより、メッセージを受信するオブジェクトを変更できるのと同じように、実行時にメッセージを変更できます。メッセージ式を構成する両方の要素に変数名を使用することができます。
|
上記の例では、レシーバ(helper
)は実行時に(架空の getTheReceiver
関数によって)選択され、レシーバが実行するように要求されるメソッド(request
)も実行時に(同様に架空の getTheSelector
関数によって)決められます。
注意:performSelector: とその関連メソッドは id を返します。実行されるメソッドが別の型を返す場合は、適切な型にキャストする必要があります(ただし、キャストがすべての型に有効なわけではありません。メソッドはポインタまたはポインタと互換性のある型を返す必要があります)。 |
ターゲットアクションパラダイム
ユーザインタフェース制御の処理において、Application Kit はレシーバとメッセージの両方を変更する方法を有効に利用しています。
NSControls は、アプリケーションへの指示を指定するのに使用できるグラフィカルデバイスです。大部分はボタン、スイッチ、ノブ、テキストフィールド、ダイヤル、メニュー項目など、現実世界の制御デバイスに似ています。ソフトウェアでは、これらのデバイスがアプリケーションとユーザの間に介在します。これらのデバイスは、キーボードやマウスなどのハードウェアデバイスから発されるイベントを解釈し、アプリケーション固有の命令に変換します。たとえば、「検索」というラベルの付いたボタンはマウスクリックを、アプリケーションが何かの検索を開始する命令に変換します。
Application Kit は制御デバイスを作成するためのテンプレートを定義し、「そのまま」使用できる独自のデバイスもいくつか定義します。たとえば、NSButtonCell クラスでは、NSMatrix に割り当てて、サイズ、ラベル、ピクチャ、フォント、およびキーボードショートカットを初期化できるオブジェクトを定義します。ユーザがボタンをクリックすると(あるいは、キーボードショートカットを使用すると)、NSButtonCell はアプリケーションに何かを実行するよう指示するメッセージを送信します。これを行うには、NSButtonCell を画像、サイズ、およびラベルだけでなく、どんなメッセージを送信し、誰に送信するかに関する指示においても初期化する必要があります。したがって、NSButtonCell はアクションメッセージ、送信するメッセージで使用するメソッドセレクタ、ターゲット(メッセージを受信するオブジェクト)で初期化することができます。
|
NSButtonCell は、NSObject の performSelector:withObject:
メソッドを使用してメッセージを送信します。すべてのアクションメッセージは 1 つの引数、つまりメッセージを送信する制御デバイスの id
を受け取ります。
Objective-C でメッセージを変更できないとしたら、すべての NSButtonCells が同じメッセージを送信しなければならなず、メソッドの名前が NSButtonCell ソースコードで固定されます。ユーザ操作をアクションメッセージに変換するメカニズムを単に実装するのではなく、NSButtonCell などのコントロールでメッセージの内容に制約を課す必要が生じます。これでは、どのようなオブジェクトも、複数の NSButtonCell に対応するのが困難になります。ボタンごとに 1 つのターゲットを用意するか、ターゲットオブジェクトがメッセージを送信したボタンを検出し、それに応じて動作する必要が生じます。ユーザインタフェースを再配置するたびに、アクションメッセージに応答するメソッドも再実装する必要が生じます。幸いにも、Objective-C では、このように不要な複雑さが回避されています。
メッセージングエラーの回避
オブジェクトが、レパートリーにないメソッドを実行するメッセージを受信すると、エラーが発生します。これは、存在しない関数を呼び出した場合と同じ種類のエラーです。しかし、メッセージングは実行時に行われるため、多くの場合、プログラムを実行するまでエラーが明らかになりません。
メッセージセレクタが不変で、受信側オブジェクトのクラスが分かっている場合、このようなエラーを回避するのは比較的簡単です。プログラムを記述するときに、レシーバが応答できることを確認しておけばよいからです。レシーバが静的に型定義されていれば、コンパイラがそのテストを実行してくれます。
しかし、メッセージセレクタまたはレシーバのクラスが可変の場合、そのテストを実行時まで延期しなければならない場合もあります。NSObject クラスで定義されている respondsToSelector:
メソッドを使用してレシーバがメッセージに応答できるかどうかを調べることができます。引数としてメソッドセレクタを受け取り、レシーバがセレクタに一致するメソッドにアクセスできるかどうかを返します。
|
respondsToSelector:
テストが特に重要なのは、コンパイル時に制御の及ぶ範囲内にないオブジェクトにメッセージを送信する場合です。たとえば、他から設定可能な変数によって指定されるオブジェクトにメッセージを送信するコードを記述する場合は、必ずメッセージに応答できるメソッドをレシーバが実装するようにします。
注意:また、オブジェクトは、自身で直接応答できない場合、受信したメッセージを他のオブジェクトに転送するように準備することもできます。その場合、メッセージを別のオブジェクトに割り当てて間接的に応答しているにも関わらず、オブジェクトがメッセージを処理できるように見えます。詳細については、「転送」を参照してください。 |
隠し引数
メッセージング関数はメソッドを実装するプロシージャを見つけると、そのプロシージャを呼び出し、メッセージのすべての引数を渡します。また、次の 2 つの隠し引数をプロシージャに渡します。
これらの引数はすべてのメソッド実装に、当該実装を呼び出したメッセージ式の両方の構成要素に関する明確な情報を提供します。それらの引数は、メソッドを定義するソースコードで宣言されないため、「隠し」引数と呼ばれています。これらは、コードのコンパイル時に実装に挿入されます。
これらの引数は明示的には宣言されませんが、ソースコードは(受信側オブジェクトのインスタンス変数を参照できるのと同じように)隠し引数を参照することができます。メソッドは受信側オブジェクトを self
として、自身のセレクタを _cmd
として参照します。次の例では、_cmd
は strange
メソッドのセレクタを参照し、self
は strange
メッセージを受信するオブジェクトを参照します。
|
この 2 つの引数のうち、self
のほうが有用です。実際、これは受信側オブジェクトのインスタンス変数を、メソッド定義で利用可能にする方法です。
self
と super
に対するメッセージ
Objective-C では、メソッドを実行するオブジェクトを参照するためにメソッド定義内で使用できる 2 つのキーワード、self
と super
が提供されています。
たとえば、操作対象となるすべてのオブジェクトの座標を変更する必要がある、reposition
メソッドを定義するとします。そのメソッドは、変更を行う setOrigin::
メソッドを呼び出すことができます。必要なのは、reposition
メッセージ自体の送信先となった同じオブジェクトに、setOrigin::
メッセージを送信することだけです。位置変更コードを記述する際には、そのオブジェクトを self
または super
のいずれかとして参照することができます。reposition
メソッドはどちらも読み取ることができます。
|
または
|
この場合、どのようなオブジェクトであっても、self
と super
はどちらも reposition
メッセージを受信するオブジェクトを参照します。ただし、この 2 つのキーワードはまったく異なるものです。self
は、メッセージングルーチンがすべてのメソッドに渡す隠し引数の 1 つであり、インスタンス変数の名前と同じように、メソッド実装内で自由に使用できるローカル変数です。super
は、メッセージ式でレシーバとしてのみ self
の代わりに使用できるキーワードです。レシーバーから見ると、2 つのキーワードは、主にメッセージング処理に与える影響が異なります。
self
は受信側オブジェクトのクラスのディスパッチテーブルから始まり、通常の方法でメソッド実装を検索します。上記の例では、再配置メッセージを受信するオブジェクトのクラスから検索を始めます。
super
は、まったく異なる場所でメソッド実装の検索を開始します。検索は、super
が出現するメソッドを定義する、クラスのスーパークラスから始まります。上記の例では、再配置が定義されるクラスのスーパークラスから検索を始めます。
どこで super
がメッセージを受信しても、コンパイラは objc_msgSend
関数の代わりに別のメッセージングルーチンを使用します。代替ルーチンは、メッセージを受信するオブジェクトのクラスではなく、定義クラスのスーパークラス、つまり super
にメッセージを送信するクラスのスーパークラスを直接参照します。
例
self
と super
の違いは、3 つのクラスの階層で明確になります。たとえば、Low というクラスに属するオブジェクトを作成するとします。Low のスーパークラスは Mid で、Mid のスーパークラスは High です。3 つのクラスすべてで negotiate
というメソッドを定義し、さまざまな用途に使用します。さらに、Mid では makeLastingPeace
という高度なメソッドを定義しますが、これは negotiate
メソッドも必要とします。次の図 2-6 にこれを図示します。
図 2-6 High、Mid、Low
ここで、makeLastingPeace
メソッドを実行するために Low オブジェクトにメッセージを送信します。すると、makeLastingPeace
は同じ Low オブジェクトに negotiate
メッセージを送信します。ソースコードの中でメッセージの送信対象オブジェクトを self
と呼ぶ場合には、次のようになります。
|
メッセージングルーチンは、self
の実際のクラスである Low に定義されているバージョンの negotiate
を探します。しかし、Mid のソースコードで送信対象のオブジェクトを super
と呼ぶ場合には、次のようになります。
|
メッセージングルーチンは、High に定義されているバージョンの negotiate
を探します。makeLastingPeace
は Mid で定義されているため、メッセージングルーチンは受信側オブジェクトのクラス (Low) を無視して、Mid のスーパークラスにスキップします。どちらのメッセージも、Mid バージョンの negotiate
を探しません。
この例に示すように、super
は別のメソッドをオーバーライドするメソッドをバイパスする手段を提供します。この場合は、makeLastingPeace
が、元の High バージョンの negotiate
を再定義する Mid バージョンを無視することを可能にしました。
Mid バージョンの negotiate
に到達できないのは欠陥のように見えるかもしれませんが、このような状況ではそれを回避するほうが適切です。
- Low クラスの作成者が意図的に Mid バージョンの
negotiate
をオーバーライドして、Low クラス(およびそのサブクラス)のインスタンスが、再定義されたバージョンのメソッドを代わりに呼び出すようにしています。Low の設計者は、Low オブジェクトに継承メソッドを実行させないようにしたわけです。
super
にメッセージを送信することで、Mid のmakeLastingPeace
メソッドの作成者は、Mid バージョンのnegotiate
(および Mid を継承する Low などのクラスで定義するバージョン)を意図的にスキップして、High クラスに定義されているバージョンを実行するようにしました。Mid の設計者は、negotiate
の High バージョンだけを使用するようにしたいわけです。
それでも Mid バージョンの negotiate
を使用することはできますが、そのためには Mid インスタンスに直接メッセージを送信する必要があります。
super の使用
super
へのメッセージにより、メソッド実装を複数のクラスに分散することができます。既存のメソッドをオーバーライドして変更や追加を行う一方で、元のメソッドをその変更に組む込むことができます。
|
処理によっては、継承階層の各クラスで作業の一部を行い、残りの作業についてはメッセージを super
に渡して処理するメソッドを実装することができます。新たに割り当てられたインスタンスを初期化する init
メソッドは、このように動作するように設計されています。それぞれの init
メソッドは、クラスに定義されているインスタンス変数を初期化する 役割を持っています。しかし、初期化の前に、init
メッセージを super
に送信して、継承元のクラスにインスタンス変数を初期化させます。init
の各バージョンがこの手続きに従うため、クラスは継承の順序に従ってインスタンス変数を初期化することになります。
|
また、中核的な機能をスーパークラスに定義する 1 つのメソッドに集中させ、サブクラスにおいて super
へのメッセージを使用してそのメソッドを組み込むこともできます。たとえば、インスタンスを作成するすべてのクラスメソッドは、新しいオブジェクトにデータ記憶域を割り当て、isa
ポインタをクラス構造で初期化する必要があります。これは、通常、NSObject クラスに定義されている alloc
および allocWithZone:
メソッドに委ねられます。別のクラスでこれらのメソッドをオーバーライドする場合も(まれなケース)、そのメソッドでメッセージを super
に送信することによって基本機能を利用することができます。
self の再定義
super
は実行するメソッドの検索を始める場所をコンパイラに伝える単なるフラグで、メッセージのレシーバとしてのみ使用します。しかし、self
は変数名で、いろいろな方法で使用でき、新しい値を代入することもできます。
クラスメソッドの定義では、まさにそうすることがよくあります。クラスメソッドは多くの場合、クラスオブジェクトではなく、クラスのインスタンスを対象としています。たとえば、多くのクラスメソッドがインスタンスの割り当てと初期化を結合し、多くの場合、同時にインスタンス変数値も設定します。このようなメソッドでは、インスタンスメソッドの場合と同様に、新たに割り当てられたインスタンスにメッセージを送信して、self
インスタンスを呼び出すことも考えられます。しかし、これはエラーになります。self
と super
は、どちらも受信側オブジェクト(メソッドを実行するように指示するメッセージを取得するオブジェクト)を参照します。インスタンスメソッド内では self
はインスタンスを参照しますが、クラスメソッド内では self
はクラスオブジェクトを参照します。次の例は、してはいけないことを示します。
|
混乱を避けるために、通常はクラスメソッド内のインスタンスを参照するとき、self
ではなく、変数を使用するほうが適切です。
|
実際、クラスメソッドの中で self
に alloc
メッセージを送信するよりも、多くの場合は alloc
を [self class]
に送信するほうが有効です。こうしておけば、クラスをサブクラス化し、サブクラスが rectangleOfColor:
メッセージを受信した場合、返されるインスタンスはそのサブクラスと同じ型になります。
|
オブジェクト割り当ての詳細については、「メモリ管理」を参照してください。
ps. 이건 걍 읽어나 봐야겠다..ㅡ.ㅡ
'아이폰개발' 카테고리의 다른 글
[아이폰개발] IB없이 HTTP통신 예제 (0) | 2011.02.25 |
---|---|
[아이폰개발] 참고 사이트 (0) | 2011.02.16 |
[아이폰개발] 탭바 인터페이스 라이브러리 번역 (0) | 2011.02.10 |
[아이폰개발] hpple 사용예 (0) | 2011.01.28 |
[아이폰개발] 오브젝트별 참고사이트 (0) | 2011.01.21 |