74

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?

hfossli
  • 22,616
  • 10
  • 116
  • 130
zoul
  • 102,279
  • 44
  • 260
  • 354

4 Answers4

149

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.

For more information, see my blog posts.

starball
  • 20,030
  • 7
  • 43
  • 238
mattjgalloway
  • 34,792
  • 12
  • 100
  • 110
40

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'.

hfossli
  • 22,616
  • 10
  • 116
  • 130
9

Caveat: I'm no expert in Blocks.

Blocks are objective-c objects but calling a block is not a message, although you could still try [block retain]ing a nil block or other messages.

Hopefully, that (and the links) helps.

Stephen Furlani
  • 6,794
  • 4
  • 31
  • 60
  • Thank you, interesting links. I know that calling a block is not the same as sending it a message, but conceptually it would be nice if nil blocks were as forgiving as nil objects. – zoul Nov 10 '10 at 14:22
  • You may be able to add a category to the `__block` type... but I'm not sure. `#define nilBlock ^{}` may also make your life easier. – Stephen Furlani Nov 10 '10 at 14:25
  • I thought about the `nilBlock` approach, unfortunately the typing gets in the way – creating a separate nil value for each block type isn’t much fun. – zoul Nov 10 '10 at 14:30
  • I have no idea if you can subclass Blocks, but adding a message `[block call]` which does an internal check might help. Not sure how close blocks are to ObjC objects. – Stephen Furlani Nov 10 '10 at 14:31
  • That’s another good idea, but I’m afraid it would get hairy once you want to pass some primitive-type arguments. – zoul Nov 10 '10 at 14:33
  • Yeah, then you're working in `NSDictionary` pattern, primitives would have to get passed as `NSNumbers` and the like. I use a combination of Objective-C++, templates, and inlines to just `_num(primitive)` those things. – Stephen Furlani Nov 10 '10 at 14:40
  • 3
    I generally just do block ? block() : nil; which is terse enough for me and is transparent in what your doing... – Patrick Pijnappel Aug 14 '12 at 09:50
2

This is my simple nicest solution… Maybe there is possible to write one universal run function with those c var-args but I don’t know how to write that.

void run(void (^block)()) {
    if (block)block();
}

void runWith(void (^block)(id), id value) {
    if (block)block(value);
}
Renetik
  • 5,887
  • 1
  • 47
  • 66