'메세징'에 해당되는 글 1건

  1. 2011.02.10 [아이폰개발] 메세징 원리 라이브러리 미번역

メッセージングの仕組み

Objective-C では、メッセージは実行時までメソッド実装にバインドされません。コンパイラは、次のメッセージ式を変換します。

[receiver message]

上記のメッセージは、メッセージ関数の呼び出し objc_msgSend に変換されます。この関数は、メッセージに記述されたレシーバ とメソッドの名前(メソッドセレクタ)を 2 つの主要なパラメータとして利用します。

objc_msgSend(receiver, selector)

メッセージに渡された引数は、objc_msgSendにも渡されます。

objc_msgSend(receiver, selector, arg1, arg2, ...)

メッセージング関数は、動的バインディングに必要なことをすべて実行します。

  • まず、セレクタが参照するプロシージャ(メソッド実装)を探します。同じメソッドを別々のクラスでそれぞれ実装できるため、メッセージング関数が探す正確なプロシージャはレシーバのクラスによって決まります。
  • 次に、プロシージャを呼び出し、メソッドに指定された引数と一緒に、受信側オブジェクト(そのデータへのポインタ)を渡します。
  • 最終的に、プロシージャの戻り値を自身の戻り値として渡します。

注意:メッセージング関数の呼び出しは、コンパイラによって生成されます。自分が作成するコードの中で直接呼び出してはなりません。

メッセージングのポイントは、コンパイラが各クラスとオブジェクトに対して構築する構造にあります。あらゆるクラス構造には、次の2つの重要な要素が含まれています。

  • スーパークラスへのポインタ。
  • クラスのディスパッチテーブル。このテーブルには、メソッドセレクタとそれらが識別するメソッドのクラス固有のアドレスを関連付けるエントリがあります。たとえば、setOrigin:: メソッドのセレクタは setOrigin::(を実装するプロシージャ)のアドレスと関連付けられ、display メソッドのセレクタは display のアドレスと関連付けられています。

新しいオブジェクトが作成されると、そのメモリが割り当てられ、そのインスタンス変数が初期化されます。オブジェクトの変数の中で最初のものはクラス構造のポインタです。このポインタは isa と呼ばれ、クラス、および継承されるすべてのクラスにアクセスする手段をオブジェクトに提供します。

注意:厳密には言語の一部ではありませんが、オブジェクトが Objective-C ランタイムシステムと連携するには isa ポインタが必要になります。オブジェクトは、構造に定義されるどのようなフィールドでも struct objc_objectobjc/objc.h で定義)と「同等」でなければなりません。しかし、独自にルートオブジェクトを作成する必要が生じることは、あったとしても非常にまれで、NSObject または NSProxy を継承するオブジェクトは isa 変数を自動的に持っています。

これらのクラスとオブジェクト構造の要素を、図 2-5 に示します。


図 2-5 メッセージングフレームワーク

図 2-5 メッセージングフレームワーク

メッセージをオブジェクトに送信すると、メッセージング関数は、オブジェクトの isa ポインタをたどってクラス構造に到達し、そこでディスパッチテーブルでメソッドセレクタを調べます。ディスパッチテーブルにセレクタが見つからない場合、objc_msgSend はポインタをたどってスーパークラスに到達し、そのディスパッチテーブルの中でセレクタを探します。失敗し続けると、objc_msgSend は NSObject クラスに達するまでクラス階層を遡ることになります。セレクタが見つけると、この関数はテーブルに含まれているメソッドを呼び出し、受信側オブジェクトのデータ構造を渡します。

これが、実行時にメソッド実装が選択される方法です。オブジェクト指向プログラミング特有の言い回しをすれば、メソッドがメッセージへ動的にバインドされるといいます。

メッセージング処理をスピードアップするため、ランタイムシステムはセレクタの使用に伴って、セレクタと、対応するメソッドのアドレスをキャッシュします。クラスごとに個別のキャッシュがあり、クラスで定義したメソッドのセレクタだけでなく、継承したメソッドのセレクタを含むこともできます。ディスパッチテーブルを検索する前に、メッセージングルーチンはまず、受信側オブジェクトのクラスのキャッシュを確認します(一度使用したメソッドは再度使用する可能性があるため)。キャッシュにメソッドセレクタがある場合、メッセージングは関数呼び出しより若干遅いだけです。キャッシュを「ウォームアップ」するのに十分な時間、プログラムが実行されると、そのプログラムが送信するほとんどすべてのメッセージは、対応するキャッシュされたメソッドが見つかります。プログラムの実行に伴い、新しいメッセージに対応するため、キャッシュは動的に成長します。

このセクションの内容:

セレクタ
隠し引数
self と super に対するメッセージ

セレクタ

効率化のため、コンパイル済みのコードでは完全な ASCII 名をメソッドセレクタとして使用しません。その代わりに、コンパイラは各メソッド名をテーブルに書き込み、その名前を実行時にメソッドを示す一意の識別子と組み合わせます。ランタイムシステムによって、各識別子が一意であることが保証されます。2 つのセレクタが同じであることはなく、同じ名前のメソッドはすべて同じセレクタに対応します。コンパイル済みのセレクタは、他のデータと区別するため、特別な型 SEL に割り当てられます。有効なセレクタは 0 であることはありません。メソッドへの SEL 識別子の割り当てはシステムに任せる必要があります。任意に割り当てても無駄になります。

@selector() ディレクティブにより、Objective-C のソースコードは完全なメソッド名ではなく、コンパイル済みのセレクタを参照することができます。次の例では、setWidth:height: のセレクタを、setWidthHeight 変数に代入しています。

SEL  setWidthHeight;
setWidthHeight = @selector(setWidth:height:);

コンパイル時に @selector() ディレクティブを使用して、SEL 変数に値を代入するのが最も効率的です。しかし、場合によっては、プログラムで実行時に文字列をセレクタに変換する必要があります。これを実行するには、NSSelectorFromString 関数を使います。

setWidthHeight = NSSelectorFromString(aBuffer);

逆方向の変換も可能です。NSStringFromSelector 関数はセレクタに対応するメソッド名を返します。

NSString *method;
method = NSStringFromSelector(setWidthHeight);

このようなランタイム関数については、Cocoa フレームワークリファレンスドキュメントで説明しています。


メソッドとセレクタ

コンパイル済みのセレクタは、メソッド実装ではなく、メソッド名を識別します。たとえば、Rectangle の display メソッドには、他のクラスで定義された display メソッドと同じセレクタが対応します。これはポリモーフィズム(多態性)と動的バインディングには不可欠です。これにより、さまざまなクラスに属するレシーバに、同じメッセージを送信することができます。メソッド実装ごとに 1 つのセレクタがあったとしたら、メッセージは関数呼び出しと変わりません。

クラスメソッドと同じ名前のインスタンスメソッドは、同じセレクタに割り当てられます。しかし、それらは別々のドメインに属するため、2 つの間に混乱はありません。クラスでは、display インスタンスメソッドに加えて、display クラスメソッドを定義することができます。


メソッドの戻り値と引数の型

メッセージングルーチンはセレクタを通してのみメソッド実装にアクセスできるため、同じセレクタに対応するすべてのメソッドを同等に扱います。ルーチンは、セレクタに基づいてメソッドの戻り型と引数のデータ型を知ります。したがって、静的に型定義されたレシーバに送信されたメッセージを除いて、動的バインディングでは、同じ名前を持つメソッドのすべての実装で、戻り型と引数型が同じである必要があります(コンパイラはクラス型からメソッド実装を知ることができるため、静的に型定義されたレシーバはこのルールの例外です)。

同じ名前のクラスメソッドとインスタンスメソッドは、同じセレクタで表されますが、引数と戻り型が異なる可能性があります。


実行時のメッセージ変更

performSelector:performSelector:withObject:、および performSelector:withObject:withObject: メソッドは NSObject プロトコルで定義されており、SEL 識別子を初期引数として利用します。3 つのメソッドはすべて、メッセージング関数に直接マップされます。たとえば、次のように記述します。

[friend performSelector:@selector(gossipAbout:)
    withObject:aNeighbor];

次のように記述しても同じです。

[friend gossipAbout:aNeighbor];

これらのメソッドにより、メッセージを受信するオブジェクトを変更できるのと同じように、実行時にメッセージを変更できます。メッセージ式を構成する両方の要素に変数名を使用することができます。

id   helper = getTheReceiver();
SEL  request = getTheSelector();
[helper performSelector:request];

上記の例では、レシーバ(helper)は実行時に(架空の getTheReceiver 関数によって)選択され、レシーバが実行するように要求されるメソッド(request)も実行時に(同様に架空の getTheSelector 関数によって)決められます。

注意:performSelector: とその関連メソッドは id を返します。実行されるメソッドが別の型を返す場合は、適切な型にキャストする必要があります(ただし、キャストがすべての型に有効なわけではありません。メソッドはポインタまたはポインタと互換性のある型を返す必要があります)。


ターゲットアクションパラダイム

ユーザインタフェース制御の処理において、Application Kit はレシーバとメッセージの両方を変更する方法を有効に利用しています。

NSControls は、アプリケーションへの指示を指定するのに使用できるグラフィカルデバイスです。大部分はボタン、スイッチ、ノブ、テキストフィールド、ダイヤル、メニュー項目など、現実世界の制御デバイスに似ています。ソフトウェアでは、これらのデバイスがアプリケーションとユーザの間に介在します。これらのデバイスは、キーボードやマウスなどのハードウェアデバイスから発されるイベントを解釈し、アプリケーション固有の命令に変換します。たとえば、「検索」というラベルの付いたボタンはマウスクリックを、アプリケーションが何かの検索を開始する命令に変換します。

Application Kit は制御デバイスを作成するためのテンプレートを定義し、「そのまま」使用できる独自のデバイスもいくつか定義します。たとえば、NSButtonCell クラスでは、NSMatrix に割り当てて、サイズ、ラベル、ピクチャ、フォント、およびキーボードショートカットを初期化できるオブジェクトを定義します。ユーザがボタンをクリックすると(あるいは、キーボードショートカットを使用すると)、NSButtonCell はアプリケーションに何かを実行するよう指示するメッセージを送信します。これを行うには、NSButtonCell を画像、サイズ、およびラベルだけでなく、どんなメッセージを送信し、誰に送信するかに関する指示においても初期化する必要があります。したがって、NSButtonCell はアクションメッセージ、送信するメッセージで使用するメソッドセレクタ、ターゲット(メッセージを受信するオブジェクト)で初期化することができます。

[myButtonCell setAction:@selector(reapTheWind:)];
[myButtonCell setTarget:anObject];

NSButtonCell は、NSObject の performSelector:withObject: メソッドを使用してメッセージを送信します。すべてのアクションメッセージは 1 つの引数、つまりメッセージを送信する制御デバイスの id を受け取ります。

Objective-C でメッセージを変更できないとしたら、すべての NSButtonCells が同じメッセージを送信しなければならなず、メソッドの名前が NSButtonCell ソースコードで固定されます。ユーザ操作をアクションメッセージに変換するメカニズムを単に実装するのではなく、NSButtonCell などのコントロールでメッセージの内容に制約を課す必要が生じます。これでは、どのようなオブジェクトも、複数の NSButtonCell に対応するのが困難になります。ボタンごとに 1 つのターゲットを用意するか、ターゲットオブジェクトがメッセージを送信したボタンを検出し、それに応じて動作する必要が生じます。ユーザインタフェースを再配置するたびに、アクションメッセージに応答するメソッドも再実装する必要が生じます。幸いにも、Objective-C では、このように不要な複雑さが回避されています。


メッセージングエラーの回避

オブジェクトが、レパートリーにないメソッドを実行するメッセージを受信すると、エラーが発生します。これは、存在しない関数を呼び出した場合と同じ種類のエラーです。しかし、メッセージングは実行時に行われるため、多くの場合、プログラムを実行するまでエラーが明らかになりません。

メッセージセレクタが不変で、受信側オブジェクトのクラスが分かっている場合、このようなエラーを回避するのは比較的簡単です。プログラムを記述するときに、レシーバが応答できることを確認しておけばよいからです。レシーバが静的に型定義されていれば、コンパイラがそのテストを実行してくれます。

しかし、メッセージセレクタまたはレシーバのクラスが可変の場合、そのテストを実行時まで延期しなければならない場合もあります。NSObject クラスで定義されている respondsToSelector: メソッドを使用してレシーバがメッセージに応答できるかどうかを調べることができます。引数としてメソッドセレクタを受け取り、レシーバがセレクタに一致するメソッドにアクセスできるかどうかを返します。

if ( [anObject respondsToSelector:@selector(setOrigin::)] )
    [anObject setOrigin:0.0 :0.0];
else
    fprintf(stderr, "%s can’t be placed\n",
        [NSStringFromClass([anObject class]) cString]);

respondsToSelector: テストが特に重要なのは、コンパイル時に制御の及ぶ範囲内にないオブジェクトにメッセージを送信する場合です。たとえば、他から設定可能な変数によって指定されるオブジェクトにメッセージを送信するコードを記述する場合は、必ずメッセージに応答できるメソッドをレシーバが実装するようにします。

注意:また、オブジェクトは、自身で直接応答できない場合、受信したメッセージを他のオブジェクトに転送するように準備することもできます。その場合、メッセージを別のオブジェクトに割り当てて間接的に応答しているにも関わらず、オブジェクトがメッセージを処理できるように見えます。詳細については、「転送」を参照してください。


隠し引数

メッセージング関数はメソッドを実装するプロシージャを見つけると、そのプロシージャを呼び出し、メッセージのすべての引数を渡します。また、次の 2 つの隠し引数をプロシージャに渡します。

  • 受信側オブジェクト
  • メソッドのセレクタ

これらの引数はすべてのメソッド実装に、当該実装を呼び出したメッセージ式の両方の構成要素に関する明確な情報を提供します。それらの引数は、メソッドを定義するソースコードで宣言されないため、「隠し」引数と呼ばれています。これらは、コードのコンパイル時に実装に挿入されます。

これらの引数は明示的には宣言されませんが、ソースコードは(受信側オブジェクトのインスタンス変数を参照できるのと同じように)隠し引数を参照することができます。メソッドは受信側オブジェクトを self として、自身のセレクタを _cmd として参照します。次の例では、_cmdstrange メソッドのセレクタを参照し、selfstrange メッセージを受信するオブジェクトを参照します。

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

この 2 つの引数のうち、self のほうが有用です。実際、これは受信側オブジェクトのインスタンス変数を、メソッド定義で利用可能にする方法です。


selfsuper に対するメッセージ

Objective-C では、メソッドを実行するオブジェクトを参照するためにメソッド定義内で使用できる 2 つのキーワード、selfsuper が提供されています。

たとえば、操作対象となるすべてのオブジェクトの座標を変更する必要がある、reposition メソッドを定義するとします。そのメソッドは、変更を行う setOrigin:: メソッドを呼び出すことができます。必要なのは、reposition メッセージ自体の送信先となった同じオブジェクトに、setOrigin:: メッセージを送信することだけです。位置変更コードを記述する際には、そのオブジェクトを self または super のいずれかとして参照することができます。reposition メソッドはどちらも読み取ることができます。

- reposition
{
    ...
    [self setOrigin:someX :someY];
    ...
}

または

- reposition
{
    ...
    [super setOrigin:someX :someY];
    ...
}

この場合、どのようなオブジェクトであっても、selfsuper はどちらも reposition メッセージを受信するオブジェクトを参照します。ただし、この 2 つのキーワードはまったく異なるものです。self は、メッセージングルーチンがすべてのメソッドに渡す隠し引数の 1 つであり、インスタンス変数の名前と同じように、メソッド実装内で自由に使用できるローカル変数です。super は、メッセージ式でレシーバとしてのみ self の代わりに使用できるキーワードです。レシーバーから見ると、2 つのキーワードは、主にメッセージング処理に与える影響が異なります。

  • self は受信側オブジェクトのクラスのディスパッチテーブルから始まり、通常の方法でメソッド実装を検索します。上記の例では、再配置メッセージを受信するオブジェクトのクラスから検索を始めます。
  • super は、まったく異なる場所でメソッド実装の検索を開始します。検索は、super が出現するメソッドを定義する、クラスのスーパークラスから始まります。上記の例では、再配置が定義されるクラスのスーパークラスから検索を始めます。

どこで super がメッセージを受信しても、コンパイラは objc_msgSend 関数の代わりに別のメッセージングルーチンを使用します。代替ルーチンは、メッセージを受信するオブジェクトのクラスではなく、定義クラスのスーパークラス、つまり super にメッセージを送信するクラスのスーパークラスを直接参照します。


selfsuper の違いは、3 つのクラスの階層で明確になります。たとえば、Low というクラスに属するオブジェクトを作成するとします。Low のスーパークラスは Mid で、Mid のスーパークラスは High です。3 つのクラスすべてで negotiate というメソッドを定義し、さまざまな用途に使用します。さらに、Mid では makeLastingPeace という高度なメソッドを定義しますが、これは negotiate メソッドも必要とします。次の図 2-6 にこれを図示します。


図 2-6 High、Mid、Low

図 2-6 High、Mid、Low

ここで、makeLastingPeace メソッドを実行するために Low オブジェクトにメッセージを送信します。すると、makeLastingPeace は同じ Low オブジェクトに negotiate メッセージを送信します。ソースコードの中でメッセージの送信対象オブジェクトを self と呼ぶ場合には、次のようになります。

- makeLastingPeace
{
    [self negotiate]; 
    ...
}

メッセージングルーチンは、self の実際のクラスである Low に定義されているバージョンの negotiate を探します。しかし、Mid のソースコードで送信対象のオブジェクトを super と呼ぶ場合には、次のようになります。

- makeLastingPeace
{
    [super negotiate]; 
    ...
}

メッセージングルーチンは、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 へのメッセージにより、メソッド実装を複数のクラスに分散することができます。既存のメソッドをオーバーライドして変更や追加を行う一方で、元のメソッドをその変更に組む込むことができます。

- negotiate
{
    ...
    return [super negotiate]; 
}

処理によっては、継承階層の各クラスで作業の一部を行い、残りの作業についてはメッセージを super に渡して処理するメソッドを実装することができます。新たに割り当てられたインスタンスを初期化する init メソッドは、このように動作するように設計されています。それぞれの init メソッドは、クラスに定義されているインスタンス変数を初期化する 役割を持っています。しかし、初期化の前に、init メッセージを super に送信して、継承元のクラスにインスタンス変数を初期化させます。init の各バージョンがこの手続きに従うため、クラスは継承の順序に従ってインスタンス変数を初期化することになります。

- (id)init
{
    [super init];
    ...
}

また、中核的な機能をスーパークラスに定義する 1 つのメソッドに集中させ、サブクラスにおいて super へのメッセージを使用してそのメソッドを組み込むこともできます。たとえば、インスタンスを作成するすべてのクラスメソッドは、新しいオブジェクトにデータ記憶域を割り当て、isa ポインタをクラス構造で初期化する必要があります。これは、通常、NSObject クラスに定義されている alloc および allocWithZone: メソッドに委ねられます。別のクラスでこれらのメソッドをオーバーライドする場合も(まれなケース)、そのメソッドでメッセージを super に送信することによって基本機能を利用することができます。


self の再定義

super は実行するメソッドの検索を始める場所をコンパイラに伝える単なるフラグで、メッセージのレシーバとしてのみ使用します。しかし、self は変数名で、いろいろな方法で使用でき、新しい値を代入することもできます。

クラスメソッドの定義では、まさにそうすることがよくあります。クラスメソッドは多くの場合、クラスオブジェクトではなく、クラスのインスタンスを対象としています。たとえば、多くのクラスメソッドがインスタンスの割り当てと初期化を結合し、多くの場合、同時にインスタンス変数値も設定します。このようなメソッドでは、インスタンスメソッドの場合と同様に、新たに割り当てられたインスタンスにメッセージを送信して、self インスタンスを呼び出すことも考えられます。しかし、これはエラーになります。selfsuperは、どちらも受信側オブジェクト(メソッドを実行するように指示するメッセージを取得するオブジェクト)を参照します。インスタンスメソッド内では self はインスタンスを参照しますが、クラスメソッド内では self はクラスオブジェクトを参照します。次の例は、してはいけないことを示します。

+ (Rectangle *)rectangleOfColor:(NSColor *) color
{
    self = [[self alloc] init]; // だめ
    [self setColor:color];
    return [self autorelease]; 
}

混乱を避けるために、通常はクラスメソッド内のインスタンスを参照するとき、self ではなく、変数を使用するほうが適切です。

+ (id)rectangleOfColor:(NSColor *)color
{
    id newInstance = [[self alloc] init]; // よい
    [newInstance setColor:color];
    return [newInstance autorelease];
}

実際、クラスメソッドの中で selfalloc メッセージを送信するよりも、多くの場合は alloc[self class] に送信するほうが有効です。こうしておけば、クラスをサブクラス化し、サブクラスが rectangleOfColor: メッセージを受信した場合、返されるインスタンスはそのサブクラスと同じ型になります。

+ (id)rectangleOfColor:(NSColor *)color
{
    id newInstance = [[[self class] alloc] init]; // 最良
    [newInstance setColor:color];
    return [newInstance autorelease];
}

オブジェクト割り当ての詳細については、「メモリ管理」を参照してください。



ps. 이건 걍 읽어나 봐야겠다..ㅡ.ㅡ
Posted by 김반장78
,