よし、やっとWoolDelegateを GitHub にアップすることができた。これで、適切な README を書くのにあと 1 か月しかかからないはずです (ただし、これは良い出発点だと思います)。
デリゲート クラス自体は非常に簡単です。SEL
s を Block にマッピングするディクショナリを維持するだけです。インスタンスが応答しないメッセージを受信すると、最終的にforwardInvocation:
セレクターの辞書を調べます。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
GenericBlock handler = [self handlerForSelector:sel];
見つかった場合は、ブロックの呼び出し関数ポインターが引き出され、ジューシー ビットに渡されます。
IMP handlerIMP = BlockIMP(handler);
[anInvocation Wool_invokeUsingIMP:handlerIMP];
}
(この関数は、他のブロック プローブ コードと同様に、 Mike AshBlockIMP()
のおかげです。実際、このプロジェクトの多くは、彼の金曜日の Q&A から学んだことに基づいています。これらのエッセイを読んでいない場合は、見逃していることになります。 .)
これは、特定のメッセージが送信されるたびに、完全なメソッド解決機構を通過することに注意してください。そこにスピードヒットがあります。別の方法は、Erik H. とEMKPantryがそれぞれたどった道であり、必要なデリゲート オブジェクトごとに新しいクラスを作成し、class_addMethod()
. のすべてのインスタンスにWoolDelegate
はハンドラーの独自の辞書があるため、これを行う必要はありませんが、ルックアップまたは呼び出しを「キャッシュ」する方法はありません。メソッドはクラスにのみ追加でき、インスタンスには追加できません。
このようにした理由は 2 つあります。これは、次に来る部分 (Block 呼び出しへのハンドオフ) を解決できるかどうかを確認するための演習であり、必要なインスタンスごとにNSInvocation
新しいクラスを作成することは単純に思えました。私にはエレガントではありません。それが私のソリューションよりも洗練されていないかどうかは、各読者の判断に委ねます。
先に進むと、この手順の要点は、実際にはプロジェクトで見つかったNSInvocation
カテゴリにあります。これはlibffiを使用して、実行時まで不明な関数 (ブロックの呼び出し) を、実行時まで不明な引数 ( 経由でアクセス可能NSInvocation
) とともに呼び出します。通常、これは不可能です。これは、ava_list
を渡すことができないのと同じ理由です。コンパイラは、引数がいくつあり、どれくらい大きいかを知る必要があります。libffi には、各プラットフォームの呼び出し規約を認識している/基づいている各プラットフォーム用のアセンブラーが含まれています。
ここには 3 つのステップがあります。libffi は、呼び出される関数への引数の型のリストを必要とします。引数の値自体を特定の形式にする必要があります。次に、関数 (ブロックの呼び出しポインター) を libffi を介して呼び出し、戻り値を に戻す必要がありますNSInvocation
。
最初の部分の実際の作業は、Mike Ash によって記述された から呼び出される関数によって主に処理されWool_buildFFIArgTypeList
ます。libffi にはstruct
、関数の引数の型を記述するために使用する internal があります。関数の呼び出しを準備するとき、ライブラリはこれらの構造体へのポインターのリストを必要とします。NSMethodSignature
for は、各引数のエンコーディング文字列へのNSInvocation
アクセスを許可します。そこから正しいものへの翻訳はffi_type
、一連のif
/else
ルックアップによって処理されます。
arg_types[i] = libffi_type_for_objc_encoding([sig getArgumentTypeAtIndex:actual_arg_idx]);
...
if(str[0] == @encode(type)[0]) \
{ \
if(sizeof(type) == 1) \
return &ffi_type_sint8; \
else if(sizeof(type) == 2) \
return &ffi_type_sint16; \
次に、libffi は引数値自体へのポインターを必要とします。これは で行われWool_buildArgValList
ます: 各引数のサイズを再び から取得し、NSMethodSignature
そのサイズのメモリのチャンクを割り当ててから、リストを返します。
NSUInteger arg_size;
NSGetSizeAndAlignment([sig getArgumentTypeAtIndex:actual_arg_idx],
&arg_size,
NULL);
/* Get a piece of memory that size and put its address in the list. */
arg_list[i] = [self Wool_allocate:arg_size];
/* Put the value into the allocated spot. */
[self getArgument:arg_list[i] atIndex:actual_arg_idx];
(余談ですが、コードには、任意のメソッド呼び出しに (隠された) 2 番目に渡される引数である をスキップすることに関するいくつかの注意事項がありSEL
ます。ブロックの呼び出しポインターには、 を保持するためのスロットがありませんSEL
。引数であり、残りは「通常の」引数です。ブロックは、クライアント コードで記述されているように、とにかくその引数にアクセスできなかったので (その時点では存在しませんでした)、無視することにしました。)
libffi は、「準備」を行う必要があります。それが成功する限り (そして戻り値用のスペースを割り当てることができる限り)、呼び出し関数ポインターを「呼び出す」ことができ、戻り値を設定できます。
ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals);
if( ret_val ){
[self setReturnValue:ret_val];
free(ret_val);
}
プロジェクトの main.m に機能のデモがいくつかあります。
最後に、「これを行うべきですか?」という質問については、「生産性が向上する限り、はい」と答えると思います。WoolDelegate
は完全にジェネリックであり、インスタンスは完全に書き出されたクラスのように振る舞うことができます。ただし、私の意図は、単純な 1 回限りのデリゲートを作成することでした。これは、1 つまたは 2 つのメソッドしか必要とせず、デリゲーターを過ぎて生活する必要はありません。まったく新しいクラスを作成するよりも作業が少なく、読みやすくなります。 /maintainable いくつかのデリゲート メソッドをビュー コントローラーに貼り付けるよりも、それらを配置するのが最も簡単な場所であるためです。このようにランタイムと言語のダイナミズムを利用することで、ブロックベースのNSNotification
ハンドラーと同じように、コードの可読性が向上することが期待されます。