みなさん、こんにちは。freddiです。
本記事は、iOS Advent Calender 2018の23日目の記事として、UIViewController
のライフサイクルとRxSwiftのお話をさせていただきます。短い記事ですが、よろしくおねがいします。
本記事では、都合上RxSwiftに関しての入門の説明はほぼ無いので、ご注意ください。1
危険なRxSwiftの使い方
まず先に、UIViewController
のライフサイクルとRxSwiftが関係するアンチパターンの例を紹介します。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
someThingObservableValue.flatMap { ... } // someThingObservableValueはObservableな変数
.subscribe( ... )
.disposed(by: disposeBag)
}
一見、ちゃんと動くように見えるコードですね、Observable
な変数をsubcribe
するだけのコードです。
しかし、ここで気にしてほしいのが、subcribe
している関数です。viewWillAppear
は、何回も呼ばれる可能性があるライフサイクルの関数です。 2
このコードの例だと、
- viewWillAppearのような複数回呼ばれる可能性のある関数でsubscribeを行おうとしている3
- 複数回同じようなストリームをsubcribeをしてしまうことになる
- 呼ばれた分だけsubscribe処理が働くことになり、subscribeで重い処理などを行っていると大変なことになる
という重大な問題が出てきます。これは、Multiple Subscribing
のような名前で呼ばれていることがあります。subscribe
で重い処理をするしない関係なく、Multiple Subscribing
は避けるべきです。
対処法
その1、無難にviewDidLoad
に入れる
override func viewDidLoad() {
super.viewDidLoad()
someThingObservableValue.flatMap { ... }
.subscribe( ... )
.disposed(by: disposeBag)
}
viewDidLoad
は一回しか呼ばれないので、subscribe
するものはviewDidLoad
に入れたほうが良いです。
ただ、これもこれで、viewDidLoad
が非常に大きくなる問題もあるのでご注意ください。4
その2、Completeを即流す
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Observable.just(())
.withLatestFrom(someThingObservableValue)
.flatMap { ... }
.subscribe( ... )
.disposed(by: disposeBag)
}
多分ですが、viewDidLoad
に入れることがそもそも解決策ではないこともあると思います(viewWillAppear
でどうしてもやりたいこととか)。
そんな時、Observableから使える**.just(:)
などを使えば、引数で与えられた値をストリームに流したあとにCompleted
を流します。**5
これによりsubscribe
後に即ストリームを開放するという事もでき、subscribe
する関数を複数回呼び出してもMultiple Subscribing
が起こる心配はありません。
ただ、目的の値を流すとなるとwithLatestFrom
のような関数を利用しなければならず、場合によってはflatMapのネストが無駄に深くなるなどの厄介な点も出てきます。
その3、BehaviorRelay
がいい説
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if someBehaviorRelayValue.value ... {
...
}
}
そもそもBehaviorRelay
を使えば、asObservable()
せず(ストリームも作らず)に値を見ることができます。何回もsubscribe
が呼ばれるような場合で、かつBehaviorRelay
に置換できそうなら置換したほうがいいです。BehaviorRelay
もsubscribe
できますし・・・。
その4、そもそもRxがいらない説2
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
... // Rxを使わない方法でゴニャゴニャ
}
これ一番上に持って行きたかったですが、怒られそうなのであえてここに書きました。Rx使わないのも、それでかなり良い選択です。6
RxSwift
がかなり根付いているプロジェクトだと、この検討は多分難しいですが・・・。Rx乱用ダメ絶対。
終わりに
私がRx初心者の頃にやらかしたことを記事にしました。ざっくりとした紹介でしたが、みなさんのご参考になりましたでしょうか?
もしTipsや修正すべき点があれば、気軽にリクエストとコメントをよろしくおねがいします。
では今回は失礼します。皆様ありがとうございました。
次の日はFumiya Sakaiさんの記事です!皆さんもお楽しみに!
-
これは個人的な意見ですが、RxSwiftを触ろうとする前に、ReactiveXについて調べるほうがいいかもしれません。理由はいくつかありますが、私はReactiveXというものを知ることで、他の言語のRxのコードがRxSwiftでも参考になることを知りました。 ↩
-
自分で作った関数を呼び出すとき、その中で
subscribe
をすれば、そこでMultiple Subscribe
の危険性も出てきます。ですが、シングルトンではないかつライフサイクルの短い(一つのスコープのみに存在する)オブジェクトではこの限りでないです。 ↩ -
この問題は"Fat viewDidLoad"という名前で呼ばれているのをたまに見ます。 ↩
-
他にも、
from
も値を流したあとにCompleted
を流します。この記事ではjust
やfrom
に似ている関数について調べることができます。 ↩ -
RxSwiftは必要に応じて使うようにして、必要無いならばなるべく使わないという方針が後々圧倒的に楽です。 ↩