5

I have a method that does an asynchronous network call, and then passes the success or failure results back via blocks:

- (void) loginWithSuccess:(void (^)(id responseObject))success failure:(void (^)(NSError* error))failure {
...
  if(error) {
    failure(error);
  }
  else {
    success(responseObject);
  }
}

I noticed that if I call this method and pass in nil as my blocks, my application will crash with EXEC_BAD_ACCESS:

[manager loginWithWithSuccess:nil failure:nil];

But, if I pass in empty blocks, it runs fine:

[manager loginWithWithSuccess:^(id responseObject){} failure:^(NSError *error){}];

I assume this is because at runtime you can't pass parameters to nil? So, when defining my methods that take blocks, should I always check that the blocks are not nil before calling them?

hodgesmr
  • 2,765
  • 7
  • 30
  • 41
  • 3
    The library you're using should be guarding against a `nil` Block, because `doThatThingYouDoWithCompletion:aBlock error:nil` should be the way you say "I don't want to hear about errors". – jscs Jan 14 '14 at 06:03
  • @JoshCaswell thanks, I'll put in nil checks. Also thanks for pointing to the other answer. – hodgesmr Jan 14 '14 at 06:09
  • 2
    @RajanBalana: Uh, no. `NULL == nil`. Neither can be called as a function. `dispatch_block_t crash = NULL; crash();` – jscs Jan 14 '14 at 06:09
  • @JoshCaswell You are right ! – Rajan Balana Jan 14 '14 at 06:11

2 Answers2

3

Just looking at Apple's Frameworks, some methods with block parameters accept NULL/nil as the block argument (e.g. animateWithDuration:animations:completion:), others don't (e.g. enumerateObjectsUsingBlock:).

If you're designing an API, you have to make that decision. If it makes sense to not have a block, you should accept nil and check before executing the block, otherwise you should throw an assertion like [@[] enumerateObjectsUsingBlock:nil] does:

'NSInvalidArgumentException', reason: '*** -[NSArray enumerateObjectsUsingBlock:]: block cannot be nil'

So why are you getting EXEC_BAD_ACCESS?

When invoking a block you are dereferencing the address, which you obviously can't do if it's not pointing to an actual block. There is a great explanation in this answer.

Community
  • 1
  • 1
Sebastian
  • 7,670
  • 5
  • 38
  • 50
  • The important difference between the two kinds of methods being, of course, can the method still do its job without the Block? – jscs Jan 14 '14 at 06:43
0

Please try the following example to understand call block logic:

void (^printString)(NSString*) = ^(NSString* arg) {
    NSLog(@"%@", arg);
};

//(1) printString = ^(NSString* arg){};
//(2) printString = NULL;

printString(@"1");

In the console you will see "1". Then uncomment (1) and console reveals "Called" but no errors! And at last, uncomment (2) and get EXEC_BAD_ACCESS. It is your situation exactly.

Called block must be not NULL or nil. You need to check existence of passing blocks in loginWithSuccess before call.

malex
  • 9,874
  • 3
  • 56
  • 77