[解決済み】APIを実装する際に、ブロック内でselfをキャプチャしないようにするにはどうすればいいですか?
質問
動作中のアプリがあり、Xcode 4.2でARCに変換しているところです。事前チェックの警告のひとつに
self
が強く、retain cycleにつながるブロックです。この問題を説明するために、簡単なコードサンプルを作成しました。私はこの意味を理解しているつもりですが、このタイプのシナリオを実装するための正しい方法、または推奨される方法がわかりません。
- self は MyAPI クラスのインスタンスです。
- 以下のコードは、私の質問に関連するオブジェクトとブロックとのインタラクションのみを表示するために簡略化されています。
- MyAPI がリモート ソースからデータを取得し、MyDataProcessor がそのデータを処理して出力を生成すると仮定します。
- プロセッサは、進捗と状態を通信するためのブロックで構成されています。
コードサンプルです。
// code sample
self.delegate = aDelegate;
self.dataProcessor = [[MyDataProcessor alloc] init];
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
self.dataProcessor.completion = ^{
[self.delegate myAPIDidFinish:self];
self.dataProcessor = nil;
};
// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];
質問:私は何が間違っているのでしょうか?
どのように解決するのですか?
簡単な答え
にアクセスするのではなく
self
を直接使用するのではなく、保持されない参照から間接的にアクセスする必要があります。
自動参照カウント(ARC)を使っていない場合
であれば、可能です。
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
は
__block
キーワードはブロック内で変更可能な変数をマークしますが(そんなことはしません)、ブロックが保持されるときに自動的に保持されることもありません(ARCを使用している場合を除く)。これを行う場合、MyDataProcessor インスタンスが解放された後に、他の何もブロックを実行しようとしないことを確認する必要があります。(コードの構造を考えると、それは問題ではないはずです)。
について詳しく読む
__block
.
ARCを使用する場合
のセマンティクスは
__block
が変更され、参照は保持されます。
__weak
の代わりに
長い回答
例えば、こんなコードがあったとします。
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
一方、ブロックはデリゲートのプロパティを取得し、デリゲートにメソッドを送るために、selfへの参照を保持する必要があります。アプリ内の他のすべてのものがこのオブジェクトへの参照を解放しても、そのretainカウントはゼロにはならず(ブロックがそれを指しているから)、ブロックは何も間違っていない(オブジェクトがそれを指しているから)ので、オブジェクトのペアはヒープに漏れ、メモリを占有するがデバッガなしでは永遠に到達できなくなるのです。悲劇ですね、本当に。
その場合は、代わりにこうすれば簡単に解決します。
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
このコードでは、selfがブロックを保持し、ブロックがデリゲートを保持し、サイクルはありません(ここから見える。デリゲートは我々のオブジェクトを保持するかもしれませんが、それは今我々の手の届かないところにあります)。このコードでは、デリゲートプロパティの値は、実行時に検索されるのではなく、ブロックが作成されたときに取得されるので、同じようにリークのリスクはありません。副作用として、このブロックが作成された後にデリゲートを変更した場合、ブロックは古いデリゲートに更新メッセージを送信します。このようなことが起こる可能性があるかどうかは、アプリケーションに依存します。
仮にその動作に冷静だったとしても、あなたの場合はそのトリックを使うことはできない。
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
ここでは
self
をメソッド呼び出しの中で直接デリゲートに渡すので、どこかでそれを取り込まなければなりません。もしブロックタイプの定義をコントロールできるのであれば、デリゲートをパラメータとしてブロックに渡すのがベストでしょう。
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
このソリューションでは、retainサイクルを回避することができます と は常に現在のデリゲートを呼び出します。
もしブロックを変更できないのであれば
対処法
. 保持サイクルがエラーではなく警告である理由は、必ずしもアプリケーションに破滅をもたらすとは限らないからです。もし
MyDataProcessor
が操作の完了時に、親がブロックを解放しようとする前にブロックを解放することができれば、サイクルは中断され、すべてが適切にクリーンアップされます。もしこのことを確認できたなら、正しいのは
#pragma
を使用すると、そのコードブロックに対する警告を抑制することができます。(または、ファイルごとのコンパイラ・フラグを使用します。ただし、プロジェクト全体の警告を無効にしないこと)。
また、上記のようなトリックを使って、参照をweakまたはunretainedと宣言し、ブロック内でそれを使用することも検討してみてください。例えば
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
上記の3つは、少し挙動が異なりますが、いずれも結果を保持せずに参照を与えます。
__weak
は、オブジェクトが解放されたときに参照をゼロにしようとします。
__unsafe_unretained
は無効なポインタを残してしまいます。
__block
は実際にはもう一段階間接的なレベルを追加し、 ブロックの中から参照の値を変更できるようにします (このケースでは
dp
は他のどこにも使われていない)。
何が ベスト は、変更できるコードとできないコードに依存します。しかし、どのように進めればよいのか、いくつかのアイデアを得ることができたと思います。
関連
-
[解決済み] イニシャライザーの要素がコンパイル時定数でない
-
[解決済み] NSTaggedPointerStringをNSStringに変換する。
-
[解決済み] クラス 'test_coredataAppDelegate' の重複したインターフェイス宣言
-
[解決済み] Objective-Cのnil、NIL、nullの違いについて
-
[解決済み] objcの "pi "と "M_PI "の違いは何ですか?
-
[解決済み] Xcodeでコンソールに何かを印刷するには?
-
[解決済み] キーボードがあるときに、UITextFieldを編集開始時に上に移動させるには?
-
[解決済み] synthesize vs @dynamic、その違いとは?
-
[解決済み] Objective-CでNSArrayを逆引きするにはどうしたらいいですか?
-
[解決済み] registerForRemoteNotificationTypes: は iOS 8.0 以降でサポートされていません。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Objective-Cのtypedef enumとは何ですか?
-
[解決済み] Objective-Cで抽象クラスを作成する
-
[解決済み] ブロック(__block)」というキーワードはどういう意味ですか?
-
[解決済み】浮動小数点値の比較はどのくらい危険か?
-
[解決済み】浮動小数点以下が2桁しか表示されないようにする
-
[解決済み】iOS7でステータスバーを隠すことができない。
-
[解決済み】コンパイルの警告:アーキテクチャi386のファイルを処理するルールがない
-
[解決済み] [Solved] UITableViewがReloadDataを完了したことを伝えるには?
-
[解決済み】ARCで@autoreleasepoolがまだ必要なのはなぜですか?
-
[解決済み】NSMutableArrayから反復処理中に削除する最良の方法は?