iOS
ReactiveCocoa
Swift
ReactiveSwift

ReactiveSwiftを克服する: SignalProducer (Part 4)

この記事について

この記事は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
    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が受け取る値や順番も異なります。
start.png
訳注: 画像は元記事からの引用です

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
    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の使い分けや関連性を理解してもらえたら嬉しいです。サンプルコードはこちらにあります。

シリーズ内リンク集

  1. ReactiveSwiftを克服する: 導入 (Part 1)
  2. ReactiveSwiftを克服する: 基本要素 (Part 2)
  3. ReactiveSwiftを克服する: SignalとObserver (Part 3)
  4. この記事です
  5. お待ちください
  6. お待ちください