如何创建一个“包裹”的块?目标/选择器对?

时间:2013-04-27 20:04:01

标签: objective-c selector objective-c-blocks objective-c-runtime

我喜欢积木,而且非常酷。

然而,我发现块可能会混乱我的代码并使其更难阅读而不将所有这些代码都放在Xcode中(我不喜欢这样做)。

我喜欢将我的代码拆分为逻辑方法(选择器)以使其更易于阅读,但是(表面上)表明,使用dispatch,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和它的运行时的强大功能,这个应该是可能的,不是吗?不过,我还没有看到这样的事情。

2 个答案:

答案 0 :(得分:14)

我从学术角度喜欢你的答案; +1,显然,你学到了一些东西。

从实际的角度来看,这似乎是一个非常大的增加的脆弱性,几乎没有减少打字,同时它也导致呼叫网站的一些信息丢失。

这样做的好处是它完全明确:

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

阅读它,可以看到需要异步回调块来处理参数,并且processEvents:上的self方法将用于执行实际工作。

表达式createBlock(self, @selector(processEvents:))是一个有损的表示;它失去了回调的显式论证以及该论证和被调用方法之间的映射(我经常看到上面的回调块有多个参数,其中在调用方法之前有一些轻量级逻辑和/或参数处理)。 p>

另请注意,在调用时将非varargs调用站点作为varargs处理是违反C标准的,并且不适用于具有某些参数列表的某些ABI。

答案 1 :(得分:6)

是的,这确实是可行的,但这个解决方案是特定于ABI的(不保证可以在所有平台上运行),并广泛使用运行时可用的有关方法的信息。

我们首先要做的是获取有关我们使用块包装的方法的信息。这是通过NSMethodSignature完成的,其中包含以下信息:

  • 参数数量
  • 每个参数的大小(以字节为单位)
  • 返回类型的大小

这允许我们(几乎)包装任何没有该方法特定代码的方法,从而创建一个可重用的函数。

其次,我们需要一种在运行时安全地调度方法调用的方法。我们通过NSInvocation执行此操作,这使我们能够在运行时创建动态,安全的方法调用。

第三,我们需要一个可以传入任意数量参数的块,并调度它。这是通过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;
    };
}

如果您对此实施有任何疑问,请与我们联系!