web-dev-qa-db-ja.com

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

動作するアプリがあり、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規則に準拠するためにこれをどのように変更する必要がありますか?

222
XJones

短い答え

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

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

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

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

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

何がbestかは、変更できるコードとできないコードに依存します。しかし、うまくいけば、これがあなたに進むべき方法についてのいくつかのアイデアを与えてくれました。

509
benzado

また、今後サイクルが中断されることを確信している場合は、警告を抑制するオプションもあります。

#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の接頭辞をいじる必要はありません。

25
zoul

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

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

次に、コードで次のことができます。

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

__blockキーワードを使用して、ARCなしのソリューションもARCで機能すると信じています。

編集: 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];
11
Tony

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

__typeof(self) __weak welf = self;

メソッド/関数で補完プレフィックス「welf」を使用して XCode Code Snippet として設定し、「we」のみを入力した後にヒットします。

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

上記の警告が表示されるよりもselfによって強く保持されるブロック内でselfまたはそのプロパティを参照する場合。

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

__weak typeof(self) weakSelf = self;

ので、代わりに

blockname=^{
    self.PROPERTY =something;
}

私たちは使うべきです

blockname=^{
    weakSelf.PROPERTY =something;
}

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

6
Anurag Bhakuni

これを行う新しい方法は、@ weakifyおよび@strongify marcoを使用することです。

@weakify(self);
[self methodThatTakesABlock:^ {
    @strongify(self);
    [self doSomething];
}];

@ Weakify @Strongify Marcoの詳細

1
Jun Jie Gan