Reputation: 11244
I have an iOS application that's crashing on calls like __destroy_helper_block_253
and __destroy_helper_block_278
and I'm not really sure what either "destroy_helper_block" is referencing or what the number after it is supposed to point to.
Does anyone have any pointers for how to track down where exactly these crashes might be occuring?
Here's an example traceback (note that the lines with __destroy_helper_block
only references the file it's contained in and nothing else, when normally the line number would be included as well).
Thread : Crashed: com.apple.root.default-priority
0 libdispatch.dylib 0x000000018fe0eb2c _dispatch_semaphore_dispose + 60
1 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
2 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
3 libdispatch.dylib 0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60
4 Example App 0x00000001000fe5a4 __destroy_helper_block_278 (TSExampleApp.m)
5 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 256
6 Example App 0x00000001000fda18 __destroy_helper_block_253 (TSExampleApp.m)
7 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 256
8 libdispatch.dylib 0x000000018fe0bfd4 _dispatch_client_callout + 16
9 libdispatch.dylib 0x000000018fe132b8 _dispatch_root_queue_drain + 556
10 libdispatch.dylib 0x000000018fe134fc _dispatch_worker_thread2 + 76
11 libsystem_pthread.dylib 0x000000018ffa16bc _pthread_wqthread + 356
Edit 1: Here's an example of one of the blocks defined in the file where the crash occurs (with application-specific code edited out).
- (void)doSomethingWithCompletion:(void (^)())completion {
void (^ExampleBlock)(NSString *) = ^{
NSNotification *notification = [NSNotification notificationWithName:kExampleNotificationName object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotification:notification];
if (completion) {
completion();
}
};
// Async network call that calls ExampleBlock on either success or failure below...
}
There are many other blocks in the file, but most of them are provided as arguments to methods instead of being defined first and then referenced later.
Edit 2: Added more context to above function.
Upvotes: 30
Views: 14071
Reputation: 3233
what about this solution: if you will call some block later not in current scope, then you ought to call copy on it to move this block to the heap from the stack
- (void)doSomethingWithCompletion:(void (^)())completion {
void (^ExampleBlock)(NSString *) = [^{
NSNotification *notification = [NSNotification notificationWithName:kExampleNotificationName object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotification:notification];
if (completion) {
completion();
}
} copy];
// Async network call that calls ExampleBlock on either success or failure below...
}
Upvotes: 0
Reputation: 43330
Each frame of the stack trace should give you a clue as to what libDispatch is doing to cause the crash. Working our way from the bottom:
11 libsystem_pthread.dylib 0x000000018ffa16bc _pthread_wqthread
10 libdispatch.dylib 0x000000018fe134fc _dispatch_worker_thread2 + 76
These two functions spin up a worker thread and runs it. In the process, it also sets up an autorelease pool for the thread.
9 libdispatch.dylib 0x000000018fe132b8 _dispatch_root_queue_drain + 556
This function signals the start of the queue destruction process. The thread-specific autorelease pool is drained, and in the process all variables referenced by that particular queue are released. Because this is libDispatch, that means the underlying mach object and the work block you submitted have got to go...
7 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 256
6 Example App 0x00000001000fda18 __destroy_helper_block_253 (TSExampleApp.m)
5 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 25
4 Example App 0x00000001000fe5a4 __destroy_helper_block_278 (TSExampleApp.m)
which is precisely what happens here. Number 7 is the outer block and because it contains a nontrivial object to destroy (yet another block), the compiler generated a destructor (__destroy_helper_block_253
) to get rid of that inner block too. Applying the same line of logic, we can deduce that the inner block has yet another bit of nontrivial destroying to do.
3 libdispatch.dylib 0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60
2 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
1 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
These lines are the root cause of all your troubles. For some reason, you've either captured the queue you're calling back on, or you've captured an object that holds a reference to a queue weakly such that when it goes the way of the dinosaur, it takes its queue with it. This causes libDispatch to assume the queue is done for and it keeps on dealloc'ing until it reaches the semaphore-specific dispose
0 libdispatch.dylib 0x000000018fe0eb2c _dispatch_semaphore_dispose + 60
With no semaphore to release, mach will complain enough not to return KERN_SUCCESS
on semaphore destruction, which is a fatal error in libDispatch. In fact, it will abort()
in such a case -well, technically __builtin_trap()
, but they accomplish the same goal. Because there's no debugger attached, down goes your app.
So this raises the question then: how do you fix this? Well, first you need to find what, if anything is referencing a dispatch object. You mentioned that you were doing some asynchronous networking, so that would be the place to check first. If any of those objects happens to hold a queue or semaphore, or references an object that does, and you aren't capturing it strongly in any of those blocks, this is precisely what happens when the block passes out of scope along with the object.
Upvotes: 43
Reputation: 19134
I would suspect, the issue is not in your code but elsewhere.
One possible issue is this:
IFF there are UIKit objects which are captured in the Block completion
you possibly get a subtle bug when the block is executed on a non-main thread AND this block keeps the last strong reference to those UIKit objects:
When the Block completion
finishes, it's block get deallocated and along with this, all imported variables get "destroyed" which means in case of retainable pointers, that they receive a release
message. If this was the last strong reference, the captured object gets deallocated which will happen in a non-main thread - and this can be fatal for UIKit objects.
Upvotes: 1
Reputation: 864
I think the completion gets released in your async call that might be causing the crash.
Upvotes: 1
Reputation: 66292
Hypothesis:
doSomethingWithCompletion:
creates ExampleBlock.
doSomethingWithCompletion:
returns, and ExampleBlock
is released.ExampleBlock
.In this case, the pointer to the block would get dereferenced after it's been deallocated. (Perhaps this is intermittent based on whether the autorelease pool has drained, or whether other nearby memory areas have been released.)
3 possible solutions:
Store the block in a property:
@property (nonatomic, copy) returnType (^exampleBlock)(parameterTypes);
Then in code,
self.exampleBlock = …
One problem with this approach is that you can only ever have one exampleBlock
.
To work around this problem, you can store the blocks in a collection (like NSMutableArray
):
@property (nonatomic, strong) NSMutableArray *blockArray;
then in code:
self.blockArray = [NSMutableArray array];
// Later on…
[self.blockArray addObject:exampleBlock];
You can remove the block from the array when it's OK to deallocate it.
Instead of managing storing and destroying your blocks, refactor your code so exampleBlock
is passed around between the various methods until your operation finishes.
Alternatively, you could use NSBlockOperation
for the asynchronous code, and set its completionBlock
for the response-finished code, and add it to an NSOperationQueue.
Upvotes: 1
Reputation: 299545
There's not a lot to go on here, but my suspicion is that the block is never getting moved to the heap. Blocks are created on the stack by default. The compiler can often figure out when to move them to heap, but the way you're handing this one from block to block probably is never doing that.
I would add a completionCopy = [completion copy]
to force it onto the heap. Then work with completionCopy
. See bbum's answer regarding storing blocks in dictionaries. With ARC, you don't need to call Block_copy()
and Block_release()
anymore, but I think you still want to call -copy
here.
Upvotes: 2
Reputation: 10938
I don't see anything wrong with the code posted and think that the bug is somewhere else.
Also nesting blocks seems unnecessary and complicates memory management and probably makes finding the cause of the crash more difficult.
Why don't you begin by moving the code in your ExampleBlock
directly to the completion
block?
Upvotes: 0