9

私はブロックが大好きで、とてもクールです。

ただし、ブロックによってコードが乱雑になり、すべてを Xcode 内に折りたたまないと読みにくくなる可能性があることがわかりました (これは好きではありません)。

コードを読みやすくするために、コードを論理メソッド (セレクター) に分割するのが好きですが、(表面的には) これはディスパッチ、AFNetworking、およびその他のいくつかのフレームワークでは容易にできないようです。

また、デリゲート アプローチも気にしません。つまり、メソッドに好きなように名前を付けることができず、他の人が必要だと思うものに頼ることができないからです。

したがって、次のような一連のグルー コードを記述しなくても、次のようになります。

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

代わりに、次のようなことができます。

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

どちらが読みやすいですか(私にとって)。

私たちが Objective-C で持っているパワーとランタイムがあれば、これは可能になるはずですよね? しかし、私はそのようなものを見たことがありません。

4

2 に答える 2

14

学術的な観点からあなたの答えが気に入りました。+1 そして、明らかに、あなたは何かを学びました。

実用的な観点からは、タイピングがほとんど減らないのに非常に多くの脆弱性が追加されているように見えますが、呼び出しサイトでの情報の損失にもつながります.

これの利点は、それが正確に明示的であることです:

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

それを読むと、引数を処理するために非同期コールバック ブロックが必要であり、processEvents:メソッド onselfが実際の作業を行うために使用されることがわかります。

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

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

于 2013-04-27T21:34:49.590 に答える
6

はい、これは確かに可能ですが、このソリューションは ABI 固有のものであり (すべてのプラットフォームで動作することが保証されているわけではありません)、メソッドに関して実行時に利用可能な情報を広範に利用します。

最初に行う必要があるのは、ブロックでラップするメソッドに関する情報を取得することです。NSMethodSignatureこれは、次のような情報を含むを介して行われます。

  • 引数の数
  • 各引数のサイズ (バイト単位)
  • 戻り型のサイズ

これにより、そのメソッドの特定のコードを使用せずに (ほぼ) 任意のメソッドをラップできるため、再利用可能な関数を作成できます。

次に、実行時にメソッド呼び出しを安全にディスパッチする方法が必要です。を介してこれを行います。これによりNSInvocation、実行時に動的で安全なメソッド呼び出しを作成できるようになります。

第三に、渡された任意の数の引数を取ることができるブロックと、それをディスパッチする必要があります。これは C のva_listAPI を介して行われ、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;
    };
}

この実装に問題がある場合はお知らせください。

于 2013-04-27T20:04:01.013 に答える