2012年4月1日日曜日

DOT.NETのデリゲートとの比較


DOT.NET C#にもDelegateがありますが、Objective-Cのものとはかなり違っています。
C#のものはコールバック関数のようなもので、Objective-Cではblockを使った処理に似ています。
ここではCocoaのでデリゲートのメソッド呼び出しの手順が似ているものとして、NSURLConnectionとDOT.NETのBackgroundWorkerの動作を比較してみます。

DOT.NET(C#) Cocoa
BackgroundWorkerオブジェクトをMyFormに追加する。
インスタンス変数:BackgroundWorker bgWorker;
bgWorker = new BackgroundWorker();
NSURLConnectionオブジェクトを作り、HTTPレスポンスを受け取るオブジェクト(この例ではself)をdelegateにセットする。
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self];
処理開始
MyForm.csにバックグランドで実行するメソッドbgWorker_DoWorkを実装し、bgWorker.RunWorkerAsyncを呼ぶとこのメソッドが呼ばれ、バックグランド処理が開始される。
処理開始
initWithRequest:delegate:メッセージを送るとHTTPリクエスト処理が開始される。
終了処理
MyFormの次のメソッドが呼ばれる。(キャンセル、エラー発生時はパラメータのRunWorkerCompletedEventArgsにフラグがセットされる。)
bgWorker_RunWorkerCompleted()
終了処理
NSURLConnectionの次のdelegateメソッドが呼ばれる。
onnection:connectionDidFinishLoading:
キャンセル処理
メインスレッドからbgWorker.CancelAsyncを呼ぶ。
キャンセル処理
メインスレッドからurlConnectionのcancelメソッドを呼ぶ。
処理中断
バックグランド処理でDoWorkで渡されるパラメータDoWorkEventArgsのCancelプロパティーをtrueにし、returnする。 メインスレッドのbgWorker_RunWorkerCompletedが呼ばれる。
処理中断
受信処理中にエラーが発生すると次のメソッドが呼ばれる。
-(void)connection:(NSURLConnection*)connection didFailWithError:(NSError *)error {

どちらも別スレッドで処理を実行し、処理経過に応じて状態変化をメインスレッドに通知し、メインスレッドでそれに応じた処理を行っています。DOT.NETでは変数名にdelegateは使われていませんが、どちらも同じデリゲートの仕組みです。

一方、次のような相違点があります。
  • DOT.NETではBackgrounWorkerオブジェクトはFormオブジェクトの変数にセットされ、そのFormオブジェクトがデリゲートとなる。
  • CocoaのNSURLConnectionはどのオブジェクトが作るか、どのオブジェクトをデリゲートとするかは前提としていない。
  • DOT.NETではBackgroundWorkerオブジェクトの変数名に応じた名前のメソッドを作り、それを呼び出す。
  • Cocoaでは既定のデリゲートメソッドが呼ばれる。
この相違点は、内部的には更に次のような違いがあります。(実際のソースコードは分からないので、推定です。)
  • DOT.NETではデザイナで設定されたプロパティー名からメソッド名を作り、その名前のメソッドをソースコードに追加し、それを呼び出す。ソースコードに直接追加するため、実行時にメソッドを探すことはしない。
  • Cocoaではdelegateがメソッドに応えるか(respondsToSelector:)か、ないしはプロトコルを実装しているか(conformsToProtpcol:)をチェックし、応える場合はそれを呼ぶ。(プロトコルの場合でも@requiredでない場合はrespondsToSelector:によるチェックが必要。)
この違いは些細なように見えますが、メソッドサーチの根本的な違いに由来するもので、Cocoaでデリゲートが多用されるのに対し、DOT.NETでは同様のコンセプトはありながらもさほど使われないことに関わっています。これはJavaについてもあてはまります。
  • DOT.NETのprotocol、JavaのinterfaceはObjective-Cのprotocolと同等のものですが、それをクラス宣言に加えた場合はすべてのメソッドを実装する必要があります。一部を欠いた場合はコンパイル時にエラーとなります。これはDOT.NETもJavaもコンパイル時に呼び出すメソッドを決定するのが基本だからです。refrectionでメソッドを探すのは特別な場合に限られ、それを多用するとパフォーマンスにも影響します。その場合もシグネチャー(メソッド名+パラメータ型)が一致するメソッドを探し、それを実行するという手順になります。
  • Objective-Cではプロトコルで宣言されたメソッドでも@requiredでなければ実装する必要はありません。Objective-Cでは常にruntimeシステムが実際に実行するメソッドを探します(ダイナミックバインディング)。そのため、コンパイル時のチェックは緩やかです。オブジェクトをid型で宣言すれば、メソッドが宣言されている.hをimportすればどのようなメソッドでもコンパイルは通ります。実行時にそのメソッドを実行できないとエラーとなりますが、respondsToSelector:でチェックすることで避けることができます。delegateはid型のことが多く、respondsToSelector:によるチェックはほとんどのデリゲートメソッドで行われています。また@requiredの場合もコンパイラのチェックに頼らず、conformsToProtpcol:によるチェックが行われているはずです。
デリゲートはオブジェクト指向のデザインパターンとしては言語に限らず一般的なものですが、その使われ方は言語に大きく依存します。

デリゲートを柔軟に使用できるかどうかで、ライブラリの性格も変わってきます。DOT.NETやJavaが機能提供傾向にあり、Cocoaはフレームワーク傾向にあると言えるでしょう。

DOT.NETのDelegateクラスはメソッド参照を持つクラスで、それにシグネチャーが同じメソッドを割り当てるとどのメソッドも同じように扱えるようになる、といったものです。デリゲートメソッドを実行するオブジェクトに実行したいメソッドを割り当てるとそのメソッドが呼ばれる、というもので、コールバックの動作になります。
正規表現のRegex.ReplaceがDelegateを使っています。

public string Replace(string input, MatchEvaluator evaluator)
public delegate string MatchEvaluator(Match match)

0 件のコメント: