APIを実装するときに、ブロック内で自己をキャプチャしないようにするにはどうすればよいですか?


222

動作するアプリがあり、Xcode 4.2でそれをARCに変換する作業をしています。チェック前の警告の1つは、selfブロックを強くキャプチャして保持サイクルにつながることです。この問題を説明するために、簡単なコードサンプルを作成しました。私はこれが何を意味するのか理解していると思いますが、このタイプのシナリオを実装するための「正しい」または推奨される方法がわかりません。

  • 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];

質問:何が「間違っている」のか、および/またはこれをARCの規則に準拠するように変更するにはどうすればよいですか?

回答:


509

短い答え

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への参照を保持する必要があります。アプリの他のすべてがこのオブジェクトへの参照を解放する場合、その保持カウントはゼロではなく(ブロックがそれを指しているため)、ブロックは何も問題を起こしていません(オブジェクトがそれを指しているため)など。オブジェクトのペアはヒープにリークし、メモリを占有しますが、デバッガなしでは永遠に到達できません。悲劇、本当に。

その場合は、代わりに次のようにすることで簡単に修正できます。

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];
};

このソリューションは、保持サイクル回避し、常に現在のデリゲートを呼び出します。

ブロックを変更できない場合は、対処できます。保持サイクルがエラーではなく警告である理由は、それらが必ずしもアプリケーションの運命を綴るとは限らないためです。MyDataProcessorが操作の完了時にブロックを解放できる場合、親がブロックを解放しようとする前に、サイクルが中断され、すべてが適切にクリーンアップされます。これが確実である場合、正しいこと#pragmaは、コードブロックの警告を抑制するためにa を使用することです。(または、ファイルごとのコンパイラフラグを使用します。ただし、プロジェクト全体の警告を無効にしないでください。)

また、上記と同様のトリックを使用して、参照を弱または非保持として宣言し、それをブロックで使用することも検討できます。例えば:

__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他の場所では使用されないため、関係ありません)。

何が最適かは、変更できるコードと変更できないコードによって異なります。しかし、うまくいけば、これがあなたにどのように進むべきかについていくつかの考えを与えてくれました。


1
素晴らしい答えです!おかげで、私は何が起こっているのか、これがどのように機能するのかについてはるかによく理解しています。この場合、すべてを制御できるので、必要に応じてオブジェクトの一部を再構築します。
XJones、2011年

18
O_Oちょっと違う問題で通り過ぎていて、読みづらくて、このページはすべて知識が豊富でかっこいいと感じました。ありがとう!
Orc JMR 2012

正解dpです。たとえば、ブロック実行の瞬間に何らかの理由で解放される場合(たとえば、それがビューコントローラーであり、ポップされた場合)、行[dp.delegate ...によりEXC_BADACCESSが発生しますか?
peetonn 2013年

ブロックを保持しているプロパティは、(例えばdataProcess.progress)でなければなりませんstrongweak
djskinner 2013年

1
libextobjcを見ると、2つの便利なマクロが呼び出され@weakify(..)、保持しない@strongify(...)方法selfでブロックを使用できます。

25

将来サイクルが壊れることがわかっている場合に警告を抑制するオプションもあります。

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

#pragma clang diagnostic pop

そうすれば__weakselfエイリアスや明示的なivarプレフィックスを付けて、あちこちに移動する必要がなくなります。


8
__weak id weakSelf = self;に置き換えることができる3行を超えるコードを必要とする非常に悪い習慣のように聞こえます。
ベンシンクレア

3
多くの場合、抑制された警告の恩恵を受けることができるコードのより大きなブロックがあります。
zoul

2
ただし__weak id weakSelf = self;、警告の抑制とは基本的に動作が異なります。質問は「保持サイクルが壊れると肯定的である場合」で始まりました
Tim

影響を実際に理解せずに、盲目的に変数を弱める人が多すぎる。たとえば、私は人々がオブジェクトを弱体化し、ブロックで[array addObject:weakObject];弱体化するのを見てきました。weakObjectがリリースされている場合、これによりクラッシュが発生します。明らかにそれは保持サイクルよりも好ましくありません。ブロックが実際に弱体化を保証するのに十分な長さであるかどうか、また、ブロック内のアクションを弱いオブジェクトがまだ有効かどうかに依存させるかどうかを理解する必要があります。
mahboudz

14

一般的な解決策として、これらをプリコンパイルヘッダーで定義しています。キャプチャを回避し、使用を避けることでコンパイラのヘルプを有効にしますid

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

次に、コードで行うことができます:

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};

これは、ブロック内で問題を引き起こす可能性があることに同意しました。ReactiveCocoaには、この問題に対する別の興味深い解決策がselfあり、ブロック内で@weakify(self)を引き続き使用できます。idブロック= ^ {@strongify(self); [self.delegate myAPIDidFinish:self]; };
Damien Pontifex 2014年

@dmpontifexそれはlibextobjcからマクロだgithub.com/jspahrsummers/libextobjc
Elechtron

11

ARCなしのソリューションはARCでも動作し、__blockキーワードを使用すると思います。

編集:ARCへの移行のリリースノートに従って__blockストレージで宣言されたオブジェクトは引き続き保持されます。__weak(推奨)または__unsafe_unretained(下位互換性のため)を使用します。

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

__blockキーワードがリファレントの保持を回避していることを理解していませんでした。ありがとう!モノリシックな答えを更新しました。:-)
ベンザド

3
Appleのドキュメントによると、「手動参照カウントモードでは、__ block id x;はxを保持しない効果があります。ARCモードでは、__ block id x;はデフォルトでxを保持します(他のすべての値と同様)。」
XJones、2011年

11

他のいくつかの答えを組み合わせて、型付きの弱い自己がブロックで使用するためにこれを使用しています:

__typeof(self) __weak welf = self;

私はそれをメソッド/関数の補完接頭辞が「welf」であるXCodeコードスニペットとして設定します。


本気ですか?このリンクとclangのドキュメントは、オブジェクトへの参照を保持するために両方を使用できると考え、使用すべきであると考えていますが、保持サイクルを引き起こすリンクではありません: stackoverflow.com/questions/19227982/using-block-and-weak
Kendall Helmstetter Gelner 2015

clangのドキュメントから:clang.llvm.org/docs/BlockLanguageSpec.html "Objective-CおよびObjective-C ++言語では、オブジェクトタイプの__block変数の__weak指定子を許可します。ガベージコレクションが有効でない場合、この修飾子はこれらの変数は、メッセージが送信されずに保持されることなく保持されます。」
Kendall Helmstetter Gelner、2015


6

警告=>「ブロック内で自己をキャプチャすると、保持サイクルが発生する可能性があります」

上記の警告よりも、自分自身によって強く保持されているブロック内の自分自身またはそのプロパティを参照する場合。

それを避けるために、私たちはそれを1週間の参照にする必要があります

__weak typeof(self) weakSelf = self;

だから代わりに

blockname=^{
    self.PROPERTY =something;
}

私たちは使うべきです

blockname=^{
    weakSelf.PROPERTY =something;
}

注:通常、保持サイクルは、2つのオブジェクトが参照カウント= 1であり、dellocメソッドが呼び出されていない2つのオブジェクトが相互に参照している場合に発生します。



-1

コードが保持サイクルを作成しないことが確実な場合、またはサイクルが後で壊れる場合は、警告を止める最も簡単な方法は次のとおりです。

// 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];

これが機能する理由は、プロパティのドットアクセスがXcodeの分析で考慮されるため、

x.y.z = ^{ block that retains x}

x of y(代入の左側)とy of x(右側)の保持があると見なされる場合、メソッド呼び出しは、プロパティアクセスメソッド呼び出しであっても、同じ分析の対象にはなりません。これらのプロパティアクセスメソッドがコンパイラによって生成された場合でも、ドットアクセスと同等なので、

[x y].z = ^{ block that retains x}

右側のみが保持を作成していると見なされ(xのyによる)、保持サイクル警告は生成されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.