9

私はブロックが大好きで、とてもクールです。ターゲット/セレクタのペアを「ラップする」ブロックを作成するにはどうすればよいですか?

しかし、私はブロックが自分のコードを乱雑にし、Xcode内でそれらをすべて折りたたむことなく読みにくくすることができます。

コードを論理的なメソッド(セレクタ)に分割して読みやすくするのが好きですが、ディスパッチ、AFNetworkingなどのフレームワークでは容易に実現できません。

デリゲートのアプローチも気にしません。他の人が必要と思うものに頼る代わりに、私が望むように自分のメソッドに名前を付けることはできません。 (私には)読みやすい

-(void) reloadData { 
    ... 
    [[EventsManager instance] data:YES async:createBlock(self, @selector(processEvents:))]; 
    ... 
} 

-(void) reloadData { 
    ... 
    [[EventsManager instance] data:YES async:^(NSArray *events) { [self processEvents:events] }]; 
    ... 
} 

私が代わりにこのような何かを行うことができます。このようにグルーコードの束を記述することなくそう

、。

私たちが持っているパワーとランタイムでは、であるべきですか?私はそこにこのようなものは見ていない。

+0

これを読んだ人には分かりにくい人にはちょっと注意してください。自分で質問しました。 –

+1

あなたはゼロ分フラット、良い仕事でそれを働いた! – Wain

+0

@Wainはほとんどありません。これを得るために2日間の頑張った仕事をしました。 –

答えて

6

はい、実際には可能ですが、この解決策はABI固有のものであり(すべてのプラットフォームで動作することは保証されていません)、実行時にメソッドに関する情報を幅広く活用しています。

最初に行う必要があるのは、ブロックでラッピングする方法に関する情報を取得することです。これは、情報などが含まれ、NSMethodSignatureを介して行われます:各引数の(バイト単位)

  • 引数の数
  • サイズ
  • これは私たちがラップすることができます

戻り値の型の

  • サイズ(ほぼ)そのメソッドのための特定のコードを持たないメソッドであり、再利用可能な関数を作成します。

    第2に、実行時にメソッド呼び出しを安全にディスパッチする方法が必要です。私たちはNSInvocationでこれを行い、実行時に動的で安全なメソッド呼び出しを作成することができます。

    第3に、渡される引数の数に制限があり、それをディスパッチできるブロックが必要です。これはCのva_list APIを介して行われ、メソッドの99%で動作するはずです。

    最後に、戻り値を取得し、それをブロックから戻す必要があります。これは、Objective-Cランタイムを使用して構造体などを返すことによる奇妙さのために、動作しない可能性のある操作全体の一部です。

    ただし、プリミティブ型とObjective-Cオブジェクトを使用している限り、このコードは優れた機能を発揮します。

    この実装について注意すべき点がいくつ:

    • それが原因のiOSとMacの呼び出し規約では、しかし、ブロック&関数型のキャストと未定義の動作時に依存している、これはいけません(あなたのメソッドがブロックが期待するものとは異なる戻り値の型を持たない限り)問題を引き起こします。

    • va_argを呼び出した結果が未定義の動作にも依存しますが、型が同じサイズであるため、これは決して問題にはなりません。

      ここで任意の前置き

    は、実装に続いて、コードの例です:


    @interface MyObj : NSObject 
    
    -(void) doSomething; 
    
    @end 
    
    @implementation MyObj 
    
    -(void) doSomething 
    { 
        NSLog(@"This is me, doing something! %p", self); 
    } 
    
    -(id) doSomethingWithArgs:(long) arg :(short) arg2{ 
        return [NSString stringWithFormat:@"%ld %d", arg, arg2]; 
    } 
    
    @end 
    
    int main() { 
        // try out our selector wrapping 
        MyObj *obj = [MyObj new]; 
    
        id (^asBlock)(long, short) = createBlock(obj, @selector(doSomethingWithArgs::)); 
        NSLog(@"%@", asBlock(123456789, 456)); 
    } 
    

    /* WARNING, ABI SPECIFIC, BLAH BLAH BLAH NOT PORTABLE! */ 
    static inline void getArgFromListOfSize(va_list *args, void *first, size_t size, size_t align, void *dst, BOOL isFirst) { 
        // create a map of sizes to types 
        switch (size) { 
          // varargs are weird, and are aligned to 32 bit boundaries. We still only copy the size needed, though. 
          // these cases should cover all 32 bit pointers (iOS), boolean values, and floats too. 
         case sizeof(uint8_t): { 
          uint8_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t); 
          memcpy(dst, &tmp, size); 
          break; 
         } 
    
         case sizeof(uint16_t): { 
          uint16_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t); 
          memcpy(dst, &tmp, size); 
          break; 
         } 
    
         case sizeof(uint32_t): { 
          uint32_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t); 
          memcpy(dst, &tmp, size); 
          break; 
         } 
    
          // this should cover 64 bit pointers (Mac), and longs, and doubles 
         case sizeof(uint64_t): { 
          uint64_t tmp = isFirst ? (uint64_t) first : va_arg(*args, uint64_t); 
          memcpy(dst, &tmp, size); 
          break; 
         } 
          /* This has to be commented out to work on iOS (as CGSizes are 64 bits) 
          // common 'other' types (covers CGSize, CGPoint) 
         case sizeof(CGPoint): { 
          CGPoint tmp = isFirst ? *(CGPoint *) &first : va_arg(*args, CGPoint); 
          memcpy(dst, &tmp, size); 
          break; 
         } 
          */ 
    
          // CGRects are fairly common on iOS, so we'll include those as well 
         case sizeof(CGRect): { 
          CGRect tmp = isFirst ? *(CGRect *) &first : va_arg(*args, CGRect); 
          memcpy(dst, &tmp, size); 
          break; 
         } 
    
         default: { 
          fprintf(stderr, "WARNING! Could not bind parameter of size %zu, unkown type! Going to have problems down the road!", size); 
          break; 
         } 
        } 
    } 
    
    id createBlock(id self, SEL _cmd) { 
        NSMethodSignature *methodSig = [self methodSignatureForSelector:_cmd]; 
    
        if (methodSig == nil) 
         return nil; 
    
        return ^(void *arg, ...) { 
         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; 
    
         [invocation setTarget:self]; 
         [invocation setSelector:_cmd]; 
    
         NSUInteger argc = [methodSig numberOfArguments]; 
         va_list args; 
         va_start(args, arg); 
    
         for (int argi = 2; argi < argc; argi++) { 
          const char *type = [methodSig getArgumentTypeAtIndex:argi]; 
    
          NSUInteger size; 
          NSUInteger align; 
    
          // get the size 
          NSGetSizeAndAlignment(type, &size, &align); 
    
          // find the right type 
          void *argument = alloca(size); 
    
          getArgFromListOfSize(&args, arg, size, align, argument, argi == 2); 
    
          [invocation setArgument:argument atIndex:argi]; 
         } 
    
         va_end(args); 
    
         [invocation invoke]; 
    
         // get the return value 
         if (methodSig.methodReturnLength != 0) { 
          void *retVal = alloca(methodSig.methodReturnLength); 
          [invocation getReturnValue:retVal]; 
    
          return *((void **) retVal); 
         } 
    
         return nil; 
        }; 
    } 
    

    はあなたが持つ任意の問題がある場合、私に教えてくださいこの実装!

  • +1

    非常に多くの魔法のようです(しかし、かなり涼しいです;詳細は実装を見ていませんでしたが、見た目はしっかりしています)。非可変引数呼び出しサイトを呼び出すと可変引数として扱うことはC ABIの違反であり、*は特定の引数リストを持つ特定のアーキテクチャを破ります。 – bbum

    +0

    @bbumは正しいですが、私は私の答えで、objcが最も使用される可能性が高いアーチ(x86、ARM)にこれを含めましたが、これは問題ではありません。実際、私は有名な "iOS app in C"の答えで同じことを使用しました。 –

    +0

    ARCがそれを保持しようとするので、最初の引数は 'id'と宣言されるべきではなく、' id'ではなく、 – newacct

    14

    私は学問的な観点からあなたの答えが好きでした。 +1、明らかに、あなたは何かを学びました。

    実用的な見地からは、タイピングの減少が非常に少ない脆弱性が増えているように見えますが、コールサイトでも情報が失われてしまいます。

    これの利点は、それが正確に明示的であるということである。

    -(void) reloadData { 
        ... 
        [[EventsManager instance] data:YES async:^(NSArray *events) { [self processEvents:events] }]; 
        ... 
    } 
    

    は1つが非同期コールバックブロックが引数を処理するために必要であることを認識し、selfprocessEvents:方法を行うために使用されることを、こと読みます実際の仕事。

    createBlock(self, @selector(processEvents:))は同じものの損失のある表現です。コールバックの明示的な引数と、その引数と呼び出されるメソッドとの間のマッピングが失われます(私はしばしば、メソッドを呼び出す前に軽量ロジックや引数の処理がある複数の引数を持つ上記のコールバックブロックを参照します)。

    varargs以外の呼び出しサイトを呼び出すとvarargsとして処理することは、C標準に違反し、特定の引数リストを持つ特定のABIでは動作しないことにも注意してください。

    +8

    @ RichardJ.RossIII - ここでは厳しくする必要があるかどうかわかりません。彼は自問自答の質問にいくつかの良い情報を追加しているので、私はこれが削除される壊れた窓としてはふさわしくないと思います。彼の答えはコメントではうまくいかないので、残っていても問題はありません。 –

    関連する問題