I started using blocks a lot and soon noticed that nil blocks cause bus errors:
typedef void (^SimpleBlock)(void); SimpleBlock aBlock = nil; aBlock(); // bus error
This seems to go against the usual behaviour of Objective-C that ignores messages to nil objects:
NSArray *foo = nil; NSLog(@"%i", [foo count]); // runs fine
Therefore I have to resort to the usual nil check before I use a block:
if (aBlock != nil) aBlock();
Or use dummy blocks:
aBlock = ^{}; aBlock(); // runs fine
Is there another option? Is there a reason why nil blocks couldn’t be simply a nop?
I'd like to explain this a bit more, with a more complete answer. First let's consider this code:
#import <Foundation/Foundation.h> int main(int argc, char *argv[]) { void (^block)() = nil; block(); }
If you run this then you'll see a crash on the block()
line that looks something like this (when run on a 32-bit architecture - that's important):
EXC_BAD_ACCESS (code=2, address=0xc)
So, why is that? Well, the 0xc
is the most important bit. The crash means that the processor has tried to read the information at memory address 0xc
. This is almost definitely an entirely incorrect thing to do. It's unlikely there's anything there. But why did it try to read this memory location? Well, it's due to the way in which a block is actually constructed under the hood.
When a block is defined, the compiler actually creates a structure on the stack, of this form:
struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };
The block is then a pointer to this structure. The fourth member, invoke
, of this structure is the interesting one. It is a function pointer, pointing to the code where the block's implementation is held. So the processor tries to jump to that code when a block is invoked. Notice that if you count the number of bytes in the structure before the invoke
member, you'll find that there are 12 in decimal, or C in hexadecimal.
So when a block is invoked, the processor takes the address of the block, adds 12 and tries to load the value held at that memory address. It then tries to jump to that address. But if the block is nil then it'll try to read the address 0xc
. This is a duff address, clearly, and so we get the segmentation fault.
Now the reason it must be a crash like this rather than silently failing like an Objective-C message call does is really a design choice. Since the compiler is doing the work of deciding how to invoke the block, it would have to inject nil checking code everywhere a block is invoked. This would increase code size and lead to bad performance. Another option would be to use a trampoline which does the nil checking. However this would also incur performance penalty. Objective-C messages already go through a trampoline since they need to look up the method that will actually be invoked. The runtime allows for lazy injection of methods and changing of method implementations, so it's already going through a trampoline anyway. The extra penalty of doing the nil checking is not significant in this case.
I hope that helps a little bit to explain the rationale.
For more information, see my blog posts.
Matt Galloway's answer is perfect! Great read!
I just want to add that there are some ways to make life easier. You could define a macro like this:
#define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil
It can take 0 – n arguments. Example of usage
typedef void (^SimpleBlock)(void); SimpleBlock simpleNilBlock = nil; SimpleBlock simpleLogBlock = ^{ NSLog(@"working"); }; BLOCK_SAFE_RUN(simpleNilBlock); BLOCK_SAFE_RUN(simpleLogBlock); typedef void (^BlockWithArguments)(BOOL arg1, NSString *arg2); BlockWithArguments argumentsNilBlock = nil; BlockWithArguments argumentsLogBlock = ^(BOOL arg1, NSString *arg2) { NSLog(@"%@", arg2); }; BLOCK_SAFE_RUN(argumentsNilBlock, YES, @"ok"); BLOCK_SAFE_RUN(argumentsLogBlock, YES, @"ok");
If you want to get the return value of the block and you are not sure if the block exists or not then you are probably better off just typing:
block ? block() : nil;
This way you can easily define the fallback value. In my example 'nil'.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With