web-dev-qa-db-ja.com

iOSブロックと自己への強い/弱い参照

IOSのブロック内の自己への強い参照と弱い参照について質問があります。次のように、ブロック内の自己を参照する適切な方法は、ブロックの外側に弱い参照を作成し、ブロック内のその弱い参照に強い参照を作成することです。

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
    typeof(self) strongSelf = weakSelf;
    NSLog(@"%@", strongSelf.someProperty);
});

ただし、ネストされたブロックがある場合はどうなりますか? 1セットの参照で十分ですか?または、各ブロックに新しいセットが必要ですか?たとえば、次のうち正しいものはどれですか?

この:

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
    typeof(self) strongSelf = weakSelf;
    NSLog(@"%@", strongSelf.someProperty);
    dispatch_async(dispatch_get_main_queue(), ^ {
        strongSelf.view.frame = CGRectZero;
    });
});

またはこれ:

__weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
        typeof(self) strongSelf = weakSelf;
        NSLog(@"%@", strongSelf.someProperty);
        __weak typeof(strongSelf) weakSelf1 = strongSelf;
        dispatch_async(dispatch_get_main_queue(), ^ {
            typeof(strongSelf) strongSelf1 = weakSelf1;
            strongSelf1.view.frame = CGRectZero;
        });
    });

どんな情報や説明も大歓迎です!

40
Mason

両方の構成体は問題ありません。それはあなたの意図次第です。オブジェクトが(a)外部ブロックが開始した後、(b)内部ブロックがメインキューで開始する前に解放された場合、どうなりますか?このシナリオでそれを保持したくない場合(最初にこのweakSelf演習を行っていることを考えれば、それはあなたの意図だったと思います)、最後の例を使用します。 2番目の弱いポインター。それ以外の場合は、他の例を使用できます。

そうは言っても、いくつかの観察:

  1. そもそもこのweakSelfパターンを使用しなければならないというのは、決して結論ではありません。一部の人々は、このweakSelfパターンを使用して強力な参照サイクル(別名、保持サイクル)を回避することをhaveと誤解しています。ただし、このコードサンプルは強力な参照サイクルを構成していません。ディスパッチされたコードの実行中にオブジェクトを保持するだけで、これは非常に異なる考慮事項です。

    実際、時々それが必要な場合があります。時々あなたはしません。それはあなたが解決しようとしているビジネス上の問題に依存します。絶対に、selfへの強い参照を保持したくないことがよくあります。その場合、weakSelfパターンは完全に意味をなします。しかし、常にそうとは限りません。

    しかし、私のポイントは、このweakSelfパターン(少なくともこのdispatch_asyncシナリオ)強い参照サイクルを回避します。そのようなサイクルは存在しません。これが問題になるのは、ブロック変数(たとえば、いくつかのcompletionHandlerブロック)がある場合です。その場合、weakSelfパターンが重要です。しかし、ここではありません。

  2. しかし、selfを保持したくないというシナリオについて少し考えてみましょう。次に、ディスパッチされたコードを最初から続行するかどうかの質問があります。そうでない場合は、GCDの代わりにキャンセル可能な操作で操作キューを使用する必要があります。

    たとえば、バックグラウンドネットワークリクエストの実行中にView Controllerを保持するかどうかについて人々がどれほど頻繁に苦しんでいるかに驚いていますが、そもそもそのバックグラウンドネットワークリクエストをキャンセルする必要があるかどうかは心配しないでください。多くの場合、後者ははるかに重要な設計上の考慮事項です(たとえば、PDFまたはダウンロードする画像は、View Controllerよりもはるかに多くのシステムリソース(メモリとネットワーク帯域幅の両方)を占有します) 。

  3. しかし、(a)ディスパッチされたコードの実行を本当に続けたいが、(b)selfを保持したくないと仮定しましょう。 (これはまれなシナリオのように思えますが、これはあなたが尋ねたものですので、それを追求しましょう。)strongSelfコンストラクトが必要かどうかの最後の質問も。 selfの単一のメソッドを呼び出しているだけの場合、このstrongSelf構造に煩わされる必要はありません。これは、ivarを優先する場合、または競合状態を回避する必要がある場合にのみ重要です。しかし、この例では、nilオブジェクトに送信されたメッセージが何もしない場合、このstrongSelfコンストラクトを心配する必要はほとんどありません。

誤解しないでください。 weakSelfパターンと、時に付随するネストされたstrongSelfパターンの周りに手を入れるのは良いことです。これらのパターンが本当に必要なときを理解することは良いことだと私は提案しているだけです。また、キャンセル可能なNSOperationに対するGCDの選択は、はるかに重要ですが、しばしば見過ごされがちな問題だと思います。

23
Rob

ブロックが作成され、スタックに保存されます。したがって、ブロックを作成したメソッドが戻ると、ブロックは破棄されます。

ブロックがインスタンス変数になったら、ARCをスタックからヒープにコピーします。コピーメッセージでブロックを明示的にコピーできます。ブロックは、スタックベースのブロックではなく、ヒープベースのブロックになりました。そして、いくつかのメモリ管理の問題に対処する必要があります。ブロック自体は、参照するオブジェクトへの強い参照を保持します。ブロック外で__weakポインターを宣言し、ブロック内でこのポインターを参照して、保持サイクルを回避します。

3
user3378170