2015年11月から実業務でRxSwiftを使いはじめて、めでたく1年経過したので
その振り返りをしつつ、実際の業務ではこんな感じで使ってます。
というの書いていこうと思います。
ポエムですよ! ポエム!
(実は、よくよく見たら書こうと思ってた内容と似た記事があったので、急遽変更したのは内緒)
導入した頃の話
導入を決定した理由
実はえいや!!の勢いで使い始めました。
- 1ページ表示するのにAPIを数本叩く必要がありコールバック地獄、スレッドの切り替え地獄が見えた
- バインディングが便利そう
- Android側と使う技術が揃えられる、技術共有などしやすい
- とにかくやってみたかった
短期決戦な案件ではじめて導入するような場合は、避けたほうが良いです。
ReactiveCocoaとRxSwiftどっちにするか
これもどっちにするかは悩みましたが、下記の理由でRxSwiftを採用しました。
- RxをAndroidでも使うのでそろえたい
- Swiftメインのもので使ってみたかった (今はもうReactiveSwiftがあるのでこの理由は使えないですね)
実際やってみると、RxSwiftからはじめて良かったかなーと思っています。
例えばこちらにあるように、RxJavaなど言語を超えて参考になる記事がありましたし。
(とはいえ、途中で詰まるとやっぱりReactiveCocoaの方が良いのでは……?と逃避することがありました。でもそこはぐっとこらえました)
ただ、ある程度慣れてきた現状だとエラーの型を持てたり、HotとColdがそれぞれ型が分かれてたりと、ReactiveCocoaの方が使いやすそうだなーと思うことがしばしばあります。
ObservableとかSubjectとか全然ワカラナイ問題
もう、Rx全然分からない。RxSwiftにあるサンプルの意味が分からない。何が分からないか分からない。
という苦しみで悩み続けました。
とにかく、簡単な事例ならともかく、この場合はどうやるんだ、あの場合はどうするんだと悩みながら
RxSwiftのサンプルコードや記事に投稿された内容を見て、書いては動かしてを繰り返していました。
その時に参考になったのは、↓です。
- [タコさんブログ]
(http://tiny-wing.hatenablog.com/entry/2016/01/02/161322) - [Reactive Extensions Basic]
(http://hamasyou.com/blog/2015/12/15/reactive-extensions-basic/)
後から出てきたこの記事も最高です。神ってますよ!
- 今日こそ理解するHot変換
- [マルチスレッドRxSwift @ 社内RxSwift勉強会]
(http://www.slideshare.net/yukitakahashi3139241/rxswift-rxswift-64155110) - [今日こそ理解するHot / Cold @社内RxSwift勉強会] (http://www.slideshare.net/yukitakahashi3139241/hot-cold)
- [RxSwift コードリーディングの勘所@社内]
(http://www.slideshare.net/yukitakahashi3139241/rxswift-rxswift) - [RxSwiftの機能カタログ]
(http://qiita.com/k5n/items/e80ab6bff4bbb170122d) - RxSwiftをプロダクトに導入してみた話
とにかく、書いて書いて動かして動かしてを繰り返すしか無いと思います。
どうしたらキレイな構成・書き方になるのか問題
書き方が分かってくると、今度はアーキテクチャどうしようという悩むことが多くなってきました。
これは人それぞれの理解度や好みの話になるかもしれないですが、ModelやViewModelの領域が曖昧になってきたりして、どうしたものかなーと悩みました。
結局は、まだMVC,MVP,MVVMで消耗してるの? iOS Clean Architectureについて
を参考にしてRxSwift+CleanArchitectureで構成をキレイにしよう
みたいな感じで作りました。
今でも、もっとスマートな書き方がないか、オペレータを上手く使えないかずっと悩んでいます。
最近、色々なアーキテクチャやRxSwiftの記事やコードも増えたので嬉しい限りです。
正解はない哲学に近い話な気がしているので、色々インプットして精査する予定です。
実際にどんな風に使っているのか
ここまでは導入してみた感想で、実際にはこんな使い方をしていますという話をしようかと。
Subscribeはどこで行う?
構成は上記のような感じで、小さく描いていますが基本PresenterでSubscribeしています。
Clean Architectureで設計してRxJSを使った話を参考にしています。
(Viewの部分でDriveも利用しているので、あくまで基本的です)
Observable型の計算式を積み重ねていって、PresenterでガツッとSubscribeするこの方式は、すっきりしていて個人的には好きです。
あと、UseCase -> Presenterは、Observable型をreturnするのではなく、SubscribeしてPresenterにバインディングするのが良いのでは?とか悩んだりもしました。
Observable型を返す方が、他の人が触るときも分かりやすいなという所で落ち着きました。
(ちなみにその中で、Observableで公開するという言葉を目に見ましたが
protocolに、returnにして返すメソッドを記載していることを指しているのか、Observable型の変数を公開してそれをバインドすることなのか、どういう意図の言葉なのだろうと悩んでます)
データバインディング&TableViewの取扱について
題名にゆるふわと書いている理由がここにあります。
TableViewとかCollectionViewの表示には、rx.itemsWithCellIdentifierなどのRxSwiftが提供しているものを利用していません。
標準のものを利用しています。
Presenterでsubscribe
-> 流れてきたModelをPresenterで保持してViewControllerにはsubscirebeが成功したことを通知
-> 通知先のViewControllerでreloadData()
-> cellForRowAtがPresenterにあるModelを参照して表示
みたいな感じです。
イメージ: PresenterとViewController (Swift2の書き方ですみません。。)
理想はデータをdelegateやdatasourceの所でバインディング出来るのが良いなーと思うのですが
こちらの方の記事を見てもらうと分かるように
- rx.itemsWithCellIdentifierは複数section対応していない(多分今も)
- RxTableViewDataSourceTypeを用いてラップするにしても、色々なtableviewが来た時に別々のものを作る必要がある
- RxCommunityのRxDataSourcesを使うと便利だけれど、tableview使うだけなのに、OSSは面倒
などなど柔軟な対応を考えると、無理に使わず標準のに任せようということで折り合いをつけました。
社内SDKとかもrx_delegateで包みたくなりましたが、キリがなさそうなのでやめました。
また、描画の際にdriveやbindを使うシーンもありますが、標準に任せる場合、そこに渡ってくる段階でもう表示するデータは作られているはずなので、使わない選択肢もあります。
ただ、万が一メインスレッドではない可能性とかnilが入る可能性とかを防ぐという意味で、自分はDriverを利用しています。超便利です。
Carthageのbuildに時間かかるRxSwift
CI環境でビルドする際に、CarthageのRxSwiftやら他のOSSやらのビルド時間がツラミだったりします。
そこで、carthage_cache、実際はfastlaneのfastlane-plugin-carthage_cacheを用いてビルドしたフレームワークをキャッシュさせて時間を短縮します。
これでCI環境のビルドが15分ぐらい短縮されてウルトラハッピーです。
// イメージです
if carthage_cache_exist(bucket: 'bucket-name')
carthage_cache_install(bucket: 'bucket-name')
else
carthage(
platform: 'iOS',
use_binaries: false,
)
if publish_carthage_cache
carthage_cache_publish(bucket: 'bucket-name')
end
end
debug
ストリームの確認は、subscribeやmap内でdumpしてもいいですがdebugを結構使っていました。
// debugの中に文字列を入れられるので
Observable.just(3).debug("debug_1").subscribe(onNext: { c in
print(c)
}).addDisposableTo(disposeBag)
// こんな感じ
2016-12-17 14:57:30.408: debug_1 -> subscribed
2016-12-17 14:57:30.410: debug_1 -> Event next(3)
3
2016-12-17 14:57:30.410: debug_1 -> Event completed
2016-12-17 14:57:30.410: debug_1 -> isDisposed
色々仕込むとどういう順番で読み込みがしているか分かりやすいです。
メモリリークとかあるのか
キャプチャリストを用いていれば、そうそう無いです(と思ってます)。
それでも気になるのであれば、Debugging memory leaksを使うとよいと思います。
と言っても、あー増えてるー、あー減ってるーぐらいの感じで、ざっくりレベルの確認ですが。。
こういう風に使ってますよーとか教えていただけると助かります
// debugでビルドする
carthage build --configuration Debug --platform iOS
// 見たい所に置いておく
print("Resource count \(RxSwift.Resources.total)")
RxSwift使ってるけれどクラッシュはどうなの?
クラッシュ率は減りました。 現状は0.3%〜0.4%ぐらいです。
その0.4%も既存のObjective-C部分が殆どです。
RxSwift部分だと、たまにキャプチャリストのunownedの部分でのクラッシュが出たりします。
コレもどうにかしたいなあと考えています。
その他
チームに新メンバーが来た時の話
新しく来た人は、リアクティブプログラミングが初経験が多いため苦しみます。
観測者に観測されてはじめてObservable型の中身が評価するなどのお作法もそこそこに
(こう書くと、まるでどこかのマッドサイエンティストみたいな、中二っぽい感じがしますよね)
とにかくコードを触らせることが大事だと思います。
資料読んでても分かった気になるだけで、いざ触ろうと手が止まると思っています。
言語もそうですが、使わないと身につかないです。
RxSwift3対応について
現在、Swift3対応をしています。
ただでさえ、Swift3対応が辛いのに、RxSwiftもかなりの変更をしているので辛さが倍です。
(気持ち的に、subscribeNext/driveNextがなくなったのでその対応がクソめんどくさいです)
もしRxSwiftの導入を考えているのでしたら、Swift3から始めるのが絶対に幸せです。
最後に
最初は分からないものの、触り続ければ普通に使えるようになるので継続は力なりという感じはします。
RxSwiftを通じて、色々な勉強会に行ったり、資料を読んだり、コードを読んだり、部内でゆるふわ勉強会を開催したりと色々経験して楽しかったです。
何にせよ、1回はリアクティブプログラミングを業務とかで経験しておくと、世界が広がる気がします。
(ずっとLAMPのサーバ側の開発だったので、ここ1年は新鮮でした)
最後の最後に。
個人的にRxSwiftのオペレータの中では、zipとwithLatestFromが大好きです。
これのお陰で、複数API叩いてもスッキリなコードが記述できます。