28

シグナルを作成して関数のスコープに入れると、有効な保持カウントは Cocoa の規則に従って 0 になります。

RACSignal *signal = [self createSignal];

シグナルをサブスクライブすると、サブスクライバーが保持され、Cocoa の規則に従って保持カウントがゼロの使い捨てが返されます。

RACDisposable *disposable = [signal subscribeCompleted:^ {
    doSomethingPossiblyInvolving(self);
}];

ほとんどの場合、サブスクライバーは閉じて、selfまたはその ivar またはそれを囲むスコープの他の部分を参照します。したがって、シグナルをサブスクライブすると、シグナルにはサブスクライバーへの所有参照があり、サブスクライバーにはあなたへの所有参照があります。そして、見返りに得られる使い捨てには、シグナルへの所有参照があります。

disposable -> signal -> subscriber -> calling scope

ある時点でサブスクリプションをキャンセルできるように、使い捨てを保持しているとします (たとえば、シグナルが Web サービスからデータを取得していて、ユーザーが画面から離れて、取得中のデータを表示する意図をキャンセルした場合)。

self.disposeToCancelWebRequest = disposable;

この時点で、循環参照があります。

calling scope -> disposable -> signal -> subscriber -> calling scope

責任を持って行うべきことは、リクエストをキャンセルするとき、またはリクエストが完了した後にサイクルが壊れていることを確認することです。

 [self.disposeToCancelWebRequest dispose]
 self.disposeToCancelWebRequest = nil;

selfの割り当てが解除されているときはこれを行うことができないことに注意してください。サブスクライバーへのコールバック中に保持サイクルを中断することについても、何か怪しいように見えます。これは、その実装がまだコール スタック上にある間にシグナルが割り当て解除される可能性があるためです。

また、実装がアクティブなシグナルのプロセスグローバルリストを保持していることにも気付きました(最初にこの質問をしている時点で)。

RAC を使用する場合、所有権についてどのように考える必要がありますか?

4

2 に答える 2

55

正直なところ、ReactiveCocoa のメモリ管理は非常に複雑ですが、価値のある最終結果は、シグナルを処理するためにシグナルを保持する必要がないことです

フレームワークですべてのシグナルを保持する必要がある場合、特に先物 (ネットワーク リクエストなど) のように使用されるワンショット シグナルの場合は、使用するのがはるかに扱いにくくなります。長期間有効なシグナルをプロパティに保存し、使い終わったら必ずクリアする必要があります。楽しくない。

加入者

subscribeNext:error:completed:先に進む前に、 (およびそのすべてのバリアントは)指定されたブロックを使用して暗黙的なサブスクライバーを作成することを指摘しておく必要があります。したがって、これらのブロックから参照されるすべてのオブジェクトは、サブスクリプションの一部として保持されます。他のオブジェクトと同様selfに、直接的または間接的な参照なしでは保持されません。

(あなたの質問の言い回しに基づいて、あなたはすでにこれを知っていると思いますが、他の人にとっては役立つかもしれません。)

有限または短命の信号

RAC メモリ管理の最も重要なガイドラインは、サブスクリプションが完了またはエラー時に自動的に終了し、サブスクライバーが削除されることです。循環参照の例を使用するには:

calling scope -> disposable -> signal -> subscriber -> calling scope

…これは、signal -> subscriber関係が終了するとすぐに取り壊されsignal、維持サイクルが中断されることを意味します。

多くの場合、必要なのはこれだけです。これはRACSignal、メモリ内の の有効期間がイベント ストリームの論理的な有効期間と自然に一致するためです。

無限のシグナル

ただし、無限のシグナル (または無限に近いほど長生きするシグナル) は、自然に破壊されることはありません。これは、使い捨てが輝くところです。

サブスクリプションを破棄すると、関連付けられているサブスクライバーが削除され、通常は、そのサブスクリプションに関連付けられているすべてのリソースがクリーンアップされます。その 1 つのサブスクライバーにとっては、シグナルで最終イベントが送信されないことを除いて、シグナルが完了またはエラーになったかのようになります。他のすべてのサブスクライバーはそのまま残ります。

ただし、一般的な経験則として、サブスクリプションのライフサイクルを手動で管理する必要がある場合は、おそらくもっと良い方法があります。-take:またはのようなメソッド-takeUntil:が破棄を処理し、最終的にはより高いレベルの抽象化になります。

由来するシグナルself

ただし、ここにはまだ少しトリッキーな中間ケースがあります。シグナルの有効期間が呼び出しスコープに関連付けられている場合はいつでも、断ち切るのがはるかに困難なサイクルになります。

これは、 を基準とするキー パスでRACAble()またはを使用し、 をキャプチャする必要があるブロックを適用する場合によく発生します。RACAbleWithStart()selfself

ここでの最も簡単な答えは、弱くキャプチャすることselfです:

__weak id weakSelf = self;
[RACAble(self.username) subscribeNext:^(NSString *username) {
    id strongSelf = weakSelf;
    [strongSelf validateUsername];
}];

または、含まれているEXTScope.hヘッダーをインポートした後:

@weakify(self);
[RACAble(self.username) subscribeNext:^(NSString *username) {
    @strongify(self);
    [self validateUsername];
}];

(オブジェクトが弱い参照をサポートしていない場合は、またはをそれぞれ__weakまたはに置き換えます。)@weakify__unsafe_unretained@unsafeify

ただし、代わりに使用できるより良いパターンがおそらくあります。たとえば、上記のサンプルはおそらく次のように記述できます。

[self rac_liftSelector:@selector(validateUsername:)
           withObjects:RACAble(self.username)];

また:

RACSignal *validated = [RACAble(self.username) map:^(NSString *username) {
    // Put validation logic here.
    return @YES;
}];

self無限シグナルと同様に、一般に、シグナル チェーン内のブロックからの参照 (または任意のオブジェクト) を回避する方法があります。


上記の情報は、ReactiveCocoa を効果的に使用するために必要なすべてです。ただし、技術的に興味のある方、または RAC への貢献に関心のある方のために、もう 1 点説明したいと思います。

また、実装がアクティブなシグナルのプロセス グローバル リストを保持していることにも気付きました。

これは絶対に真実です。

「保持する必要がない」という設計目標は、次のような疑問を投げかけます: 信号の割り当てをいつ解除すべきかをどのように知ることができるでしょうか? 作成されたばかりで、自動解放プールをエスケープし、まだ保持されていない場合はどうなりますか?

本当の答えはそうではありませんが、通常、呼び出し元が保持したい場合は、現在の実行ループの反復内でシグナルを保持すると想定できます。

その結果:

  1. 作成されたシグナルは、アクティブなシグナルのグローバル セットに自動的に追加されます。
  2. シグナルは、メインの実行ループの 1 回のパスを待機し、サブスクライバーがない場合はアクティブ セットから自身を削除します。何らかの方法でシグナルが保持されない限り、この時点で割り当てが解除されます。
  3. その実行ループ反復で何かがサブスクライブした場合、シグナルはセットに残ります。
  4. その後、すべてのサブスクライバーがなくなると、#2 が再びトリガーされます。

実行ループが (OS X のモーダル イベント ループのように) 再帰的にスピンされる場合、これは逆効果になる可能性がありますが、他のほとんどまたはすべてのケースでは、フレームワーク コンシューマーの寿命が大幅に短縮されます。

于 2012-12-31T12:33:21.423 に答える