9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2018-11-05

この記事について

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

シリーズ内リンク集

  1. ReactiveSwiftを克服する: 導入 (Part 1)
  2. ReactiveSwiftを克服する: 基本要素 (Part 2)
  3. ReactiveSwiftを克服する: SignalとObserver (Part 3)
  4. この記事です
  5. ReactiveSwiftを克服する: Property (Part 5)
  6. ReactiveSwiftを克服する: Action (Part 6)
9
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?