__destroy_helper_block_上的Objective-C崩溃

我有一个iOS应用程序崩溃了__destroy_helper_block_253__destroy_helper_block_278类的调用,我不确定“destroy_helper_block”正在引用什么或它应该指向的数字。

有没有人有任何关于如何追踪这些崩溃可能发生的地方的指示?

这是一个示例回溯(请注意,带有__destroy_helper_block的行仅引用它包含的文件__destroy_helper_block包含任何其他内容,通常也会包含行号)。

 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 

编辑1:这是发生崩溃的文件中定义的一个块的示例(编辑出特定于应用程序的代码)。

 - (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... } 

文件中还有许多其他块,但是大多数块都是作为方法的参数提供的,而不是先被定义然后再引用。

编辑2:为上述function添加了更多上下文。

堆栈跟踪的每一帧都应该为您提供有关libDispatch正在执行哪些操作以引发崩溃的线索。 从底部开始工作:

 11 libsystem_pthread.dylib 0x000000018ffa16bc _pthread_wqthread 10 libdispatch.dylib 0x000000018fe134fc _dispatch_worker_thread2 + 76 

这两个函数会启动一个工作线程并运行它。 在此过程中,它还为线程设置自动释放池。

 9 libdispatch.dylib 0x000000018fe132b8 _dispatch_root_queue_drain + 556 

此函数表示队列销毁过程的开始。 排除特定于线程的自动释放池,并且在该过程中释放该特定队列引用的所有变量。 因为这是libDispatch,这意味着底层的mach对象和你提交的工作块必须…

 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) 

这正是这里发生的事情。 Number 7是外部块,因为它包含一个要销毁的非平凡对象(又是另一个块),编译器生成了一个析构函数( __destroy_helper_block_253 )来摆脱那个内部块。 应用相同的逻辑线,我们可以推断出内部块还有另外一点非常重要的破坏。

 3 libdispatch.dylib 0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60 2 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56 1 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56 

这些线条是你所有麻烦的根本原因。 出于某种原因,你要么已经捕获了你正在回调的队列,要么你已经捕获了一个对象,该对象弱有一个对队列的引用,这样当它进入恐龙的路时,就会占用它的队列。 。 这会导致libDispatch假定队列已完成,并且一直处于dealloc’ing状态,直到达到信号量特定的配置为止

 0 libdispatch.dylib 0x000000018fe0eb2c _dispatch_semaphore_dispose + 60 

没有信号量释放,马赫会抱怨不要在信号量破坏时返回KERN_SUCCESS ,这是libDispatch中的一个致命错误。 实际上,它会在这样的情况下abort() ,技术上是__builtin_trap() ,但它们实现了相同的目标。 因为没有连接调试器,所以你的应用程序就可以了。

所以这提出了一个问题:你如何解决这个问题? 好吧,首先你需要找到什么,如果有什么东西引用一个调度对象。 你提到你正在做一些异步网络,所以这将是首先检查的地方。 如果这些对象中的任何一个恰好拥有一个队列或信号量,或者引用一个对象,并且你没有在任何这些块中强烈捕获它,那么这正是当块与对象一起超出范围时会发生的情况。

这里没有太多东西可以继续,但我怀疑这个块永远不会被移动到堆中。 默认情况下,会在堆栈上创建块。 编译器通常可以确定何时将它们移动到堆中,但是你将这个块从一个块移到另一个块的方式可能永远不会这样做。

我会添加一个completionCopy = [completion copy]来强制它进入堆。 然后使用completionCopy 。 请参阅bbum关于在词典中存储块的答案 。 使用ARC,您不再需要调用Block_copy()Block_release() ,但我认为您仍然希望在此处调用-copy

假设:

  1. doSomethingWithCompletion:创建ExampleBlock.
  2. 您启动了一些异步网络操作。
  3. doSomethingWithCompletion:返回,并释放ExampleBlock
  4. 异步网络操作完成,并调用ExampleBlock

在这种情况下,指向块的指针在被解除分配后将被取消引用。 (根据自动释放池是否已耗尽,或者是否已释放其他附近的内存区域,这可能是间歇性的。)

3种可能的解决方

1.将块存放在属性中

将块存储在属性中:

 @property (nonatomic, copy) returnType (^exampleBlock)(parameterTypes); 

然后在代码中,

 self.exampleBlock = … 

这种方法的一个问题是你只能拥有一个exampleBlock

2.将块存储在一个数组中

若要解决此问题,您可以将块存储在集合中(如NSMutableArray ):

 @property (nonatomic, strong) NSMutableArray *blockArray; 

然后在代码中:

 self.blockArray = [NSMutableArray array]; // Later on… [self.blockArray addObject:exampleBlock]; 

当可以解除分配时,可以从数组中删除该块。

3.通过简单地传递块来解决存储问题

不要管理存储和销毁块,而是重构代码,以便在各种方法之间传递exampleBlock直到操作完成。

或者,您可以将NSBlockOperation用于异步代码,并为响应完成代码设置其completionBlock ,并将其添加到NSOperationQueue。

我认为完成会在您的异步调用中释放,可能导致崩溃。

我怀疑,问题不在你的代码中,而在于其他地方。

一个可能的问题是:

IFF有块UIKit对象,它们在块completion被捕获,当块在非主线程上执行时,你可能会得到一个微妙的bug。这个块保留了对那些UIKit对象的最后一个强引用:

当块completion ,它的块被释放,并且随之而来,所有导入的变量都被“销毁”,这意味着在可保留指针的情况下,它们会收到release消息。 如果这是最后一个强引用,则捕获的对象将被释放,这将在非主线程中发生 – 这对UIKit对象来说可能是致命的。

我没有看到发布的代码有任何问题,并认为该错误是在其他地方。

嵌套块似乎也是不必要的,并且使内存管理变得复杂,并且可能使得查找崩溃的原因更加困难。

为什么不首先将ExampleBlock的代码直接移动到completion块?

这个解决方案怎么样:如果你以后不在当前范围内调用某个块,那么你应该调用它上面的copy来将这个块从栈中移到堆中

 - (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... }