In Objective-C/C, can you write a function that co

2020-02-07 05:11发布

问题:

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:

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:

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.

The Combine function needs only take in 2 blocks, if I have more I can just chain them. I'm at wits end on how to go about implementing this or whether it's even possible.

P.S. I wouldn't mind if the solution involved C macros

EDIT

I'd like to be able to use the resulting block as a method argument, e.g.:

[UIView animateWithDuration:1 animations:someCombinedBlock];

回答1:

Now up on 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.

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.

- (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. 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:

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.

N.B. I have tested this on OS X on x86_64, but not on any other platform. I hope it works on ARM under iOS, but varargs is famously "not portable" and it may not. Caveat compilor, and let me know if something breaks.



回答2:

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.



回答3:

Here is a fun abuse of 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.

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.



回答4:

Since you don't mind macros

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


回答5:

if you need to perform 2 or more animations simultaneously then RZViewActions is everything you need. Its code looks like almost as animateWithDuration:... calls but with additional features.

If you need to perform ANY blocks simultaneously then you need something like ReactiveCocoa. But I suggest you PromiseKit (simply because it is easier).