10

バックグラウンドGCDキューにアセットをダウンロードするビューコントローラがあります。私は、ダウンロードが完了したら、ダウンロード機能をコールバックブロックに渡し、メインブロック上でこのブロックを実行します。ダウンロードが完了する前に、私のビューコントローラがユーザによって却下された場合deallocが背景で呼び出されましたGCDのキューがクラッシュするARCで作成されたアプリケーション

問題が発生します。私は何が起こっているのだろうと思う、一度私のビューコントローラが却下され、コールバックブロックは、コントローラへの強い参照を保持する唯一のものです。コールバックブロックはバックグラウンドスレッドにのみ保持されるため、リリースされると、コールバックブロックのスコープでキャプチャされたすべてのオブジェクトもバックグラウンドキューに存在しますが、解放されます。

これは問題です:にdeallocが同じキューではなく、メインキューで実行することが原因となっているバックグラウンドキューにリリースされています。これは、順番に、背景とアプリのクラッシュにdeallocを呼び出します。

2012-01-19 12:47:36.349 500px iOS[4892:12107] bool _WebTryThreadLock(bool), 0x306c10: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now... 
[Switching to process 16643 thread 0x4103] 
[Switching to process 16643 thread 0x4103] 
(gdb) where 
#0 0x307fd3c8 in _WebTryThreadLock() 
#1 0x307ff1b0 in WebThreadLock() 
#2 0x33f7865e in -[UITextView dealloc]() 
#3 0x0005d2ce in -[GCPlaceholderTextView dealloc] (self=0x309010, _cmd=0x340ce6b8) at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/GCPlaceholderTextView.m:113 
#4 0x337cac42 in -[NSObject(NSObject) release]() 
#5 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#6 0x33e836cc in -[UIScrollView removeFromSuperview]() 
#7 0x33f762f0 in -[UITextView removeFromSuperview]() 
#8 0x33e01de2 in -[UIView dealloc]() 
#9 0x337cac42 in -[NSObject(NSObject) release]() 
#10 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#11 0x33e01de2 in -[UIView dealloc]() 
#12 0x33f437e4 in -[UIScrollView dealloc]() 
#13 0x337cac42 in -[NSObject(NSObject) release]() 
#14 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#15 0x33e836cc in -[UIScrollView removeFromSuperview]() 
#16 0x33e01de2 in -[UIView dealloc]() 
#17 0x337cac42 in -[NSObject(NSObject) release]() 
#18 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#19 0x33e01de2 in -[UIView dealloc]() 
#20 0x337cac42 in -[NSObject(NSObject) release]() 
#21 0x33e5a00e in -[UIViewController dealloc]() 
#22 0x00035f16 in -[PXPhotoViewController dealloc] (self=0x5158d0, _cmd=0x340ce6b8) at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoViewController.m:118 
#23 0x337cac42 in -[NSObject(NSObject) release]() 
#24 0x337e5046 in sendRelease() 
#25 0x331fc92e in _Block_object_dispose() 
#26 0x0003c33a in __destroy_helper_block_() at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoViewController.m:878 
#27 0x331fc88e in _Block_release() 
#28 0x331fc91c in _Block_object_dispose() 
#29 0x000c8d32 in __destroy_helper_block_() at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoFetcher.m:557 
#30 0x331fc88e in _Block_release() 
#31 0x35eec8ec in _dispatch_call_block_and_release() 
#32 0x35ee2de2 in _dispatch_queue_drain() 
#33 0x35ee2f32 in _dispatch_queue_invoke() 
#34 0x35ee24f2 in _dispatch_worker_thread2() 
#35 0x34ecb590 in _pthread_wqthread() 
#36 0x34ecbbc4 in start_wqthread() 

私はメインスレッドで実行するコードは次のようになります。

[[PXPhotoFetcher sharedPXPhotoFetcher] fetchPhotoDetailsWithPriority:PhotoRequestLowPriority 
    withCallback:^(PXPhotoModel *thePhotoModel) { 
     // a callback function which captures self in its scope 
    } forModel:model]; 

ので、もし私が、4.3用に構築しています私は__unsafe_unretainedコールバックブロックの自己参照を使用して、これは私の現在の問題を修正しますが、ぶら下がりポインタを持つ新しい問題を導入します。

このためのソリューションの設計には、どのような方法をお勧めしますか?その他の回避策にはoverriding releaseが含まれていたため、メインスレッドで常に呼び出されていましたが、これは明らかにARC環境では機能しません。

それはARCとGCDとはるかに多くの共通の問題でなければなりませんようにこれはそうですが、私は、オンラインで何かを見つけることができません。それはちょうど私が< iOS 5をターゲットに設定しているため弱い参照を使用できないのでしょうか?

答えて

5

UPDATE:以下の解決策は、実際には、iOSの4の作業は、これは、いくつかの理由で5に働いていないではなく、4、私はよりよい解決策を思い付きました。

問題は、ブロックがバックグラウンドで破棄されたために発生します。そのため、ローカル変数とバックグラウンドブロック内に配置して呼び出すと、メインスレッドのブロックに非同期で渡します。また、厄介だが、次のようになります。

void(^block)(void) = ^{/*do all the things*/}; 

dispatch_async(queue, ^{ 

    block(); 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     if ([block isKindOfClass:[NSString class]]) 
      NSLog(@"Whoa, bro"); 
    }); 
}); 

メインスレッド上のコードでは、コンパイラは単にコードを完全に離れて最適化していないことを確認するためだけのトリックです。私はブロックオブジェクトがメインスレッドによって最後に解放される必要があります。このコードは、-Osコンパイラ最適化レベルで動作するようです。

だから私は自分の問題を解決する方法を考え出しましたが、それはスーパーハッキーです。私はこのフィックスから問題を再現することができませんでしたが、それは非常に貧弱なアーキテクチャーデザインです。

要約すると、問題は、破棄されているバックグラウンドキューにブロックがあることです。そのブロックはオブジェクトであり、自己への強い参照を含むコールバックブロックへの強い参照を所有しています。バックグラウンドブロックがバックグラウンドキューから解放されています。だから私がやったことは、別のディスパッチコールでコールをメインキューにラップすることです。だから、私はこの方法を取得ここから行く:

dispatch_async(backgroundQueue, ^{ 
    /* do all the things */ 
}); 

この目的のために:

dispatch_async(dispatch_get_main_queue(), ^{ 
    dispatch_async(backgroundQueue, ^{ 
     /* do all the things */ 
    }); 
}); 

コードは、非同期バックグラウンド・キューにブロックを派遣メインキューにブロックを送出します。バックグラウンドブロックはオブジェクトでメインキューのスコープに属しているため、メインスレッドで解放され、UITextViewの最終的な割り当て解除によってメインキューでクラッシュが発生し、問題を解決します。

明白なアーキテクチャ上の解決策は、自分のコールバックブロックで__weakという自己参照を使用することですが、iOS 4.3のサポートがなくなるまで待つ必要があります。

+1

弱い参照が好きな人は、http://mikeash.com/pyblog/introducing-mazeroingweakref.htmlを参照してください。 – tapi

+0

少し醜いですが、それは巧妙なトリックです。私はより良い答えを持っていればいいと思う。 –

+0

ええ、私はMike Ashのソリューションを見ていましたが、私は短期間でiOS 4.3しかサポートしていないので、ちょっと試してみます。 –

0

あなたの分析は健全なようです。だから、ブロックの終わりがコントローラを保持し、メインスレッド上で自動解放を行うことができます(競合状態を避けるためにおそらく少しの遅延の後に)。

+0

私はARCでコンパイルしていますので、retainとautoreleaseの呼び出しは無効です。私はブロックのスコープをキャプチャするdispatch_after呼び出しでそれを偽造することができましたが、私はより良い解決策を見つけたと思います。 –

+0

Doh。おそらくそれは参照を持つブロックをディスパッチするだけで動作しますが(これはあなたがやったことだと思いますが、インサイドアウトです)。 –

0

あなたが知っているように、オブジェクトが特定のスレッドで割り当て解除されることを決して保証することはできません。

私が見つけた最良の解決策は、あなたのdeallocにあります。あなたがメインスレッドにいるかどうかを確認してください。そうでない場合は、残りのdeallocでメインスレッドでSelectSelectorを実行し、完了を待ちます。

また、ブロックと同じロジックを実装してください。メインスレッドでない場合は、残りのdeallocでメインキューにディスパッチする必要があります。あなたのキューが取り壊されているよう

あなたの問題

+0

しかし、私は明示的に '[dealloc]'を呼び出すことはできないので、それはARCではうまくいきません。 '[super dealloc]'さえも、実行をメインスレッドに切り替える方法はありません。 –

+0

Deallocはまだ存在します。 ARCは自動的にあなたのためにそれを呼び出します。 – user1139069

2

アッシュは、早期に解除されます。したがって、それをやめてください。どうやって?あなたはほとんどそこにいます。バックグラウンドキューから、メインスレッドのコントローラにデータをSYNCHRONOUSLYして返す必要があります。そうすれば、すべてが解けば安全な順序で割り当てが解除されます。例:

dispatch_async(backgroundQueue, ^{ 

    /* download stuff */ 

    dispatch_sync(dispatch_get_main_queue(), ^{ 

     // Pass the data to the controller. 
    }); 
}); 

このパターンを使用すると、メインスレッドの任意のUIコンポーネントにデータを正しく配信できるようになり、正常に解放されます。 __block変数を有する第二の答えのための

アンドリュー

編集:ARCで

__block UIViewController *vc = danglingVC; 

dispatch_async(backgroundQueue, ^{ 

    /* download stuff */ 

    dispatch_async(dispatch_get_main_queue(), ^{ 

     // Pass the data to the controller. 

     vc = nil; 
    }); 
}); 

あなたはnilに強いストレージスロットを設定することにより、リリースを強制します。私は今、主スレッド非同期への転送を行うことができます。私はこれがあなたの問題に対する明確かつ明白な解決策だと思う。 ARC、GCD、ブロック間の微妙な相互作用には依存しません。

アンドリュー

+1

これは、内部ブロックが実行される順序だけ、割り当てが解除される順序を変更しません。 –

+0

BJ、物事が実行される順序を変更することがポイントです。同期呼び出しは、非同期キューが排除されないようにします。したがって、コントローラの保持は、ダウンロードが完了し、データがコントローラに配信されるまで解放されません。これにより、解放の順序が安全な順序に劇的に変わります。 Andrew – adonoho

+0

@adonohoこれは、私のオブジェクト上の時期尚早のリリースではなく、範囲の問題です。あなたの 'dispatch_sync'ソリューションを使用しても、最初の行(' backgroundQueue')に渡されたブロックオブジェクトは、最終的にView Controllerを保持しています。ブロックオブジェクトは、同期呼び出しでもバックグラウンドスレッドで常に解放されます。 –

3

実際に、GCDでは、この目的のためにディスパッチキューの保持と解放が許可されています。実際には"Memory Management for Dispatch Queues"に記載されています。

dispatch_retain(first_queue); 
dispatch_async(a_queue, ^{ 
          do_not_wait_for_me(); 
          dispatch_async(first_queue, ^{ i_am_done_now(); }); 
          dispatch_release(first_queue); 
         }); 

あなたのシナリオでは、私はあなたのメインディスパッチキューに置き換えます。メインディスパッチキューを保持することで、コールバックが終了するまで解放されないことを保証します。代替ディスパッチ非同期外部スコープ内のオブジェクトの参照を格納することであろう「Performing a Completion Block When a Task Is Done.

+0

これは、バックグラウンドスレッドでuitextviewが* deallocated *になる問題を解決しません。 –

+0

あなたは私にUITextViewに関連するもう少しのコンテキストを教えてもらえますか?私の例と答えの両方では、デバッグ出力を除いて、UITextViewへの参照はありません。また、あなたの答えでは、あなたのブロックをスタックローカルデータ構造にするパターンを使用しているようです。これは、[「ブロックプログラミングトピック:回避するためのパターン」]というパターンです(http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Blocks/Articles/bxUsing.html#//apple_ref/doc/uid/TP40007502-CH5-SW6)は、スコープの問題が原因であると警告します。 – flukey

0

別の例は、で見つけることができます。たとえば、

// create a dispatch queue 
dispatch_queue_t sdq = dispatch_queue_create("com.fred.exampleQueue", NULL); 

// my object references container 
__block NSMutableArray *ressources = [[NSMutableArray alloc] init]; 

dispatch_async(sdq, ^{ 
    __block MyLoader *ml = [[MyAsyncLoader alloc] initWithCallback:^(id result) 
    { 
     NSLog(@"loader result: %@", result); 
     // since I ask for ressource in my final CallBack it's still here 
     // and my loader too, I can now dispose of it. 
     [ressources removeObject:ml]; 
    }]; 

    // perform async loading and call my callback when it's done... 
    [ml asyncLoad]; 
    // save my object 
    [ressources addObject:ml]; 
}); 
0

最近、同様の問題が発生しました。私はこの特定の問題を解決するために、ARCで弱い参照をゼロにするという贅沢を持っていました。しかし、私はMRCで動作するはずの代替ソリューションを検討しました。 vc

__block UIViewController *vc = ...; 
[vc retain]; 
dispatch_async(backgroundQueue, ^{ 
    // ... do some things with vc 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     // ... do more things with vc 
     [vc release]; // vc might be dealloc'd here but not before 
    }); 
}); 

__blockストレージ修飾子は、ブロックがvcによって参照されるオブジェクトを保持しないことを保証します。 View Controllerは、メインキューで呼び出されたブロックがそれを解放し、その時点でdeallocされるまで保持されます。

関連する問題