4

This seems like a very strange interaction to me but at the same time it not only works but throws no warnings or errors in the process. Just looking to get some better understanding of blocks in general and why something like this could be right or wrong.

Is there any reason why something like this shouldn't be done?

NSArray *array = [NSArray arrayWithObjects:^{NSLog(@"Block 1");}, ^{NSLog(@"Block 2");}, ^{NSLog(@"Block 3");}, nil];

for (id block in array) {
    [block invoke];
}
Ryan Poolos
  • 18,421
  • 4
  • 65
  • 98

3 Answers3

4

Putting Blocks into NSArrays is fine; they're objects. In fact, they inherit from NSObject.

You do need to copy, them, however. Those Blocks are created on the stack and need to be moved to the heap in order to live past the end of the current method. If you're using ARC, this is easy:

NSArray *array = [NSArray arrayWithObjects:[^{NSLog(@"Block 1");} copy], ...

Under MRR, you need to balance that copy, so you have two unpleasant options: use temps, or enumerate the array right after creating it and send release to all its members.

Sending invoke, on the other hand, isn't completely kosher, because that's a private method. The only fully-API-compliant way to invoke a Block is with function-call syntax:

typedef GenericBlock dispatch_block_t;
for( GenericBlock block in array ){
    block();
}
jscs
  • 63,694
  • 13
  • 151
  • 195
  • Exactly the type of thing I was hoping for — random info about blocks I had missed. Didn't know invoke was private. Didn't know I had to copy them either. Thanks very much. – Ryan Poolos Jul 26 '12 at 20:31
  • 1
    "you have two unpleasant options" You can also autorelease – newacct Jul 26 '12 at 21:06
  • 1
    @newacct: Okay, three unpleasant options. :) – jscs Jul 27 '12 at 06:47
  • 1
    @JoshCaswell: yeah but it's much simpler to write than those two options. You can write the autorelease inline, without adding any additional lines anywhere else. – newacct Jul 27 '12 at 08:43
  • @newacct: Simpler indeed, but ugly. – jscs Jul 27 '12 at 18:02
1

Sure, that's fine. Why wouldn't it be fine?

In languages like JavaScript this technique is commonplace when registering event handlers.

object.clickHandlers.push(function() { doStuff() });
object.clickHandlers.push(function() { doMoreStuff() });

I see no reason that similar techniques couldn't be used with ObjC blocks, as they are real objects.

The more interesting question to me though, is if this pattern is the best choice for whatever your goal is. Which you haven't really told us.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • I don't have a particular goal in mind for this just a thought that come to mind and when the compiler didn't throw any warnings or errors I thought it was a bit strange. I'm not versed in Java or Javascript but I had heard thats basically where blocks came from so this makes sense. – Ryan Poolos Jul 26 '12 at 19:56
  • 1
    Indeed, but like most design patterns, how "right" it is depends on how it's being used. It's a fine tool in the toolbox, just be sure it's the right tool for the job. – Alex Wayne Jul 26 '12 at 20:28
1

Blocks in Objective-C are "first-class citizen" objects. Whatever you can do to a regular object, be it passing as a parameter, storing in an array or a dictionary, and so on, you can do it to block objects as well.

For example, an array of block objects may be useful to encode a sequence of actions that is not known at compile time; a dictionary of block objects keyed by strings could be useful in implementing a scripting language, and so on.

The best way to call a block retrieved from a collection is casting it to its proper type, and using the regular block invocation syntax on it.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523