37

I was just thinking, as you can treat Blocks like objects if I create two of them and then add them to an NSArray is there a way to execute them from the array?

int (^Block_001)(void) = ^{ return 101; };
int (^Block_002)(void) = ^{ return 202; };
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];

EDIT: Update for clarity Per @davedelong's excellent answer

int (^Block_001)(void) = [^{ return 101; } copy];
int (^Block_002)(void) = [^{ return 202; } copy];
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];

[Block_001 release];
[Block_002 release];
fuzzygoat
  • 26,573
  • 48
  • 165
  • 294
  • 2
    Nice set of answers.... I saw the title and was hoping for some easy rep points. Folks got it quite thoroughly covered. :) – bbum Jul 15 '10 at 18:09
  • Just a quick point, if you don't copy/release the block will be in stack... So if the stack gets destroyed the app will crash right? – fzaziz Dec 27 '12 at 07:00

3 Answers3

60

@KennyTM and @David are correct, but your code is potentially wrong. Here's why:

When creating an NSArray with objects, it will retain the objects put into it. In the case of blocks, it's using the Block_retain function. This means that the array has retained the blocks that you created, but that live on the stack (blocks are one of the very rare examples of Objective-C objects that can be created on the stack without delving into absurd tricks). That means that as soon as this method exits, your array now points to garbage, because the blocks it was pointing to no longer exist. To do this properly, you should do:

int (^Block_001)(void) = [^{ return 101; } copy];
int (^Block_002)(void) = [^{ return 202; } copy];
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];

[Block_001 release];
[Block_002 release];

By invoking copy on the block, you're explicitly moving the block off of the stack and onto the heap, where it can safely remain after the method/function exits. Then after you've added the blocks to the array, you have to balance your copy (because of the NARC rule) with a subsequent call to release. Make sense?

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • Hi Dave, yes I understand, I was not aware of blocks being created on the stack (only just started looking at blocks today). Much appreciated. – fuzzygoat Jul 15 '10 at 17:40
  • Hi Dave, I found this question and David Gelhar's accepted answer so I implemented it and it worked quite well, it **didn't** crash. Reading further, your answer made sense but does not explain why david's code is not crashing (immediately?). Do you have any clue? – Tieme Jan 18 '13 at 14:43
  • 1
    @Tieme David's code doesn't crash immediately because when you execute the block stored in the array, you're still in the same lexical scope in which the block was created. If you were to return that array as the return value of the method, and then try and execute the blocks in the calling function, then it would crash. Although if you're using ARC it probably won't, because the compiler will insert the necessary copy call for you. – Dave DeLong Jan 18 '13 at 15:01
  • Okay got, it, I'll test it. But although the compiler will probably insert the copy you shouldn't leave it out like `release` and `retain` right? – Tieme Jan 18 '13 at 15:14
29

Sure, you just invoke it with () like any other block, but you need to typecast the value you retrieve from NSArray. Here's an example (with an added typedef, because otherwise my head hurts):

typedef int (^IntBlock)(void);
IntBlock Block_001 = ^{ return 101; };
IntBlock Block_002 = ^{ return 202; };
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];
int x = ((IntBlock)[array objectAtIndex:0]) (); // now x == 101
David Gelhar
  • 27,873
  • 3
  • 67
  • 84
  • ok, so not being familiar with function pointers am I getting this right? The typedef is defining "IntBlock" that is a pointer to a block that returns an int and takes no arguments. I think I get it, and looking at the alternative (which I am pretty sure I would get wrong) I appreciate your decision to go with the typedef :) Much appreciated. – fuzzygoat Jul 15 '10 at 17:44
  • 1
    You don't need to cast it. It works just fine to do `int (^block)() = [array objectAtIndex:0]`. A pointer is a pointer is a pointer, after all, at least in low-level languages like `C` and its derivatives. – aroth Dec 14 '11 at 23:09
  • @aroth Sure, you can also use an assignment to a variable of the appropriate type to perform the "conversion" of the generic `id` type returned from `-objectAtIndex`. What you *can't* do is `int y = [array objectAtIndex:0]();` (the compiler will complain "called type `id` is not a function or function pointer"). One way (cast) or another (assignment to a block pointer), you need to tell the compiler "I know this thing I'm pulling out of the array is a block, so when I say `()`, please call it!" – David Gelhar Dec 15 '11 at 17:53
7

Of course you can.

int (^x)(void) = [array objectAtIndex:0];
printf("%d\n", x()); // prints 101.
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • I did consider this, very interesting. Can you just clarify for me, am I right in referring to "int (^x)(void)" as a pointer to a block? Just trying to make sure I am getting the terminology correct. – fuzzygoat Jul 15 '10 at 18:20