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.