RxSwift - Reactive Programming with Swift

RxSwift - Reactive Programming with Swift

Ray Wenderlich Store | RxSwift がかなりわかりやすくて、買ってよかったと(まだ読み始めたばかりのくせに)感じている。

読み進めながら内容をまとめていこうと思う。

What is an Observable

  • observable はRxの主要部
  • observable は observable sequence, sequence などとも呼ばれる
  • 他の Reactive Programmingの世界ではstreamと呼ばれることもあるが、RxSwiftの世界では "Everything is a Sequence"
  • ObservableもまたSequence
    • 非同期などの特別な能力を備えた
  • observablesは一定周期に渡ってeventsを排出する
    • このプロセスをemittingと呼ぶ
  • eventsはvaluesを含む事ができる
  • これを概念化する最適な方法の一つが marble diagram

Lifecycle of an observable

  • 以下のいずれかが起きるまでobservableはnext eventとして要素を発行する
    • error eventを発行して終了する
    • completed eventを発行して終了する
  • 一度observableが終了すると、それ以上eventを発行することはできない
  • RxSwiftではこれらのeventsはenumとして表現されている
/// Represents a sequence event.
///
/// Sequence grammar: 
/// **next\* (error | completed)**
public enum Event<Element> {
    /// Next element is produced.
    case next(Element)

    /// Sequence terminated with an error.
    case error(Swift.Error)

    /// Sequence completed successfully.
    case completed
}

Creating observables

以下のコードを記述してみる。

// 1
let one = 1
let two = 2
let three = 3

// 2
let observable: Observable<Int> = Observable<Int>.just(one)
  1. integerの定数を定義する
  2. oneを使ったjustメソッドを用いてIntのobservable sequenceを作成
    • justは名の通り、ただ一つの要素を含むobservable sequenceを作成する
    • justはObservableのtype methodだが、Rxではメソッドはoperatorと呼ばれる

続いて以下を追記してみる

let observable2 = Observable.of(one, two, three)

今回は明示的にtypeを指定していない。複数のintegerを含むので、Observable<[Int]>と思うかもしれないが、これはObservable。 operatorは可変長引数をとることができるからだ。

arrayを渡すこともできる。

let observable3 = Observable.of([one, two, three])

今度はObservable<[Int]>になる。ただ、これはarrayという単一の要素を用いているに過ぎず、array内の各要素を用いるわけではない。

arrayの各要素からObservableを作成するにはfrom operatorを使う。

let observable4 = Observable.from([one, two, three])

arrayを渡しているが、Observableとなる。from operatorはarrayのみ引数に取る事ができる。

Subscribing to observables

  • NotificationCenterは馴染みがあるでしょう
    • RxSwiftのObservableとは異なり、これはobserverに対して通知をブロードキャストする
  • observableもよく似ている
    • observableをobserveする事をsubscribeと呼ぶ
  • NotificationCenterではシングルトンインスタンス.defaultを利用するのに対し、Rxでは各Observableを使用する点が異なる
  • observableは、subscriberが現れるまでイベントを発行しないという点も重要な違い
  • observableはsequenceである
    • observvableをsubscribeすることは、Iteratorのnext()をコールすることに酷似している
  • observableをsubscribeする時には各イベント(.next, .error, .completed)にハンドラをつける事ができるのでさらに合理的
let one = 1
let two = 2
let three = 3

let observable = Observable.of(one, two, three)

observable.subscribe { event in
    print(event)
}
  • subscribeの例
    • Intを要素にもつobservableを作成し、subscribeしている
    • observableは各要素に対して.nextイベントを発行し、最後に.completedイベントを発行して終了する
  • subscribeする際には、イベントそのものよりも、イベントが発行する要素に対して関心があることのほうが多い
    • 以下のようにして要素をとりだす事が可能
observable.subscribe { event in
    if let element = event.element {
        print(element)
    }
}
  • RxSwiftでは上記パターンの代わりに短縮形が用いられる
observable.subscribe(onNext: { element in
    print(element)
})
  • 単数または複数の要素を持つobservableの作成方法は上記の通り
  • 要素を一つも持たないobservableはemptyを使って作成する
let observable = Observable<Void>.empty()

observable.subscribe(
    onNext: { element in
        print(element)
},
    onCompleted: {
        print("completed")
})
  • 上記はempty使用例
    • nextイベントは一度も発行されず、completedイベントのみが発行され、終了する
    • 直ちに終了したり、あえて一つも要素を持たないobservableを作成する場合に使用する
  • 何のイベントも発行せず、終了することもないobservableはneverを用いて作成する
let observable = Observable<Any>.never()

observable.subscribe(
    onNext: { element in
        print(element)
},
    onCompleted: {
        print("completed")
})
  • 上記はnever使用例
    • 実行しても、何も出力されない。completedも出力されない
  • rangeを指定してobservableを作成することもできる
let observable = Observable<Int>.range(start: 1, count: 10)

observable.subscribe(onNext: { i in
    let n = Double(i)
    let fibonacci = Int(((pow(1.61803, n) - pow(0.61803, n)) / 2.23606).rounded())
    print(fibonacci)
})
  • rangeオペレータを使用して、startとcountを指定
    • 各要素として、n次のフィボナッチ数を算出している
      • この手の変換はonNextハンドラよりも良い方法があるが、それは後ほど

Disposing and terminating

  • observableは、subscribeされるまでは何もしない
    • subscribeによってイベントの発行を開始し、.errorか.completedを発行して終了する
  • subscribeを中止して、observableを強制的に終了させることもできる
    • subscribeを明示的に中止するにはdisposeを呼ぶ
let observable = Observable.of("A", "B", "C")

let subscription = observable.subscribe { event in
    print(event)
}

subscription.dispose()
  • 全てのsubscriptionを個別に管理するのは大変
    • RxSwiftではDisposeBagが用意されている
    • disposed(by:)を用いて追加していく
let disposeBag = DisposeBag()

Observable.of("A", "B", "C")
    .subscribe {
        print($0)
}.disposed(by: disposeBag)
  • これが最もよく使われるパターン
    • observableを作成してsubscribeし、dispose bagに追加する
  • もしsubscribeをdispose bagに追加するのを忘れればメモリリークを起こす
  • subscribersに対して発行するイベントを全て指定してobservableを作成するにはcreateオペレータを使用する
let disposeBag = DisposeBag()

Observable<String>.create { observer in
    observer.onNext("1")

    observer.onCompleted()

    observer.onNext("?")

    return Disposables.create()
    }
    .subscribe(
        onNext: { print($0) },
        onError: { print($0) },
        onCompleted: { print("Completed") },
        onDisposed: { print("Disposed") }
    )
    .disposed(by: disposeBag)
  • observable作成時に.next -> .completed -> .nextとイベントを発行している
    • .completed発行後は.disposed発行ののち直ちにobservableは終了する
    • 2つ目の.nextは発行されない
  • .errorを発行した場合も、.disposedを発行したのに直ちにobservableは終了する
    • 以降のイベントは発行されない
  • .completedも.errorも発行せず、subscriptionをdisposeBagに追加しなければメモリリークを起こす
Observable<String>.create { observer in
    observer.onNext("1")

    observer.onNext("?")

    return Disposables.create()
    }
    .subscribe(
        onNext: { print($0) },
        onError: { print($0) },
        onCompleted: { print("Completed") },
        onDisposed: { print("Disposed") }
    )
  • observableは終了することはなく、disposableが破棄されることもない

Creating observable factories

  • observableを作成する代わりに、各subscriberに対してobservableを作成するobservable factoryを作成することもできる
let disposeBag = DisposeBag()

var flip = false

let factory: Observable<Int> = Observable.deferred {
    flip = !flip

    if flip {
        return Observable.of(1, 2, 3)
    } else {
        return Observable.of(4, 5, 6)
    }
}
  • deferredオペレータを使用して、Intのobservable factoryを作成
    • subscribeされるたびに、異なるobservableを作成して返す

Using Traits

  • traitsは、observableの小機能版となる
    • traitsを使ってできることは全てobservableでできるので、traitsは使わないという選択も可能
    • コードの意図を、読み手に明示的に伝えるために利用する。
  • RxSwiftのtraitsには3種類ある
    • Single, Maybe, Completable
  • Single
    • .success, errorイベントの発行
    • .nextと.completedイベントの役割を果たす
    • 一回きりの処理で、成功して値を産出するか、または失敗するといったケース(データのダウンロードや、ファイル読み込みなど)で利用できる
  • Completable
    • .completed, .errorイベントの発行
    • 正常終了か失敗かのみ気にするケース(ファイル書き込みなど)で利用できる
  • Maybe
    • Single, Completableを合わせたもの
    • .success, .completed, .errorイベントを発行
    • 成功か失敗かを必ず返し、値を返すこともある、といったケースで利用できる
let disposeBag = DisposeBag()

enum FileReadError: Error {
    case fileNotFound, unreadable, encodingFailed
}

func loadText(from name: String) -> Single<String> {
    return Single.create { single in
        let disposable = Disposables.create()

        guard let path = Bundle.main.path(forResource: name, ofType: "txt") else {
            single(.error(FileReadError.fileNotFound))
            return disposable
        }

        guard let data = FileManager.default.contents(atPath: path) else {
            single(.error(FileReadError.unreadable))
            return disposable
        }

        guard let contents = String(data: data, encoding: .utf8) else {
            single(.error(FileReadError.encodingFailed))
            return disposable
        }

        single(.success(contents))
        return disposable
    }
}

loadText(from: "Copyright")
    .subscribe {
        switch $0 {
        case .success(let string):
            print(string)
        case .error(let error):
            print(error)
        }
    }
    .disposed(by: disposeBag)
  • 上記はファイル読み込みの例
  • doオペレータを使って、observableが発行するイベントそのものには影響を与えることなく、別の処理を挟み込む事が可能
  • debugオペレータを使うことで、printデバッグの代用が可能
    • neverはなにもイベントを発行しないが、動作していることを確認するにはどのようにすればいいか
let disposeBag = DisposeBag()
let observable = Observable<Any>.never()

observable
    .do(
        onNext: { element in
            print(element)
    },
        onError: { error in
            print(error)
    },
        onCompleted: {
            print("completed")
    },
        onSubscribe: {
            print("subscribe")
    },
        onSubscribed: {
            print("subscribed")
    },
        onDispose: {
            print("dispose")
    })
    .subscribe(
        onNext: { element in
            print(element)
    },
        onCompleted: {
            print("completed")
    })
    .disposed(by: disposeBag)
  • doオペレータを使用することで、observableが発行するイベントそのものには影響を与えずprint出力を加える事ができた
let disposeBag = DisposeBag()
let observable = Observable<Any>.never()

observable
    .debug("hogehoge", trimOutput: false)
    .subscribe(
        onNext: { element in
            print(element)
    },
        onCompleted: {
            print("completed")
    })
    .disposed(by: disposeBag)
  • debugオペレータを使用して、発行された各イベントについて自分でprint文を書かずともデバッグ情報を出力可能

その他

配布されているサンプルプロジェクトについて、Playgroundが実行エラーになる問題がある。 ワークアラウンドは以下の通り Playground does not execute on first chapter - Books / RxSwift - raywenderlich.com Forums

diff --git a/Podfile.org b/Podfile
index 8e1bd29..b1d239a 100644
--- a/Podfile.org
+++ b/Podfile
@@ -8,9 +8,17 @@ target 'RxSwiftPlayground' do
 end
 
 post_install do |installer|
+
+  installer.pods_project.build_configurations.each do |config|
+    config.build_settings.delete('CODE_SIGNING_ALLOWED')
+    config.build_settings.delete('CODE_SIGNING_REQUIRED')
+  end
+
   installer.pods_project.targets.each do |target|
     target.build_configurations.each do |config|
       config.build_settings['CONFIGURATION_BUILD_DIR'] = '$PODS_CONFIGURATION_BUILD_DIR'
     end
   end
 end