2

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];
lmirosevic
  • 15,787
  • 13
  • 70
  • 116
  • 1
    Progress report: I've got this basically working. Need to do a little more thorough testing and clean it up. By the end of the week, I expect. It does require a bit of infrastructure: two new classes, a handful of helper functions, and libffi. – jscs Jul 16 '13 at 20:17
  • @JoshCaswell I'm looking forward to seeing what you've come up with – lmirosevic Jul 16 '13 at 21:55

5 Answers5

4

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.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • In essence yes, except it should be generic in the sense that it can take any parameter list. With the restriction that all the blocks are the same type, and that they all return void. – lmirosevic Jul 14 '13 at 11:03
  • @lms: I see. That is probably a lot more difficult. Even if you use the internal implementation of blocks (http://clang.llvm.org/docs/Block-ABI-Apple.html), one problem that I see is how to invoke block1 or block2 without having an explicit parameter list. I don't know of a method how a function can just *pass through* all its arguments to another function. – Martin R Jul 14 '13 at 11:44
  • Could one just (ab)use a macro with ... and \_VA_ARGS\_ ? – lmirosevic Jul 14 '13 at 11:54
  • 1
    Arbitrarily passing function arguments around requires some assembler (libffi would help here), and still necessitates knowing the signature at some point, @lms. You can't just sling bytes around without knowing how many to put where. A class that is a variation of `NSInvocation` ought to be able to accomplish this, which sounds like a fun project. I will put it on my weekend "mess around with this" list. Oh! It _is_ the weekend. – jscs Jul 14 '13 at 19:48
  • @JoshCaswell please mess away ;) – lmirosevic Jul 15 '13 at 09:23
2

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • Sorry this took me an extra week; I got sidetracked by [a sort of related problem](http://stackoverflow.com/questions/17689616/how-to-pass-an-array-to-an-objc-method-that-expects-var-args-eg/17711424#17711424) and then had a devil of a time with the object return values -- ARC was emitting an unexpected release when retrieving. – jscs Jul 26 '13 at 22:51
  • This is awesome! I'll try it out as soon as GitHub is back online. Sorry for the late response, I was on holiday when you posted this. Thank you. – lmirosevic Aug 06 '13 at 07:55
  • My pleasure. It was a fun project; I hope it works out for you. Please do let me know about any problems. – jscs Aug 06 '13 at 07:56
  • It does not work currently on the Simulator. The flexibility would be really useful. – Leonardo Marques Dec 13 '15 at 23:04
  • I'll take a look when I get a chance, @LeonardoMarques. What are your various version numbers? – jscs Dec 13 '15 at 23:20
  • I'm using XCode 7.1.1, testing on the iOS Simulator, seems that the problems com from libffi, none of the versions I tried seemed to solve the problem so I don't know if its fixable. If you could throw a podspec in there that would be great too ;) Thank you – Leonardo Marques Dec 13 '15 at 23:26
1

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.

Kevin
  • 53,822
  • 15
  • 101
  • 132
0

Since you don't mind macros

#define combinedBlock(string, object)      \
         block1((string), (object) )       \            
         block2((string), (object) )
levengli
  • 1,091
  • 7
  • 18
  • Thanks, but that wouldn't work when passing it as an argument to a method or when assigning it to a variable, it doesn't really create a block that calls through to the originals, it just expands the single call into 2 separate calls. – lmirosevic Jul 14 '13 at 10:55
0

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).

Vyachaslav Gerchicov
  • 2,317
  • 3
  • 23
  • 49