GCD Asynchronous Block and EXC_BAD_ACCESS

This crash is quite rare, but it happened often enough to convince me that I am doing it wrong.

This is an API call made using dispatch of asynchronous main-thread messages and sending a barrier in a user-side parallel queue (the barrier is used because we modify data that is read elsewhere through unsafe calls in one queue).

The goal is to make an HTTP request asynchronously, and then run dispatch_barrier_async to process the received data.

An accident occurs when the dispatch_barrier_async call copies the variables that are used in the passed block. I believe that the variable was freed before it was used in the block, but given how I declare it (as the __block variable), I donโ€™t see how this can happen (if the problem is not with the other side of the assignment operator .. .).

Here's the stack trace:

#0 0x00004f44 in __Block_byref_object_copy_ at /blah/ABEvent.m:156 #1 0x0000582d in __copy_helper_block_ at /blah/ABEvent.m:191 #2 0x02cf3be2 in _Block_call_copy_helper () #3 0x02cf3681 in _Block_copy_internal () #4 0x02c25526 in _dispatch_Block_copy () #5 0x02c26802 in dispatch_barrier_async () #6 0x00004e71 in __18+[ABEvent fetch]_block_invoke at /blah/ABEvent.m:159 #7 0x00056e77 in __88-[ABClient get:parameters:success:failure:]_block_invoke_2 at /blah/ABClient.m:375 #8 0x02c2553f in _dispatch_call_block_and_release () #9 0x02c37014 in _dispatch_client_callout () #10 0x02c277d5 in _dispatch_main_queue_callback_4CF () #11 0x02facaf5 in __CFRunLoopRun () #12 0x02fabf44 in CFRunLoopRunSpecific () #13 0x02fabe1b in CFRunLoopRunInMode () #14 0x02f517e3 in GSEventRunModal () #15 0x02f51668 in GSEventRun () #16 0x01ef1ffc in UIApplicationMain () #17 0x0005164d in main at /blah/main.m:3 #18 0x00002db5 in start () 

And the code:

 + (void)fetch { ABBlock _success = ^(ABMessage *m) { __block NSMutableArray *fetched = [NSMutableArray arrayWithArray:m.params[@"live"]]; [fetched addObjectsFromArray:m.params[@"soon"]]; // EXC_BAD_ACCESS (top of stack) dispatch_barrier_async([ABEvent eventQueue], ^{ // CRASHED IN BLOCK INVOKE (stack line 6) NSMutableArray *events = [NSMutableArray array]; for (NSDictionary *d in fetched) { [events addObject:[ABEvent eventWithDictionary:d]]; } AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; appDelegate.events = events; events = nil; }); }; [[ABClient sharedInstance] events:_success failure:nil]; } 

Upstream:

 -(void)events:(ABBlock)success failure:(ABBlock)failure { NSString *params = [NSString stringWithFormat:@"%@",[ABUser loggedInUser]? [ABUser loggedInUser].name : @"no"]; NSDictionary *dict = @{@"loggedIn": params}; [self get:@"events/live.json" parameters:dict success:success failure:failure]; } - (void)get:(NSString *)path parameters:(NSDictionary *)parameters success:(ABBlock)success failure:(ABBlock)failure __block ABBlock blockSuccess = success; __block ABBlock blockFailure = failure; NSString *blockPath = path; NSDictionary *blockParameters = parameters; AFHTTPSuccessBlock _success = ^(AFHTTPRequestOperation *request, id response) { if (blockSuccess) { ABMessage *msg = [ABMessage messageWithObject:response]; dispatch_async(dispatch_get_main_queue(), ^(void) { blockSuccess(msg); }); } }; } Elsewhere: typedef void (^ABBlock) (ABMessage *); 

Is there something clearly wrong with the way the variables are used by the async_array block? I am wondering if I need to copy the message (* m) as an argument.

+4
source share
3 answers

Try creating a strong link for it, for example:

  ABMessage *msg = [ABMessage messageWithObject:response]; dispatch_async(dispatch_get_main_queue(), ^(void) { ABMessage *m = msg; blockSuccess(m); }); 
+1
source
  • You do not need the __block in this line:

     __block NSMutableArray *fetched = [NSMutableArray arrayWithArray:m.params[@"live"]]; 

When you specify the __block for Obj-C objects, they are not saved automatically. Since you do not need fetched change inside the block, you do not need to specify __block . (Bonus question: how would you do this if you wanted to overwrite the value of the fetched array?)

  • You do not need to point __block to success and failure :

     ABBlock blockSuccess = success; ABBlock blockFailure = failure; 

should be enough. When the _success block is copied, it, in turn, will copy the success and failure blocks. I'm not even sure what will happen if you specify the __block on the block (perhaps this is non-op - it would be a good exercise to find out), but this is not necessary.

Side note: there is no need to copy ABMessage *msg - the block will add a link to it.

0
source

Try copying this block to the heap by manually executing:

 [[ABClient sharedInstance] events:[_success copy] failure:nil]; 

Since you call success in another dispatch_async , your block that you declared in +fetch must be copied to the heap before sending.

0
source

All Articles