Question

The question here is more of an educational one. I began to think of this an hour ago while flipping around a lego block (silly, I know).

A block is an object created on stack, from what I understand.

Let say A is an object, which means we can do:

[A message];

Based on that, if a block is an object, we could also do:

[block message];

Am I correct?

And when the runtime sees that, it would call:

objc_msgSend(block, @selector(message), nil);

So my question is, how can we send a block a message?

And if that is possible, I would imagine that it would also be possible to send a block a message with the arguments being blocks as well?

And, if we could call a block by doing:

block();

Does that mean we could even make a block as a message (SEL) as well, because blocks have the signature void (^)(void) which resembles that of a method?

Because if it would be possible, then the following would really surprise me:

objc_msgSend(block, @selector(block), block);

or:

objc_msgSend(block1, @selector(block2), block3);

I hope my imagination is not running a bit wild and that my understanding is not off here (correct me, if it is).

Was it helpful?

Solution

Blocks are objects only for the purposes of storage and referencing. By making them objects, blocks can be retain/release'd and can, therefore, be shoved into arrays or other collection classes. They also respond to copy.

That's about it. Even that a block starts on the stack is largely a compiler implementation detail.

When a block's code is invoked, that is not done through objc_msgSend(). If you were to read the source for the block runtime and the llvm compiler, then you'd find that:

  • a block is really a C structure that contains a description of the data that has been captured (so it can be cleaned up) and a pointer to a function -- to a chunk of code -- that is the executable portion of the block

  • the block function is a standard C function where the first argument must always be a reference to the block. The rest of the argument list is arbitrary and works just like any old C function or Objective-C method.

So, your manual calls to objc_msgSend() treat the block like any other random ObjC object and, thus, won't call the code in the block, nor, if it did (and there is SPI that can do this from a method... but, don't use it) could it pass an argument list that was fully controllable.


One aside, though of relevance.

imp_implementationWithBlock() takes a block reference and returns an IMP that can be plugged into an Objective-C class (see class_addMethod()) such that when the method is invoked, the block is called.

The implementation of imp_implementationWithBlock() takes advantage of the layout of the call site of an objective-c method vs. a block. A block is always:

blockFunc(blockRef, ...)

And an ObjC method is always:

methodFunc(selfRef, SEL, ...)

Because we want the imp_implementationWithBlock() block to always take the target object to the method as the first block parameter (i.e. the self), imp_implementationWithBlock() returns a trampoline function that when called (via objc_msgSend()):

- slides the self reference into the slot for the selector (i.e. arg 0 -> arg 1)
- finds the implementing block puts the pointer to that block into arg 0
- JMPs to the block's implementation pointer

The finds the implementing block bit is kinda interesting, but irrelevant to this question (hell, the imp_implementationWithBlock() is a bit irrelevant, too, but may be of interest).


Thanks for response. It's definitely an eye opener. The part about blocks calling is not done thru objc_msgSend() tells me that it is because blocks are not part of the normal object-hierachry (but coda's mentioning of NSBlock seems to refute what I understand so far, because NSBlock would make it part of the object-hierachy). Feel free to take a stab at me, if my understanding is still off so far. I am very interested in hearing more about the followings 1: the SPI and the way (how) to call that method. 2: the underlying mechanisms of: sliding the self reference into the slot. 3: finds the implementing block and puts the pointer to that block into arg 0. If you have time to share and write a bit more about those in detail, I am all ears; I find this all very fascinating. Thanks very much in advance.

The blocks, themselves, are very much just a standard Objective-C object. A block instance contains a pointer to some executable code, any captured state, and some helpers used to copy said state from stack to heap (if requested) and cleanup the state on the block's destruction.

The block's executable code is not invoked like a method. A block has other methods -- retain, release, copy, etc... -- that can be invoked directly like any other method, but the executable code is not publicly one of those methods.

The SPI doesn't do anything special; it only works for blocks that take no arguments and it is nothing more than simply doing block().

If you want to know how the whole argument slide thing works (and how it enables tail calling through to the block), I'd suggest reading this or this. As well, the source for the blocks runtime, the objc runtime, and llvm are all available.

That includes the fun bit where the IMP grabs the block and shoves it into arg0.

OTHER TIPS

Yes, blocks are objects. And yes, that means you can send messages to them.

But what message do you think a block responds to? We are not told of any messages that a block supports, other than the memory management messages retain, release, and copy. So if you send an arbitrary message to a block, chances are that it will throw a "does not recognize selector" exception (the same thing that would happen if you sent an arbitrary message to any object you don't know the interface of).

Invoking a block happens through a different mechanism than message sending, and is magic implemented by the compiler, and not exposed to the programmer otherwise.

Blocks and selectors are very different. (A selector is just an interned string, the string of the method name.) Blocks and IMPs (functions implementing methods) are somewhat similar. However, they are still different in that methods receive the receiver (self) and the selector called as special parameters, whereas blocks do not have them (the function implementing the block only receives the block itself as a hidden parameter, not accessible to the programmer).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top