2012-12-04 12 views
5

ブロックを引数として受け入れる変数があります。正確な数の引数とその型は異なる場合があります。例えば、それはブロックの引数の数と型が変わる可能性がある場合、va_listからの引数を持つ呼び出しブロック

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; 
    } 
} 

答えて

6

さてあなたは、古典的な不足-のメタデータとABIの問題Cでここに直面して実行されている: 私の解決策のようなものです。 Mike AshのAwesome Article about MABlockClosureに基づいて、ブロックの基礎となる構造体を調べて、ブロックが期待するものとva_listが一致していると仮定します。ブロックをBlock_layout構造体にキャストすると、block->記述子が構造体BlockDescriptorを返します。次に、ブロックの引数と型を表す@encode文字列があります(@encodeは他にもワームの可能性があります)。

引数のリストと型を取得したら、block_layoutを呼び出して呼び出しを取得し、最初のパラメータがコンテキストを提供するブロックである関数ポインタとして扱うことができます。 Mike AshにはTrampolining Blocksに関する情報もありますが、タイプ情報は気にせず、ブロックを呼び出すだけの場合に有効です。

「ここにドラゴンズがいる」という大きな脂肪を追加しましょう。これはすべて非常に敏感で、ABIに基づいて異なり、あいまいであるか、および/または文書化されていない機能に依存しています。

また、必要に応じてブロックを直接呼び出すことができます。戻り値の型としてNSArrayを唯一のパラメータとして、idを使用することもできます。そうすれば、あなたに逆行する「賢い」ハックを心配する必要はありません。

編集:NSMethodSignatureのsignatureWithObjCTypes:を使用して、ブロックの署名を渡すことができます。次に、NSInvocationのinvocationWithMethodSignature:を呼び出すことができますが、セレクタを持たないためprivate invokeWithIMP:メソッドを実際に呼び出す必要があります。ターゲットをブロックに設定してから、invokeWithIMPを呼び出して、ブロック構造体の呼び出しポインタを渡します。 Generic Block Proxying

+1

ありがとうございました。 私はすでに引数とその型のリストを持っています。そして、はい、_invoke_関数ポインタを見つけて取得できます。しかし、どうすればキャストのための 'BLOCK_ARG(N)'マクロと引数のための一時的な配列よりも優れた方法で呼び出すことができますか? 'ffi_call'以外の解決策はありますか? ** libffi **を使用することは私の小さな仕事のオーバーヘッドにすぎません。 – Yan

+0

さて、ここでは、Cが期待する方法でパラメータを設定するためにアセンブリを使用する必要があります。これは、Objective-CランタイムのNSInvocationやその他のさまざまな欠点が、C言語では特定のレジスタに配置されなければならず、特定の方法でスタックに流出するためです。これはプラットフォーム(x86、x64、ARM)に基づいて変わります。 – russbishop

0

プライベートなinvokeWithIMPを使用する代わりに、異なる実装をしたいメソッドをswizzleし、呼び出されたときにいつでも望ましいIMPを探すようにすることができます。同じようなものを使用します:https://github.com/tuenti/TMInstanceMethodSwizzler

0

本当の解決策は、va_listパラメータを持つブロックを用意し、ブロック自体をソートすることです。これは実績のある簡単な方法です。

関連する問題