The __block specifier in Objective-C and Why its so mis-understood

I’ve seen a lot of source code recently where people are mis-using the __block “specifier” that ships with modern Objective-C runtimes. I’ve always had the opinion that if you are to use __block for an Object then you should design around it and avoid, reserving its use for primitives. Regardless of that, here is a summary of my understanding of __block to share with any other keen readers who may be interested.

__block is used as variables outside the scope of a block become readonly once inside a block. For example

 int num=1;
void (^someBlock)(void) = ^{
num = 2;
};
someBlock();

Would cause a compiler error asking for the __block specifier to be used. so in this case you can try:

 __block int num=1;
void (^someBlock)(void) = ^{
num = 2;
};
someBlock();

and num will contain the correct value after block execution.

Straight forward right? So what about the following example:

 __block NSMutableArray *someArray = @[@"Hello",@"World"];
void (^someBlock)(void) = ^{
[someArray addObject:@"Goodbye"];
};
someBlock();

It’s wrong… you don’t need __block in this case… why? because you’re not assigning a value to the captured “variable” someArray, rather you’re just sending a message. I often see this and wonder why.

The __block specifier is actually a storage-class specifier, to give you an idea of what this means, the following are also storage-class specifiers in C. extern, typedef, static and so on.

Why don’t I like __block a great deal then? Read on for more…

Well… it seems to create a lot of un-neccesary code under the hood. Take a look at the example given above with __block int num = 1; and the simple closure/block. This actually generates the following code:

struct __Block_byref_num_0 { void *__isa;
__Block_byref_num_0 *__forwarding; int __flags;
int __size;
int num;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_num_0 *num;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
__Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) { impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc; }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num;
(val->__forwarding->num) = 1; }
static void __main_block_copy_0( struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign(&dst->num, src->num, BLOCK_FIELD_IS_BYREF); }
static void __main_block_dispose_0(struct __main_block_impl_0*src) { _Block_object_dispose(src->num, BLOCK_FIELD_IS_BYREF);
}
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0,
sizeof(struct __main_block_impl_0),

__main_block_copy_0, __main_block_dispose_0
};
int main() {
__Block_byref_num_0 num = { 0,
&num,
0, sizeof(__Block_byref_num_0), 10
};
blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &num, 0x22000000);
return 0; }

Now… if you’re still reading at this point, well done 🙂 and you may have realised my point, __block isn’t as magically as it first seems. Anyway, this may be the most boring blog post ever… However, think again if you’re typing __block and definitely don’t use it unnecessarily. Saying all this, I could be very wrong, feel free to point that out to me on twitter 🙂 @italoarmstrong.

 

 

Leave a Reply