この記事について
この記事はConquering ReactiveSwift: SignalProducer (Part 4)の翻訳です。
以下本文です。
ReactiveSwiftを克服する: SignalProducer (Part 4)
ReactiveSwiftを克服するシリーズのPart 4にようこそ。前の記事では、Signalの生成と監視について学びました。今回は、Sourceカテゴリに属する重要な基本要素であるSignalProducerを紹介します。
定義
名前の示す通り、SignalProducerはSignalを生産します。簡単に言うと、SignalProducerは遅延実行したり、繰り返し実行することができるタスクをカプセル化します。SignalProducerをstartするとSignalを生産します。
なぜSignalProducerは便利なのでしょうか。
前回の記事の課題を思い出してください。
50秒の間、5秒に一度の間隔で経過時間を出力しなさい。
この課題に対して、私たちは、50秒の間、5秒ごとにInt型の値を排出するSignalを生成しました。そして、それらの値を監視して経過時間を出力しました。それでは、ボタンを押すと監視を開始したい場合を考えてみましょう。ObserverはSignalの監視しかできません。開始したり停止したりすることはできません。こういうときにぴったりなのがSignalProducerです。
くわしくみていきましょう。
まずは、Intの値を排出するコードをSignalProducerの中にカプセル化します。
let signalProducer: SignalProducer<Int, NoError> =
SignalProducer { (observer, lifetime) in
for i in 0..<10 {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0 * Double(i)) {
observer.send(value: i)
if i == 9 { // 9回目で完了します
observer.sendCompleted()
}
}
}
}
ここでは、SignalProducerがクロージャで初期化されています。SignalProducerのstart
メソッドを呼び出すとこのクロージャが実行されます。クロージャの引数はobserver(型は Signal.Observer<Int, NoError>
)とlifetime(型はLifetime)です。observerは値を送るために使います。監視が中断したので処理を取りやめたいときにlifetimeを使います。
SignalProducerの準備ができたので、開始して監視しましょう。
// Observerを生成する
let signalObserver = Signal<Int, NoError>.Observer (
value: { value in
print("Time elapsed = \(value)")
}, completed: {
print("completed")
}, interrupted: {
print("interrupted")
})
// signalProducerをstartする
signalProducer.start(signalObserver)
SignalProducerがstartするたびに異なるSignalが生産されることに注意してください。同じSignalProducerでも、startの呼び出しが異なると、Observerが受け取る値や順番も異なります。
訳注: 画像は元記事からの引用です
SignalProducerを10秒後に中断させたい場合も考えてみましょう。そのためには10秒後にdisposeします。
SignalProducerをstartする
let disposable = signalProducer.start(signalObserver)
// 10秒後にdisposeする
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
disposable.dispose()
}
今の実装だと、10秒後にObserverをdisposeしてもSignalProducerは50秒間Intの値を排出し続けてします。SignalProducerを使う時に重要なのは、中断されたらリソースを解放し、実行中のタスクを止めることです。修正しましょう。
let signalProducer: SignalProducer<Int, NoError> =
SignalProducer { (observer, lifetime) in
for i in 0..<10 {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0 * Double(i)) {
// guard文を追加
guard !lifetime.hasEnded else {
observer.sendInterrupted()
return
}
observer.send(value: i)
if i == 9 { // 9回目で完了します
observer.sendCompleted()
}
}
}
}
lifetimeのhasEnded
プロパティをチェックして、sendInterrupted()
を呼ぶことでinterruption型のEventを送っています。
Signal vs SignalProducer
SignalとSignalProducerとの違いを理解するためにテレビとオンデマンド配信サービスとの関連性から考えてみましょう。Signalは絶え間なく映像と音声を流すのでテレビ番組と言えます。任意の時点で、その番組の監視者(視聴者)は全て同じ場面を観ています。監視者たちはそのテレビ番組に対して作用をもたらさないし、番組を開始したり中止したりしません。一方で、SignalProducerは、Youtubeのようなオンデマンド配信サービスと言えます。監視者は映像と音声を受け取るのは同じですが、それぞれの監視者はそれぞれ別の場面をみています。監視者は番組を開始したり中止したりすることができます。
以上から、Signalはすでに進行中のイベントストリームを表すために使われるのが一般的です。たとえば通知やユーザーからの入力です。SignalProducerは、一方で、開始する必要があるオペレーションやタスクを表します。たとえば通信リクエストです。通信リクエストは、start
を呼び出すと新しい通信オペレーションを生成します。Signalだと監視されるまえにEventを送ることもあります。SignalProducerはstart
されない限りEventを送りません。
まとめ
この記事でSignalとSignalProducerの使い分けや関連性を理解してもらえたら嬉しいです。サンプルコードはこちらにあります。