ReactiveCocoa 3.0に採用予定のSignalGeneratorは何らかの値からシグナルを生成するためのロジックを抽象化したものです。
本記事では、まず簡単にSignalGeneratorを紹介した後に、実際にSignalGeneratorを利用している例として、ReactiveCocoa 3.0開発版のrepeat
とretry:
を見ていきます。
SignalGenerator概要
RACSignalGenerator
はSignalGeneratorの抽象基底クラスです。
通常はこのクラスのサブクラスであるRACDynamicSignalGenerator
などを利用します。
RACSignalGenerator
でシグナルを生成するときには次のメソッドを利用します。RACSignalGenerator
でこれを呼ぶと必ずnil
を返します。
1 | - ( RACSignal *)signalWithValue:( id )input; |
クラスをわざわざ作る必要があるのか疑問に思われる方もいるかと重いますが、SignalGeneratorを使うのは次の2つの利点があります。
- SignalGeneratorはロジックをカプセル化しているので、これらを組み合わせて別のSignalGeneratorを作る、なんてことも容易にできるようになります。実際に、
RACSignalGenerator
にはpostcompose:
というメソッドを用意しています。 - SignalGeneratorはシグナル生成地に自分自身を参照できます。これによって再帰的なものを書くことができます。
RACDynamicSignalGenerator
RACDynamicSignalGenerator
はRACSignalGenerator
のサブクラスです。このクラスのクラスメソッドgeneratorWithReflexiveBlock:
を使うと、再帰的な処理を書くことができます。
これは次のようなメソッドです。シグナルを生成しようとするときに自分自身を参照することができるblockを指定します。
1 2 3 4 5 6 | + (instancetype)generatorWithReflexiveBlock:( RACSignal * (^)( id input, RACDynamicSignalGenerator *generator))block { RACDynamicSignalGenerator *generator = [ self alloc ]; return [generator initWithBlock :^( id input) { return block(input, generator); }]; } |
signalWithValue:
は次のようにシンプルなものです。
1 2 3 | - ( RACSignal *)signalWithValue:( id )input { return self .block (input); } |
RACDynamicSignalGenerator
の利用例
ではRACDynamicSignalGenerator
を利用したメソッドrepeat
とretry:
を見ていきましょう。
なお、これらは前回紹介したconcat:
とcatch:
を利用しているので、必要ならこちらも参照してください。
repeat
repeat
はレシーバのシグナルを永遠に繰り返します。つまり次のようなものが無限に続いているものを作ればよいです。
1 | [[[ self concat : self ] concat : self ] concat : self ]; |
RACDynamicSignalGenerator
とdefer:
を用いれば、上記のような無限の繰り返しのシグナルを作ることができます。
1 2 3 4 5 6 7 8 | - ( RACSignal *)repeat { RACSignalGenerator *generator = [ RACDynamicSignalGenerator generatorWithReflexiveBlock :^( RACSignal *signal, RACSignalGenerator *generator) { return [signal concat :[ RACSignal defer :^{ return [generator signalWithValue :signal]; }]]; }]; return [[generator signalWithValue : self ] setNameWithFormat : @"[%@] -repeat" , self .name ]; } |
defer:
はシグナルがサブスクライブされるときまでシグナルの生成を遅延してくれるメソッドです。サブスクライブされるときに引数のblockが実行されます。
repeat
の場合には、self
がcompleteイベントで終わろうとするときにdefer:
のblockが実行されることで、繰り返しでself
がサブスクライブされるようになります。
retry:
retry:
はレシーバのシグナルがErrorイベンドであったときに、それを指定回数に繰り返します。つまり、次のようなものが指定回数だけ続いているものを作ればよいです。
1 2 3 4 5 6 7 | [ self catch :^(NSError*){ return [ self catch :^(NSError* e){ return [ self catch :^(NSError* e){ return [ RACSignal error :e] }] }] }] |
これも、RACDynamicSignalGenerator
とdefer:
を用いると実現できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | - ( RACSignal *)retry:(NSUInteger)retryCount { return [[ RACSignal defer :^{ RACSignalGenerator *generator = [ RACDynamicSignalGenerator generatorWithReflexiveBlock :^( NSNumber *currentRetryCount, RACSignalGenerator *generator) { return [ self catch :^( NSError *error) { if (retryCount == 0 || currentRetryCount .unsignedIntegerValue < retryCount) { return [generator signalWithValue :@(currentRetryCount .unsignedIntegerValue + 1 )]; } else { // We've retried enough times, so let the error propagate. return [ RACSignal error :error]; } }]; }]; return [generator signalWithValue : @0 ]; }] setNameWithFormat : @"[%@] -retry: %lu" , self .name , (unsigned long )retryCount]; } |
例えば、retry:2
としたときの挙動を説明します。
-
まず、遅延で
[generator signalWithValue:@0]
が実行されます。 retryCount
は2
でcurrentRetryCount
は0
なので、if
文のtrue節が実行されることになります。見やすさのために、
if
文を外して、実際に実行される部分だけにしてみると、123return
[
self
catch
:^(
NSError
*error) {
return
[generator
signalWithValue
:@(
0
+
1
)];
}];
となります。
ここで、
self
がErrorイベントを送信したときにはcatch:
のblockが実行されることでsignalWithValue:@1
が実行されます。retryCount
は変化がないので2
のまま、currentRetryCount
は1
なので、if
文のtrue節が実行されます。見やすさのために、
if
文を外して、実際に実行される部分だけにしてみると、前とあまり変わらないような、123return
[
self
catch
:^(
NSError
*error) {
return
[generator
signalWithValue
:@(
1
+
1
)];
}];
が得られます。
ここで、さらに
self
がErrorイベントを送信したなら、signalWithValue:@2
が実行されます。retryCount
は変化がないので2
のまま、currentRetryCount
は2
なので、if
文のfalse節が実行されることになります。見やすさのために、
if
文を外して、実際に実行される部分だけにしてみると、123return
[
self
catch
:^(
NSError
*error) {
return
[
RACSignal
error
:error];
}];
のようになります。
上記3コードのgenerator
の部分をまとめると、
1 2 3 4 5 6 7 | return [ self catch :^( NSError *error) { return [ self catch :^( NSError *error) { return [ self catch :^( NSError *error) { return [ RACSignal error :error]; }]; }]; }]; |
となり、最初に書いたコードと同じになることがわかります。
おわりに
SignalGeneratorについて紹介しました。
RACDynamicSignalGenerator
とdefer:
あたりは理解するまでにちょっと時間がかかりましたが、わかると非常に綺麗なコードだと思えるようになりました (2.0とコードを比べるとよくわかると重います)。
みなさんも3.0が安定版になったときにはぜひとも使ってみてください。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。