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を含む事ができる
- 数値、custom typeのインスタンス、tapのようなrecognized gesturesなど
- これを概念化する最適な方法の一つが 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 }
- .next eventsはElementのインスタンスを含む
- .error eventsはSwift.Errorのインスタンスを含む
- .complete eventsはeventsを止めるだけでdataは含まない
Creating observables
以下のコードを記述してみる。
// 1 let one = 1 let two = 2 let three = 3 // 2 let observable: Observable<Int> = Observable<Int>.just(one)
- integerの定数を定義する
- 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
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
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ハンドラよりも良い方法があるが、それは後ほど
- 各要素として、n次のフィボナッチ数を算出している
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