- RxSwift: Reactive Programming with Swift の読書メモです。この本を読んでみようかと思う人の参考になれば。
- 演習が充実している本ですが、その部分については割愛します。
Chapter 1: Hello RxSwift
- RxSwift は...
- 観察可能なシーケンスと関数スタイルの演算子によって非同期でイベントベースのコードを構築し、スケジューラによってパラメータ化された実行を可能にするためのライブラリ。
- 本質的にはコードが新しいデータに反応して順番に独立して処理できるようにすることで非同期のプログラム開発を用意する。
Introduction to asynchronous programming
Cocoa and UIKit Asynchronous APIs
- 外部要因に依存するので非同期処理のコードがどのような順番で実行されるかはわからない。
- Apple はデリゲート、クロージャ、通知センターなど一貫性のない様々な方法で非同期処理を行うのでコードを書くのも理解するもの難しい。
- 同期コードではコレクションはイテレーション中変化しない。
var array = [1, 2, 3]
for number in array {
print(number)
array = [4, 5, 6]
}
print(array)
/* 結果
1
2
3
[4, 5, 6]
*/
- 非同期コードでは繰り返し処理中に他のコードがコレクションの内容を変更したり、ループカウンタを変更したりするかもしれない。
- 非同期コードの問題とは
- コードが実行される順番
- 変更可能な共有データ
Asynchronous programming glossary
1. State, and specifically, shared mutable state
- ステート定義するのは難しい...
- メモリやディスクのデータ、ユーザーの入力に対する反応によって作られたもの、クラウドサービスからデータを取得した後も残っている痕跡、そういうものすべてを合わせたものがステート。
2. Imperative programming
- プログラムのステートを変更する文を使うプログラミングパラダイム。いつ何を実行するのかを正確に指示する命令型のコードを使う。
- 複雑な非同期アプリを命令型のコードで書くのは人間には難しい。特に変更可能な共有データがある場合には。
3. Side effects
- 現在のスコープの外側で起こる全ての変更が副作用。現在のスコープから呼び出された関数により発生する変化も副作用。
- 副作用それ自体は悪いものではない。結局のところ何からの副作用を発生させるのが全てのプログラムの目的なのだから。
4. Declarative code
- 関数型のコードは一切の副作用を起こさない。現実の世界は完全ではないので RxSwift は命令型コードと関数型コードの間でバランスをとる。
- 宣言的なコードによって部分部分のコードの動作を定義し、RxSwift が関連するイベントの発生によってこれらの動作を実行し、その動作に必要な変更不能で独立したデータを提供する。
- これよよって変更不能なデータで処理を使って、決定的な順番で処理が行われるという単純な for 文と同じ想定を持つことができる。
5. Reactive systems
- リアクティブシステムとは次の特性を持つものである
- Responsive: UI は常に最新のステートを表す
- Resilient(回復力のある、立ち直りの早い): それぞれの動作は独立に定義され、柔軟なエラーからの復旧処理を提供する
- Elastic(制度、計画、時間などが柔軟な、融通がきく): コードは様々なワークロードを扱う
- Message driven: コードの再利用性、独立性が高まる
Foundation of RxSwift
- 非同期、スケーラブル、リアルタイムなアプリケーションのためサーバーとクライアントのフレームワークとして 2009 年にマイクロソフトから Reative Extensions for .NET(Rx) が提供された。
- 2012 年にオープンソースになり、様々なプラットフォーム、言語に移植された。
- RxSwift を再定義する:
- RxSwift は伝統的な命令型の Cocoa のコードと純粋な関数型のコードのちょうど良い中間点を見つける。決定的で組み合わせ可能な方法で入力を非同期に処理する変更不能なコードの定義を用いてイベントに対応できる。
- Rx のコードの 3 つのビルディングブロックがオブザーバブル、オペレータ、スケジューラ。
Observables
- Observable クラスは Rx の基礎。
- データの変更不能なスナップショットを 運ぶ ことができるイベントのシーケンスを非同期に生成する能力を持つ。
- 複数のオブザーバーが Observable に反応することができる。
- Observable は 3 種類のイベントを発行できる。
- next: 最新のデータを 運ぶ
- completed: 成功としてシーケンスを終了する。
- error: エラーでシーケンスを終了する。
- 有限オブザーバーブルシーケンス
- ダウンロードの最中に一部ずつ next イベントによりデータが届き最終的に completed によりダウンロードが完了するか error イベントによってエラーで終了するネットワークアクセス
- 無限オブザーバブルシーケンス
- Landscape, Portrait の変化
Operators
- Observable クラスのメソッドで非同期処理を行う抽象的で分離した要素。これを組み合わせて複雑なロジックを実装する。
- Observable からの発行されたイベントを入力としてとり、最後の値と決定するまで値を出力する。副作用を起こすこともできる。
Scheduler
- dispatch queue に相当するもの。
- 多数の定義済みスケジューラがあり、自分で定義する必要は殆どない。
- RxSwift はサブスクリプションとスケジューラの間のディスパッチャとして働く。
App architecture
- RxSwift はアプリケーションのアーキテクチャを変えない。MVC, MVVM でも MVP でも構わない。
- RxSwift のためにアプリケーションをスクラッチで書く必要はない。既存のもののリファクタリングに使ったり、新たに追加する部分で使うこともできる。
- RxSwift の MVVM の相性がいいのは ViewModel から ViewController へのインターフェースを Observable として公開でき、ViewController は直接それを UIKit のコントロールにバインドできるから。
RxCocoa
- RxSwift は共通の Rx の API のみを扱い、UIKit のクラスのことは何も知らない。UIKit に関する部分をカバーするのが RxCocoa
Installing RxSwift
- CocoaPods でも Carthage でも OK
- https://github.com/ReactiveX/RxSwift
- RxEample は見たほうがいい
Community
- http://community.rxswift.org
- https://github.com/RxSwiftCommunity
- Slack channel: http://rxswift-slack.herokuapp.com
Chapter 2: Observables
What is an observable?
- Observable は次の特徴を持つシーケンス。一定の期間にわたって非同期的にイベントを発行する。イベントは数値やカスタムタイプのインスタンスを持つことができる。
- マーブルダイアグラムで図示できる。
Lifecycle of an observable
- 要素を含む next イベントを発行する。これは次のいずれかが発行されるまで続く。
- error イベントを発行し終了する。
- completed イベントを発行し終了する。
- 終了したらイベントを発行しない。
Creating observables
- 要素を 1 つだけ含むシーケンスを生成する。
// class Observable
static func just(_ element: E) -> Observable<E>
- 可変長引数から複数の要素を含むシーケンスを生成する。
// class Observable
static func of(_ elements: E ..., scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable<E>
- 配列から複数の要素を含むシーケンスを生成する
// class Observable
static func from(_ array: [E], scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable<E>
Subscribing to observables
- Observable をサブスクライブするには subscribe() を使う。
// protocol ObservableType
func subscribe(_ on: @escaping (RxSwift.Event<Self.E>) -> Swift.Void) -> Disposable
- observable はオブザーバーにサブスクライブされるまでイベントを発行しない。
- サブスクライブする時に .next, .error, .completed それぞれのためのイベントハンドラを指定できる。
- Event は element プロパティを持つ。オプショナルで .next の場合だけ有効な値を持つ。
- .next, .error, .completed をそれぞれ別のクロージャで受け取る subscribe もある。
// protocol ObservableType
func subscribe(onNext: ((Self.E) -> Swift.Void)? = default, onError: ((Error) -> Swift.Void)? = default, onCompleted: (() -> Swift.Void)? = default, onDisposed: (() -> Swift.Void)? = default) -> Disposable
- 要素 0 個で .completed のみを発行する Observable を作る。
// class Observable
static func empty() -> Observable<E>
- なんのイベントも発行しない Observable を作る。無限の期間を表すのに使う。
// class Observable
static func never() -> Observable<E>
- 整数で範囲を指定して Observable を作る。
// class Observable where Element : SignedInteger
static func range(start: E, count: E, scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable<E>
Disposing and terminating
- observer が Observable を終了させる必要がある。
- サブスクリプションをキャンセルすることで終了させることができる。
- subscribe の戻り値である Disposable のメソッドである dispose() を明示的に呼ぶことでキャンセルできる。
// protocol Disposable
func dispose()
- 通常は dispose() ではなく、DisposeBag を使う。各 Observable を DisposeBag に登録しておくと DisposeBag が解放される時に各 Observable の dispose() が自動的に呼び出される。
- DisposeBag への登録は addDisposeBag() を使う。
// protocol Disposable
func addDisposableTo(_ bag: DisposeBag)
- いずれかの方法で dispose しないとメモリリークする。
- create() は Observable を生成する関数の一つで、Observer を引数に取るクロージャによって発行するイベントを決定する。
// class Observable
static func create(_ subscribe: @escaping (AnyObserver<E>) -> Disposable) -> Observable<E>
Creating observable factories
- observer がサブスクライブするたびに新しい Observable を生成するファクトリを作ることができる。
// class Observable
static func deferred(_ observableFactory: @escaping () throws -> Observable<E>) -> Observable<E>
- deferred で生成された Observable がサブスクライブされる度に observableFactory が呼ばれ、observableFactory が生成した Observable が新しいサブスクリプションで使われる。
- ファクトリ Observable は通常の Observable と型は変わらない。
Challenges
Challenge 1: Perform side effects
- do オペレータを使うと任意のイベントに対して任意の処理を行わせることができる。
// protocol ObservableType
func `do`(onNext: ((Self.E) throws -> Swift.Void)? = default,
onError: ((Error) throws -> Swift.Void)? = default,
onCompleted: (() throws -> Swift.Void)? = default,
onSubscribe: (() -> ())? = default,
onSubscribed: (() -> ())? = default,
onDispose: (() -> ())? = default) -> RxSwift.Observable<Self.E>
Challenge 2: Print debug info
- debug オペレータを使うと、通過する全てのイベントをダンプできる。
// protocol ObservableType
func debug(_ identifier: String? = default,
trimOutput: Bool = default,
file: String = #file,
line: UInt = #line,
function: String = #function) -> RxSwift.Observable<Self.E>
Chapter 3: Subjects
What are subjects?
- Subject は observable としても observer としても機能する。
- observer としてイベントを受け取る度、observable としてイベントを発行する。
- RxSwift には 4 種類の subject がある。
- PublishSubject: 空で始まり、新しい要素をサブスクライバに発行する。
- BehaviorSubject: 初期値を持ち、新しいサブスクライバには最新の要素を再送する。
- ReplySubject: 初期化時に指定されたサイズのバッファだけの要素を保持し、新しいサブスクライバにその要素を再送する。
- Variable: BehaviorSubject のラッパーで現在の値をステートとして保持する。新しいサブスクライバには最新の要素を送信する。
- observer として扱うには asObservable() を使う。
// protocol ObservableConvertible
func asObservable() -> Observable<E>
Working with PublishSubjects
- すでに終了した subject をサブスクライブすると error もしくは completed イベントが送られてくる。
Working with BehaviorSubjects
- 最後に発行したイベントを保持しており、新しいサブスクライバが接続して来た時にそのイベントを発行する。
- 初期値を与える必要がある。
- View に表示するデータの提供元として使う場合に便利。どこから実際のデータを取得し終わるまでの間表示する仮の値として初期値を使える。
Working with ReplySubjects
- 指定した数だけ過去のイベントを保持する。
- そのぶんだけメモリを喰うので注意。
- 終了した ReplySubjects に新しいサブスクライバが接続してきた場合、バッファに保持されているイベントが再送されたから終了イベントが送られる。
Working with Variables
- BehaviorSubject のラッパー
- value プロパティで現在の値を読み書きできる。
- observable として使うには asObservable() を使う。
// class Variable
func asObservable() -> Observable<E>
- value 経由で error や completed を発行することはできない。
Chapter 4: Observables and Subjects in Practice
- ViewController の Subject を public にしてしまうと他のクラスからイベントを発行できてしまうので、Subject は private にして asObservable() の戻り値を取得する computed propety を public にする。
- RxSwift はりソースリークのデバッグ用に使用中のリソースを数える機能を持っている。
- デフォルトではオフになっていて RxSwift 自体のコンパイルオプションで有効にする必要がある。
- Podfile で有効にする場合:
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'RxSwift'
target.build_configurations.each do |config|
if config.name == 'Debug'
config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D', 'TRACE_RESOURCES']
end
end
end
end
end
- ViewController が持つ Subject から作られた Observable が ViewController が消えるタイミングで dispose されるようにするには viewWillDisapper でその Subject の onCompleted() を呼ぶ。