简体   繁体   English

在Objective-C / C中,您可以编写包含2个块的函数吗?

[英]In Objective-C/C, can you write a function that combines 2 blocks?

I often find myself creating a "wrapper" block which just serves to execute a number of other blocks, usually with the same type signature. 我经常发现自己创建了一个“包装器”块,它只用于执行许多其他块,通常具有相同类型的签名。

Say I have 2 blocks with the same type signature: 假设我有2个具有相同类型签名的块:

MyBlockT block1 = ^(NSString *string, id object) {
    //1 does some work
};

MyBlockT block2 = ^(NSString *string, id object) {
    //2 does some other work
};

Is there some way to implement the magic function Combine() which would take 2 blocks: 有没有办法实现魔术函数Combine() ,它需要2个块:

MyBlockT combinedBlock = Combine(block1, block2); //hypothetical function

and be equivalent to doing: 并等同于:

MyBlockT combinedBlock = ^(NSString *string, id object) {
    block1(string, object);
    block2(string, object);
};

I know this only makes sense with blocks that return void , but that's all I'm interested in. 我知道这对于返回void块只有意义,但这就是我感兴趣的全部内容。

The Combine function needs only take in 2 blocks, if I have more I can just chain them. Combine功能只需要2个块,如果我有更多,我可以链接它们。 I'm at wits end on how to go about implementing this or whether it's even possible. 我知道如何实现这个或者是否可能。

PS I wouldn't mind if the solution involved C macros PS我不介意解决方案是否涉及C宏

EDIT 编辑

I'd like to be able to use the resulting block as a method argument, eg: 我希望能够将结果块用作方法参数,例如:

[UIView animateWithDuration:1 animations:someCombinedBlock];

Is this what you are looking for? 这是你想要的?

MyBlockT CombineBlocks(MyBlockT block1, MyBlockT block2)
{
    return [^(NSString *string, id object) {
        block1(string, object);
        block2(string, object);
    } copy];
}

The function creates a new block that calls the two given blocks sequentially. 该函数创建一个新块,按顺序调用两个给定的块。

Now up on GitHub, WoolBlockInvocation ! 现在在GitHub上, WoolBlockInvocation

This is a pair of classes, WSSBlockInvocation and WSSBlockSignature , along with some supporting code, that leverage libffi and the ObjC @encode strings which the compiler generates for Blocks to allow you to invoke a whole list of Blocks with the same set of arguments. 这是一对WSSBlockInvocationWSSBlockSignature类,以及一些支持代码,它们利用libffi和ObjC @encode字符串,编译器为它们生成的Blocks允许您使用相同的参数集调用整个Blocks列表。

Any number of Blocks can be added to an invocation object, provided their signatures -- meaning return type and number and types of arguments -- match. 可以将任意数量的块添加到调用对象,只要它们的签名(即返回类型和参数的数量及类型)匹配即可。 After setting arguments on the invocation object, the Blocks can be invoked in turn, with the return values, if any, stored for later access. 在调用对象上设置参数之后,可以依次调用这些块,并存储返回值(如果有)以供以后访问。

The piece that you're particularly interested in, sewing that list of Blocks up into a single Block, is provided by the invocationBlock method of WSSBlockInvocation . 您特别感兴趣的部分,将块列表缝合到单个块中,由WSSBlockInvocationinvocationBlock方法提供。

- (id)invocationBlock
{
    return [^void (void * arg1, ...){
        [self setRetainsArguments:YES];
        va_list args;
        va_start(args, arg1);
        void * arg = arg1;
        NSUInteger numArguments = [blockSignature numberOfArguments];
        for( NSUInteger idx = 1; idx < numArguments; idx++ ){

            [self setArgument:&arg atIndex:idx];

            arg = va_arg(args, void *);
        }
        va_end(args);

        [self invoke];

    } copy];
}

This returns a Block that (ab)uses varargs functionality to defer assigning arguments until that encapsulating Block is actually invoked itself. 这将返回一个块,其中(ab)使用varargs功能来推迟分配参数,直到实际上自己调用封装块为止。 You can thus do the following: 您可以这样做:

WSSBlockInvocation * invocation = [WSSBlockInvocation invocationWithBlocks:@[animationBlockOne, animationBlockTwo]];

void (^combinedAnimation)(void) = [invocation invocationBlock];

[UIView animateWithDuration:1 animations:combinedAnimation];

Of course, if you're just worried about Blocks for animations, that take no arguments and have no return value, constructing a wrapper Block is trivial: 当然,如果您只是担心动画块,该块不带任何参数且没有返回值,则构造包装器Block是很简单的:

void (^combinedAnimation)(void) = ^{
    animationBlock();
    anotherAnimationBlock();
    // etc.
};

You only need my code if you need to wrap a set of Blocks and invoke them all with the same set of arguments. 仅当需要包装一组Block并使用相同的一组参数调用它们时,才需要我的代码。

NB I have tested this on OS X on x86_64, but not on any other platform . 注意:我已经在x86_64的OS X上对此进行了测试,但是没有在任何其他平台上进行过测试。 I hope it works on ARM under iOS, but varargs is famously "not portable" and it may not. 我希望它可以在iOS下的ARM上运行,但是varargs众所周知是“不可移植的”,而且可能不是。 Caveat compilor, and let me know if something breaks. 请注意编译器,如果有问题请通知我。

Here is a fun abuse of varargs: 这是一个有趣的varargs滥用:

id combine(id block, ...)
{
        NSMutableArray *blocks = [NSMutableArray array];
        //[blocks addObject:block];
        va_list objlist;
        va_start(objlist, block);
        //while((obj = va_arg(ap, id))) { // }
        for(id obj = block; obj; obj = va_arg(objlist, id)) {
                [blocks addObject:[obj copy]];
        }
        va_end(objlist);
        void (^wrapper)(id,...) = ^(id arg, ...) {
                NSMutableArray *args = [NSMutableArray array];
                va_list arglist;
                va_start(arglist, arg);
                for(id x = arg; x; x = va_arg(arglist, id)) {
                        [args addObject:x];
                }
                va_end(arglist);

                for(void (^blk)() in blocks) {
                        blk(args);
                }
        };
        return [wrapper copy];
}

int main() {
        NSString *fmt = @"-%d-\n%@\n---";
        void (^foo)() = combine(^(NSArray *a){ NSLog(fmt, 1, a); },
                                ^(NSArray *a){ NSLog(fmt, 2, a); }, nil);
        foo(@"first", @"second", nil);
        return 0;
}

You must define each block to accept an NSArray of arguments, and both the combine and resulting block invocation must have at least one argument and end in nil . 你必须定义每个块接受的参数一个NSArray,并同时combine并导致块调用必须至少有一个参数,并在结束nil

If you know the method signature ahead of time, you can work around the NSArray and block arguments restriction by altering the wrapper block appropriately. 如果您提前知道方法签名,则可以通过适当地更改包装块来解决NSArray并阻止参数限制。

Since you don't mind macros 因为你不介意宏

#define combinedBlock(string, object)      \
         block1((string), (object) )       \            
         block2((string), (object) )

if you need to perform 2 or more animations simultaneously then RZViewActions is everything you need. 如果您需要同时执行2个或更多动画,那么RZViewActions就是您需要的一切。 Its code looks like almost as animateWithDuration:... calls but with additional features. 它的代码看起来几乎像animateWithDuration:...调用但具有其他功能。

If you need to perform ANY blocks simultaneously then you need something like ReactiveCocoa . 如果你需要同时执行任何块,那么你需要像ReactiveCocoa这样的东西。 But I suggest you PromiseKit (simply because it is easier). 但我建议你PromiseKit (因为它更容易)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM