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

## この記事について

この記事は[Conquering ReactiveSwift: SignalProducer (Part 4)](https://medium.com/fueled-engineering/conquering-reactiveswift-signalproducer-part-4-d3f50dcf222f)の翻訳です。

以下本文です。

# 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の中にカプセル化します。

```swift
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の準備ができたので、開始して監視しましょう。

```swift
// 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](https://qiita-image-store.s3.amazonaws.com/0/130238/7c2105c1-97b0-d939-3c7e-10ca468263e0.png)
訳注: 画像は元記事からの引用です

SignalProducerを10秒後に中断させたい場合も考えてみましょう。そのためには10秒後にdisposeします。

```swift
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を使う時に重要なのは、中断されたらリソースを解放し、実行中のタスクを止めることです。修正しましょう。

```swift
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の使い分けや関連性を理解してもらえたら嬉しいです。サンプルコードは[こちら](https://github.com/hsusmita/conquering-reactive-swift/blob/master/ConqueringReactiveSwift/Paygrounds/Conquering-Reactive-Swift.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift)にあります。

### シリーズ内リンク集

1. [ReactiveSwiftを克服する: 導入 (Part 1)](https://qiita.com/taka1068/items/5d916f1e68cbcdce0121)
2. [ReactiveSwiftを克服する: 基本要素 (Part 2)](https://qiita.com/taka1068/items/9e6ccce4297832817444)
3. [ReactiveSwiftを克服する: SignalとObserver (Part 3)](https://qiita.com/taka1068/items/bdd0329202cea0e0c4b6)
4. この記事です
5. [ReactiveSwiftを克服する: Property (Part 5)](https://qiita.com/taka1068/items/fcde158848dbe3169477)
6. [ReactiveSwiftを克服する: Action (Part 6)](https://qiita.com/taka1068/items/9ea40476076623c2a910)