5

いくつかの引数を受け入れるブロックを持つ変数があります。引数の正確な数とそのタイプは異なる場合があります。たとえば、ブロックにすることができます

void(^testBlock1)(int) = ^(int i){}

またはブロック

void(^testBlock2)(NSString *,BOOL,int,float) = ^(NSString *str,BOOL b,int i,float f){}

引数の種類はに制限されてい{id, BOOL, char, int, unsigned int, float}ます。

引数の現在の数とそのタイプを知っています。与えられた引数でブロックを実行できるメソッドを実装する必要があります:

-(void)runBlock:(id)block withArguments:(va_list)arguments 
          types:(const char *)types count:(NSUInteger)count;

私には実用的な素朴な解決策が1つありますが、それは非常に醜く、4バイト以下のサイズのタイプのみをサポートし、アライメントに依存しています。だから私はもっと良いものを探しています。私の解決策は次のようなものです:

#define MAX_ARGS_COUNT 5
-(void)runBlock:(id)block withArguments:(va_list)arguments 
          types:(const char *)types count:(NSUInteger)count{

    // We will store arguments in this array.
    void * args_table[MAX_ARGS_COUNT];

    // Filling array with arguments
    for (int i=0; i<count; ++i) {
        switch (types[i]) {
            case '@':
            case 'c':
            case 'i':
            case 'I':
                args_table[i] = (void *)(va_arg(arguments, int));
                break;
            case 'f':
                *((float *)(args_table+i)) = (float)(va_arg(arguments, double));
                break;
            default:
                @throw [NSException exceptionWithName:@"runBlock" reason:[NSString stringWithFormat:@"unsupported type %c",types[i]] userInfo:nil];
                break;
        }
    }

    // Now we need to call our block with appropriate count of arguments

#define ARG(N) args_table[N]

#define BLOCK_ARG1 void(^)(void *)
#define BLOCK_ARG2 void(^)(void *,void *)
#define BLOCK_ARG3 void(^)(void *,void *,void *)
#define BLOCK_ARG4 void(^)(void *,void *,void *,void *)
#define BLOCK_ARG5 void(^)(void *,void *,void *,void *,void *)
#define BLOCK_ARG(N) BLOCK_ARG##N

    switch (count) {
        case 1:
            ((BLOCK_ARG(1))block)(ARG(0));
            break;
        case 2:
            ((BLOCK_ARG(2))block)(ARG(0),ARG(1));
            break;
        case 3:
            ((BLOCK_ARG(3))block)(ARG(0),ARG(1),ARG(2));
            break;
        case 4:
            ((BLOCK_ARG(4))block)(ARG(0),ARG(1),ARG(2),ARG(3));
            break;
        case 5:
            ((BLOCK_ARG(5))block)(ARG(0),ARG(1),ARG(2),ARG(3),ARG(4));
            break;
        default:
            break;
    }
}
4

3 に答える 3

7

さて、あなたはここでCの古典的なメタデータの欠如とABIの問題に直面しています。MABlockClosureに関するMikeAshの素晴らしい記事に基づいて、ブロックの基礎となる構造体を調べ、va_listがブロックの期待値と一致すると想定できると思います。ブロックをstructBlock_layoutにキャストすると、block->descriptorによってstructBlockDescriptorが提供されます。次に、ブロックの引数と型を表す@encode文字列があります(@encodeは、ワームの他のすべての缶です)。

したがって、引数とその型のリストを取得したら、block_layoutを掘り下げ、invokeを取得し、それを関数ポインターとして扱うことができます。最初のパラメーターは、コンテキストを提供するブロックです。Mike Ashは、トランポリンブロックに関する情報もいくつか持っています。これは、タイプ情報を気にせず、ブロックを呼び出したい場合に機能する可能性があります。

「ここにドラゴンがいる」という大きな脂肪の警告を追加しましょう。これはすべて非常に扱いやすく、ABIによって異なり、あいまいな機能や文書化されていない機能に依存しています。

また、必要な場所でブロックを直接呼び出すことができるようです。おそらく、NSArrayを唯一のパラメーターとして使用し、idを戻り型として使用します。そうすれば、「巧妙な」ハッキングがバックファイアすることを心配する必要はありません。

編集:NSMethodSignatureのsignatureWithObjCTypes:を使用して、ブロックの署名を渡すことができる場合があります。次に、NSInvocationのinvocationWithMethodSignature:を呼び出すことができますが、セレクターがないため、実際にトリガーするには、プライベートのinvokeWithIMP:メソッドを呼び出す必要があります。ターゲットをブロックに設定してから、invokeWithIMPを実行し、Block構造体のinvokeポインターを渡します。汎用ブロックプロキシを参照してください

于 2013-01-13T03:58:03.143 に答える
0

プライベートinvokeWithIMPを使用する代わりの良い方法は、さまざまな実装が必要なメソッドをスウィズルし、呼び出されたときに目的のIMPを検索するようにすることです。https://github.com/tuenti/TMInstanceMethodSwizzlerで同様のものを使用します

于 2014-03-31T09:23:10.660 に答える
0

実際の解決策は、va_listパラメーターを持つブロックを作成し、ブロックにそれ自体をソートさせることです。これは、実績のある簡単な方法です。

于 2014-03-31T09:30:29.683 に答える