【问题标题】:Objective-C crash on __destroy_helper_block___destroy_helper_block_ 上的 Objective-C 崩溃
【发布时间】:2014-05-09 20:07:30
【问题描述】:

我有一个 iOS 应用程序在调用 __destroy_helper_block_253__destroy_helper_block_278 时崩溃,我不确定“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:为上述函数添加了更多上下文。

【问题讨论】:

  • 您很可能错误地保留了该块。确保copy 用于存储。
  • 数字是编译器分配的标识符。你拥有的数字是驱逐舰辅助块,而不是块本身。我会启用僵尸并尝试重现崩溃以查看释放了哪个对象。可能会告诉你哪个区块被过早释放。
  • 启用僵尸对象怎么样?
  • 另一个建议:启用异常断点
  • 也许尝试在主队列的dispatch_async 中发布到NSNotificationCenter

标签: ios objective-c objective-c-blocks


【解决方案1】:

堆栈跟踪的每一帧都应该为您提供有关 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)

这正是这里发生的事情。数字 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 假定队列已完成并继续解除分配,直到达到特定于信号量的处置

0  libdispatch.dylib              0x000000018fe0eb2c _dispatch_semaphore_dispose + 60

由于没有信号量要释放,mach 会抱怨到在信号量破坏时不返回 KERN_SUCCESS,这是 libDispatch 中的一个致命错误。事实上,在这种情况下它会abort() - 好吧,技术上是__builtin_trap(),但它们实现了相同的目标。因为没有附加调试器,所以你的应用程序崩溃了。

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

【讨论】:

  • 您的分析是否正确,即在释放块completion 时发生崩溃? (恕我直言,就是这样)。
  • 是和不是。根块是一个调度块,但完成块或示例块可能是捕获队列或信号量的内部块。您说得对,我们需要查看更多代码以提供更好的诊断。
  • 很好的答案。尽管我无法从中提取行号,但您的分析似乎是我能得到的最接近的。谢谢!
  • 没问题。当我们无法提供更多信息而没有看到潜在的 NDA 或私有源代码时,这总是很不幸,但鉴于我所掌握的信息,我已尽力而为
  • 我仍然无法从上面的堆栈跟踪中理解。那么,实际问题是什么?不复制块或块内的强指针。我有完全相同的情况,您的意见将帮助我深入了解我面临的问题。
【解决方案2】:

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

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

【讨论】:

  • 当 dtor 试图从框架中抓取某些东西时,这不会导致 KERN_INVALID_ADDRESSes 突然出现吗?块是活的,块引用的东西不是。
  • ExampleBlock被另一个块捕获时将被复制到堆中(除非它已经在堆上)。因为有一些完成处理程序——正如 OP 所说——“调用这个块”并且如果这个完成处理程序本身是一个块,那么我们可以很确定块 ExampleBlock 是在堆上。
【解决方案3】:

假设:

  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。

【讨论】:

  • 您的假设不太可能成立:如果块 ExampleBlock 在另一个块中执行(网络请求的完成块(我想确实是这种情况),那么块ExampleBlock 将在完成块文字表达式被评估时保留,并在完成块完成时释放。(除非禁用 ARC 或 Block 不是对象(iOS 6 之前)。跨度>
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

    一个可能的问题是:

    IFF 有 UIKit 对象在块 completion 中被捕获,当块在非主线程上执行并且此块保持 last 对那些 UIKit 对象:

    completion 块完成时,它的块会被释放,与此同时,所有导入的变量都会被“销毁”,这意味着在可保留指针的情况下,它们会收到 release 消息。如果这是最后一个强引用,则捕获的对象将被释放,这将在非主线程中发生 - 这对于 UIKit 对象可能是致命的。

    【讨论】:

    • UIKit 框架不会出现在跟踪中吗?这看起来像是一个 libDispatch 工作线程无法停止。 UIKit 并没有禁止线程,它只是保留了如此多的无锁全局状态,以至于线程使访问和修改异常不稳定。
    • @CodaFi UIKit 框架不会出现在跟踪中吗? 可能是的,但不一定是由于竞争条件的一些微妙的副作用。但是为了调查这个问题,我们需要更多的代码,以及调试它的方法。
    【解决方案6】:

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

    此外,嵌套块似乎没有必要,并且使内存管理复杂化,并且可能使查找崩溃原因变得更加困难。

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

    【讨论】:

    • 这个块中的逻辑重复了两次(一次成功,一次失败),我试图避免重复代码。
    • @Dan 或许你还是应该尝试一下以缩小问题范围?
    【解决方案7】:

    这个解决方案怎么样: 如果您稍后会调用不在当前范围内的某个块,那么您应该在其上调用 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...
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-24
      • 1970-01-01
      • 2015-08-08
      • 1970-01-01
      • 1970-01-01
      • 2012-01-31
      • 2013-02-07
      • 1970-01-01
      相关资源
      最近更新 更多