__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:上記の関数にコンテキストを追加しました。
スタックトレースの各フレームは、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()
ですが、同じ目標を達成します。デバッガーが接続されていないため、アプリがダウンします。
それで、これは問題を提起します:これをどのように修正しますか?さて、最初に、ディスパッチオブジェクトを参照しているものがあるかどうかを見つける必要があります。あなたは非同期ネットワークをやっていると言ったので、それが最初にチェックする場所になるでしょう。これらのオブジェクトのいずれかがキューまたはセマフォを保持している場合、または保持しているオブジェクトを参照していて、それらのブロックのいずれかでそれを強力にキャプチャしていない場合、これは、ブロックがオブジェクトとともにスコープ外を通過したときに発生します。 。
ここで行うことはそれほど多くありませんが、私の疑いは、ブロックがヒープに移動されることは決してないということです。デフォルトでは、ブロックはスタック上に作成されます。コンパイラーは、それらをいつヒープに移動するかを理解できることがよくありますが、これをブロックからブロックに渡す方法は、おそらくそれを行うことはありません。
_completionCopy = [completion copy]
_を追加して、ヒープに強制します。次に、completionCopy
を操作します。辞書へのブロックの格納については、 bbumの回答 を参照してください。 ARCでは、Block_copy()
とBlock_release()
を呼び出す必要はありませんが、ここでは_-copy
_を呼び出したいと思います。
問題はあなたのコードではなく、他の場所にあるのではないかと思います。
考えられる問題の1つは次のとおりです。
ブロックにキャプチャされたUIKitオブジェクトがある場合completion
ブロックが非メインスレッドで実行され、このブロックがlastの強力な参照を保持すると、微妙なバグが発生する可能性がありますそれらのUIKitオブジェクト:
ブロックcompletion
が終了すると、ブロックの割り当てが解除され、これに伴い、インポートされたすべての変数が「破棄」されます。つまり、保持可能なポインターの場合、release
メッセージを受信します。これが最後の強力な参照である場合、キャプチャされたオブジェクトの割り当てが解除されます。これは非メインスレッドで発生します。これは、UIKitオブジェクトにとって致命的となる可能性があります。
仮説:
doSomethingWithCompletion:
作成ExampleBlock.
doSomethingWithCompletion:
が戻り、ExampleBlock
が解放されます。ExampleBlock
を呼び出します。この場合、ブロックへのポインタは、割り当てが解除された後に逆参照されます。 (おそらく、これは、自動解放プールが空になったかどうか、または他の近くのメモリ領域が解放されたかどうかに基づいて断続的になります。)
3つの可能な解決策:
ブロックをプロパティに格納します。
@property (nonatomic, copy) returnType (^exampleBlock)(parameterTypes);
次に、コードで、
self.exampleBlock = …
このアプローチの問題の1つは、exampleBlock
を1つしか持てないことです。
この問題を回避するには、ブロックをコレクションに保存します(NSMutableArray
など)。
@property (nonatomic, strong) NSMutableArray *blockArray;
次にコードで:
self.blockArray = [NSMutableArray array];
// Later on…
[self.blockArray addObject:exampleBlock];
ブロックの割り当てを解除しても問題がない場合は、配列からブロックを削除できます。
ブロックの保存と破棄を管理する代わりに、コードをリファクタリングして、操作が終了するまでさまざまなメソッド間でexampleBlock
が渡されるようにします。
または、非同期コードにNSBlockOperation
を使用し、応答が終了したコードにcompletionBlock
を設定して、NSOperationQueueに追加することもできます。
completionが非同期呼び出しで解放され、クラッシュの原因になっている可能性があると思います。
投稿されたコードに問題はなく、バグは別の場所にあると思います。
また、ブロックをネストすることは不要であるように思われ、メモリ管理を複雑にし、おそらくクラッシュの原因を見つけることをより困難にします。
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...
}