正直なところ、ReactiveCocoa のメモリ管理は非常に複雑ですが、価値のある最終結果は、シグナルを処理するためにシグナルを保持する必要がないことです。
フレームワークですべてのシグナルを保持する必要がある場合、特に先物 (ネットワーク リクエストなど) のように使用されるワンショット シグナルの場合は、使用するのがはるかに扱いにくくなります。長期間有効なシグナルをプロパティに保存し、使い終わったら必ずクリアする必要があります。楽しくない。
加入者
subscribeNext:error:completed:
先に進む前に、 (およびそのすべてのバリアントは)指定されたブロックを使用して暗黙的なサブスクライバーを作成することを指摘しておく必要があります。したがって、これらのブロックから参照されるすべてのオブジェクトは、サブスクリプションの一部として保持されます。他のオブジェクトと同様self
に、直接的または間接的な参照なしでは保持されません。
(あなたの質問の言い回しに基づいて、あなたはすでにこれを知っていると思いますが、他の人にとっては役立つかもしれません。)
有限または短命の信号
RAC メモリ管理の最も重要なガイドラインは、サブスクリプションが完了またはエラー時に自動的に終了し、サブスクライバーが削除されることです。循環参照の例を使用するには:
calling scope -> disposable -> signal -> subscriber -> calling scope
…これは、signal -> subscriber
関係が終了するとすぐに取り壊されsignal
、維持サイクルが中断されることを意味します。
多くの場合、必要なのはこれだけです。これはRACSignal
、メモリ内の の有効期間がイベント ストリームの論理的な有効期間と自然に一致するためです。
無限のシグナル
ただし、無限のシグナル (または無限に近いほど長生きするシグナル) は、自然に破壊されることはありません。これは、使い捨てが輝くところです。
サブスクリプションを破棄すると、関連付けられているサブスクライバーが削除され、通常は、そのサブスクリプションに関連付けられているすべてのリソースがクリーンアップされます。その 1 つのサブスクライバーにとっては、シグナルで最終イベントが送信されないことを除いて、シグナルが完了またはエラーになったかのようになります。他のすべてのサブスクライバーはそのまま残ります。
ただし、一般的な経験則として、サブスクリプションのライフサイクルを手動で管理する必要がある場合は、おそらくもっと良い方法があります。-take:
またはのようなメソッド-takeUntil:
が破棄を処理し、最終的にはより高いレベルの抽象化になります。
由来するシグナルself
ただし、ここにはまだ少しトリッキーな中間ケースがあります。シグナルの有効期間が呼び出しスコープに関連付けられている場合はいつでも、断ち切るのがはるかに困難なサイクルになります。
これは、 を基準とするキー パスでRACAble()
またはを使用し、 をキャプチャする必要があるブロックを適用する場合によく発生します。RACAbleWithStart()
self
self
ここでの最も簡単な答えは、弱くキャプチャすること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 が再びトリガーされます。
実行ループが (OS X のモーダル イベント ループのように) 再帰的にスピンされる場合、これは逆効果になる可能性がありますが、他のほとんどまたはすべてのケースでは、フレームワーク コンシューマーの寿命が大幅に短縮されます。