web-dev-qa-db-ja.com

__destroy_helper_block_でObjective-Cがクラッシュする

__destroy_helper_block_253__destroy_helper_block_278などの呼び出しでクラッシュするiOSアプリケーションがありますが、「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:これはクラッシュが発生するファイルで定義されたブロックの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:上記の関数にコンテキストを追加しました。

25
Dan Loewenherz

スタックトレースの各フレームは、libDispatchがクラッシュを引き起こすために何をしているのかについての手がかりを与えるはずです。下からの作業:

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

これらの2つの関数は、ワーカースレッドを起動して実行します。その過程で、スレッドの自動解放プールも設定します。

_9  libdispatch.dylib              0x000000018fe132b8 _dispatch_root_queue_drain + 556
_

この関数は、キュー破棄プロセスの開始を通知します。スレッド固有の自動解放プールが排出され、その過程で、その特定のキューによって参照されるすべての変数が解放されます。これはlibDispatchであるため、基になるマッハオブジェクトと送信したワークブロックを移動する必要があります...

_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_)を生成しました。同じロジックラインを適用すると、内側のブロックにはさらにもう1つの重要な破壊が必要であると推測できます。

_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は、libDispatchの致命的なエラーであるセマフォの破棄時に_KERN_SUCCESS_を返さないように十分に文句を言います。実際、そのような場合はabort()になりますが、技術的には__builtin_trap()ですが、同じ目標を達成します。デバッガーが接続されていないため、アプリがダウンします。

それで、これは問題を提起します:これをどのように修正しますか?さて、最初に、ディスパッチオブジェクトを参照しているものがあるかどうかを見つける必要があります。あなたは非同期ネットワークをやっていると言ったので、それが最初にチェックする場所になるでしょう。これらのオブジェクトのいずれかがキューまたはセマフォを保持している場合、または保持しているオブジェクトを参照していて、それらのブロックのいずれかでそれを強力にキャプチャしていない場合、これは、ブロックがオブジェクトとともにスコープ外を通過したときに発生します。 。

36
CodaFi

ここで行うことはそれほど多くありませんが、私の疑いは、ブロックがヒープに移動されることは決してないということです。デフォルトでは、ブロックはスタック上に作成されます。コンパイラーは、それらをいつヒープに移動するかを理解できることがよくありますが、これをブロックからブロックに渡す方法は、おそらくそれを行うことはありません。

_completionCopy = [completion copy]_を追加して、ヒープに強制します。次に、completionCopyを操作します。辞書へのブロックの格納については、 bbumの回答 を参照してください。 ARCでは、Block_copy()Block_release()を呼び出す必要はありませんが、ここでは_-copy_を呼び出したいと思います。

2
Rob Napier

問題はあなたのコードではなく、他の場所にあるのではないかと思います。

考えられる問題の1つは次のとおりです。

ブロックにキャプチャされたUIKitオブジェクトがある場合completionブロックが非メインスレッドで実行され、このブロックがlastの強力な参照を保持すると、微妙なバグが発生する可能性がありますそれらのUIKitオブジェクト:

ブロックcompletionが終了すると、ブロックの割り当てが解除され、これに伴い、インポートされたすべての変数が「破棄」されます。つまり、保持可能なポインターの場合、releaseメッセージを受信します。これが最後の強力な参照である場合、キャプチャされたオブジェクトの割り当てが解除されます。これは非メインスレッドで発生します。これは、UIKitオブジェクトにとって致命的となる可能性があります。

1
CouchDeveloper

仮説:

  1. doSomethingWithCompletion:作成ExampleBlock.
  2. 非同期ネットワーク操作を開始します。
  3. doSomethingWithCompletion:が戻り、ExampleBlockが解放されます。
  4. 非同期ネットワーク操作が終了し、ExampleBlockを呼び出します。

この場合、ブロックへのポインタは、割り当てが解除された後に逆参照されます。 (おそらく、これは、自動解放プールが空になったかどうか、または他の近くのメモリ領域が解放されたかどうかに基づいて断続的になります。)

3つの可能な解決策:

1.ブロックをプロパティに保存します

ブロックをプロパティに格納します。

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

次に、コードで、

self.exampleBlock = …

このアプローチの問題の1つは、exampleBlockを1つしか持てないことです。

2.ブロックを配列に格納します

この問題を回避するには、ブロックをコレクションに保存します(NSMutableArrayなど)。

@property (nonatomic, strong) NSMutableArray *blockArray;

次にコードで:

self.blockArray = [NSMutableArray array];

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

ブロックの割り当てを解除しても問題がない場合は、配列からブロックを削除できます。

3.ブロックを渡すだけで、ストレージの問題を回避できます

ブロックの保存と破棄を管理する代わりに、コードをリファクタリングして、操作が終了するまでさまざまなメソッド間でexampleBlockが渡されるようにします。

または、非同期コードにNSBlockOperationを使用し、応答が終了したコードにcompletionBlockを設定して、NSOperationQueueに追加することもできます。

1
Aaron Brager

completionが非同期呼び出しで解放され、クラッシュの原因になっている可能性があると思います。

1
Anand

投稿されたコードに問題はなく、バグは別の場所にあると思います。

また、ブロックをネストすることは不要であるように思われ、メモリ管理を複雑にし、おそらくクラッシュの原因を見つけることをより困難にします。

ExampleBlockのコードを直接completionブロックに移動することから始めてみませんか?

0
Rivera

このソリューションはどうですか:後で現在のスコープにないブロックを呼び出す場合は、そのブロックで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...
}
0