3

dispatch_async を使用してバックグラウンド スレッドに複雑な計算を実行しようとしていますが、ブロックで使用しているオブジェクトが過剰に解放されているようです。私はARCを使用しているので、保持と解放についてあまり気にする必要はないと思いましたが、私の場合、重要なものを見逃したか、ARCがオブジェクトを過剰解放しました。

問題が発生するのは次の場合のみです。

  • forループでブロックを作成するdispatch_asyncを呼び出します
  • ブロック外で作成したブロック内のオブジェクトを参照しています
  • ループは少なくとも 2 回反復します (したがって、少なくとも 2 つのブロックが作成され、キューに追加されます)。
  • RELEASE ビルド構成が使用されている (したがって、おそらく何らかの最適化に関連している)

関係ないようです

  • シリアル キューかコンカレント キューか
  • どのようなオブジェクトが使用されているか

この質問は、ブロックが RELEASE 構成でリリースされること ( iOS 5 のブロックは Release Build でのみクラッシュするように) に関するものではなく、ブロックで参照されているオブジェクトがオーバーリリースされていることに関するものです。

NSURL オブジェクトを使用して小さな例を作成しました。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSURL *theURL = [NSURL URLWithString:@"/Users/"];
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(myQueue, ^(){
        NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
        NSLog(@"Successfully created new url: %@ in initial block", newURL);
    });

    for (int i = 0; i < 2; i++)
    {
        dispatch_async(myQueue, ^(){
            NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
            NSLog(@"Successfully created new url: %@ in loop block %d", newURL, i);
        });
    }
}

for ループにない最初のブロックは問題なく動作します。ループの反復が 1 回だけの場合は、2 番目も同様です。ただし、指定された例では、2 つの反復が行われ、RELEASE 構成で実行するとクラッシュします。スキームで NSZombie を有効にすると、次のように出力されます。

2013-01-07 23:33:33.331 BlocksAndARC[17185:1803] Successfully created new url: /Users/test in initial block
2013-01-07 23:33:33.333 BlocksAndARC[17185:1803] Successfully created new url: /Users/test in loop block 0
2013-01-07 23:33:33.333 BlocksAndARC[17185:1803] *** -[CFURL URLByAppendingPathComponent:]: message sent to deallocated instance 0x101c32790

URLByAppendingPathComponentforループ内のブロック内の呼び出しでデバッガーが停止します。

同時キューを使用する場合、失敗した呼び出しは、実際にreleaseは呼び出しスタックに _Block_release がある呼び出しになります。

2013-01-07 23:36:13.291 BlocksAndARC[17230:5f03] *** -[CFURL release]: message sent to deallocated instance 0x10190dd30
(lldb) bt
* thread #6: tid = 0x3503, 0x00007fff885914ce CoreFoundation`___forwarding___ + 158, stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x00007fff885914ce CoreFoundation`___forwarding___ + 158
    frame #1: 0x00007fff885913b8 CoreFoundation`_CF_forwarding_prep_0 + 232
    frame #2: 0x00007fff808166a3 libsystem_blocks.dylib`_Block_release + 202
    frame #3: 0x00007fff89f330b6 libdispatch.dylib`_dispatch_client_callout + 8
    frame #4: 0x00007fff89f38317 libdispatch.dylib`_dispatch_async_f_redirect_invoke + 117
    frame #5: 0x00007fff89f330b6 libdispatch.dylib`_dispatch_client_callout + 8
    frame #6: 0x00007fff89f341fa libdispatch.dylib`_dispatch_worker_thread2 + 304
    frame #7: 0x00007fff852f0cab libsystem_c.dylib`_pthread_wqthread + 404
    frame #8: 0x00007fff852db171 libsystem_c.dylib`start_wqthread + 13

しかし、これはおそらくタイミングがわずかに異なるためです。

どちらのエラーも、によって参照されている NSURL オブジェクトがオーバーリリースされていることを示していると思いますtheURL。しかし、それはなぜですか?私は何かを見逃していましたか、それとも ARC とブロックの組み合わせのバグですか?

私が期待するのは、dispatch_async呼び出しの前または実装のいずれかでdispatch_async(とにかく: for ループ内で、dispatch_async-call ごとに 1 回)、ブロック内で参照されるすべての変数が保持され、(しかし、で)ブロック。

実際に起こっているように見えるのは、変数がコード内でretainの出現に対して一度編集されるが、ブロックの最後で呼び出されるため、実行されるたびに呼び出されるため、ループ内の呼び出しよりも多くの呼び出しが発生することです。dispatch_asyncreleasereleaseretain

しかし、多分私は何かを見落としています。より良い説明はありますか?ブロックまたは ARC を何らかの方法で誤用しましたか、それともバグですか?

編集:参照された変数を for ループ内のローカル変数にコピーするという @Joshua Weinberg の提案を試しました。指定されたサンプル コードでは機能しますが、関数呼び出しが関係している場合は機能しません。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSObject *theObject = [[NSObject alloc] init];

    [self blocksInForLoopWithObject:theObject];
}

-(void)blocksInForLoopWithObject:(NSObject *)theObject
{
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 2; i++)
    {
        NSObject *theSameObject = theObject;
        dispatch_async(myQueue, ^(){
            NSString *description = [theSameObject description];
            NSLog(@"Successfully referenced object %@ in loop block %d", description, i);
        });
    }
}

では、なぜケースでは機能するのに、他のケースでは機能しないのでしょうか? 違いがわかりません。

4

2 に答える 2

0

試してみたところ、再現できました。あなたの診断は的確であるように思われ、私が知る限り、ブロックがどのようにコピーされるか、またはスコープがうまくいかないようにするかについての最適化に関する問題があります。レーダーに値するようです。

これを回避するためにできることに関しては。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSURL *theURL = [NSURL URLWithString:@"/Users/"];
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(myQueue, ^(){
        NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
        NSLog(@"Successfully created new url: %@ in initial block", newURL);
    });

    for (int i = 0; i < 2; i++)
    {
        NSURL *localURL = theURL;
        dispatch_async(myQueue, ^(){
            NSURL *newURL = [localURL URLByAppendingPathComponent:@"test"];
            NSLog(@"Successfully created new url: %@ in loop block %d", newURL, i);
        });
    }
}

それをスタックにコピーすると、ブロックは毎回それを再キャプチャし、意図したメモリセマンティクスを適用します。

于 2013-01-07T23:48:37.983 に答える
0

この問題のトラブルシューティングを試みる人々を支援するために、XCode 4.5 のリリース構成でこの簡略化されたバージョンで問題を再現することができました。

- (id)test {
  return [[NSObject alloc] init];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  id foo = [self test];
  for (int i = 0; i < 2; i++)
  {
    [^(){
      NSLog(@"%@", foo);
    } copy];
  }
  NSLog(@"%@", foo);

  return YES;
}

プロファイリングから、ARC がループの内側の最後に誤ってリリースを挿入しているようです。

于 2013-01-08T01:42:28.143 に答える