- バックエンド / リーダー候補
- PdM
- Webエンジニア(シニア)
- Other occupations (17)
- Development
- Business
2018/6/28に主催した「ReactorKit Meetup Japan」についてご紹介します。このミートアップは、FluxにインスパイアされたフレームワークであるReactorKitについて現場での知見を共有するために開催されました。当日は作者であるSuyeol Jeon 氏 (@devxoul)をお招きし基調講演をしていただきました。さらに、Picos 菅原 祐 氏、Mercari 多賀谷 洋一 氏、Wantedly 永島 次朗 氏、RCUBE 八谷 賢 氏、StyleShare Wooseong Kim 氏の計5名の方に導入事例など知見を共有して頂きました。本記事では、開催の経緯や各発表者の内容をまとめてご紹介いたします。また、この記事はCodezineで公開した記事をベースにしています。
はじめに
Visitの開発チームでiOSエンジニアをしている 永田 健人(@ngtknt)です。今回は2018/6/28に開催された「ReactorKit Meetup Japan」についてご紹介します。
まず、ReactorKitについて簡単にご説明します。ReactorKitは、iOSでリアクティブかつ一方向なデータフローを実現するフレームワークです。リアクティブプログラミングとFluxの組み合わせにより、よりシンプルでわかりやすい状態管理を実現しています。詳しくは作者 Suyeol Jeon 氏のキーノートにてご紹介したいと思います。
さて、本イベントの開催経緯も簡単にご説明したいと思います。
ご存知の通りクライアントサイドでのユーザ体験はよりリッチなものになってきています。ユーザ体験がリッチになればより複雑な状態管理が求められます。iOS開発においてもこの複雑さに対応するためのアーキテクチャやその実装について長く議論されてきました。
一方で、ウェブフロントエンドでもFluxやReduxの登場で、ページ内での状態管理やアプリケーション全体を通してのグローバルな状態管理をどうするのかなど、非常に複雑な状態管理をわかりやすく記述するための設計が確立しつつあります。Fluxでは状態変更に必要な責務を適切に分割し、わかりやすく宣言できるようにしたこと、流れが一方向であることが、読みやすさに大きく貢献しているように思います。さらに、Reduxではページ間での状態共有や変更の依存関係をうまく表現するための仕組みを実現しています。
これらの流れはiOSでも起きており、FluxではReactorKit、ReduxではReSwiftなどで実現されています。さらにReactorKitはプロダクトで採用される機会が増えてきており、現場での導入事例や知見を共有するフェーズにあります。そこで今回はReactorKitを利用されている5名の開発者にご登壇いただき、知見を共有いただきました。さらに作者であるSuyeol Jeon 氏よりReactorKitの制作理由やコンセプトなどを発表いただきました。ご登壇頂いた皆様、この場をお借りして感謝いたします。ありがとうございます。
それでは、各発表の内容をご紹介していきます。
「Hello, ReactorKit! 👋」Suyeol Jeon, StyleShare
https://www.slideshare.net/devxoul/hello-reactorkit
ReactorKitの作者である Suyeol Jeon 氏のキーノートで「Hello, ReactorKit! 👋」というタイトルです。Jeon 氏は ReactorKit の他に Then や URLNavigator など OSS プロジェクトの作者であり、RxSwift のコントリビュータでもあります。本イベントのために韓国よりご参加いただきました。
キーノートでは、ReactorKit を作った理由や全体のコンセプト、データフローについて、またテストについてご紹介されていました。
ReactorKitを作った理由を説明する Suyeol Jeon 氏
ReactorKit を作った理由
Jeon 氏はReactorKitを作った理由として以下の3つを上げました。
- Controller の肥大化を防ぐ
- RxSwift の恩恵を受ける
- より分かりやすく状態管理をする
まず、「Controller の肥大化を防ぐ」についてです。MVCで開発する場合、状態を管理する Controller が肥大化するという問題は多くの iOSエンジニアが経験しているかと思います。Controller は多くの責務を持つためこれは当然のことです。Controller は View または Model と相互的にコミュニケーションしますが、これらのコードには、状態管理のロジックと View のインタラクションに関するコードが含まれます。ReactorKit では状態管理のロジックを Controller から引き離すことで、Controller の肥大化を防ぐことを目的としています。
続いて、「RxSwift の恩恵を受ける」についてです。RxSwift を含む Reactive Programming では、ある値を変更すれば依存している値も同時に変更されます。これは、ページネーションやリストの操作などで役立ちます。ReactorKit ではこの利点を受けることを目的としています。
最後に、「より分かりやすく状態管理をする」についてです。「Controller の肥大化を防ぐ」で説明した通り、状態管理のロジックを分離しますが、さらに状態管理を分かりやすく表現するための規約の導入を目的としています。Flux で述べられている通り、一方向のデータフローを実現することで開発者は変更の流れを簡単に追いやすくなります。また、後述の reduce 関数でのみ状態を変更するため、見通しが良くなります。これらの規約により非常にシンプルで分かりやすい記述が可能になります。
コンセプト
次に、全体のコンセプトについてです。
Fig. 1.1の通り、大きく4つの概念からなります。View と Reactor、そして Action と State です。
まず、Action と State に注目します。Action はユーザインタラクションを抽象化したもので、例えば「フォローする」や「ページングする」などのインタラクションを表現します。State は View の状態の抽象化したもので、例えば「フォローされているか」や「投稿のコレクション」などを表現します。
次に、View と Reactor についてです。View は現在の State を描画し、ユーザからのインタラクションを処理する役割です。View Controller や Cell などがそれに当たります。一方 Reactor は、ビジネスロジックを担い状態を管理する役割を持ちます。また、Reactor と View は1:1の関係となります。
Fig. 1.1 ReactorKitの概念図 (https://www.slideshare.net/devxoul/hello-reactorkit Page 33より引用)
データフロー
Reactor には、mutateとreduceの2つの関数があります。ReactorはActionを受け取るとmutate関数を呼び出し、副作用のある処理を実行しMutationを返すObservableを返却します。reduce関数はこのMutationを受け取り、新しいStateを作成し返却します。ここで新しく登場したMutationは、具体的な状態の変更方法を抽象化したものです。さらに、mutate関数はObservableを返却するため、非同期処理を含むことができます。また、MutationはReactor内部に閉じたオブジェクトでありViewからは参照されません。
Fig. 1.2 Reactor内での流れ (https://www.slideshare.net/devxoul/hello-reactorkit Page 45より引用)
より具体的な実装を「Instagramのプロフィール画面にあるフォローボタンをタップしてフォロー状態にする」という状態変化を例に紹介されていました。
例えば、プロフィール画面のView ControllerをProfileViewControllerとするとReactorはProfileViewReactorといった名前になります。まず、Actionを定義します。ユーザインタラクションは、フォローなのでActionの名前はfollowとなります。Stateはフォローしているかどうかという状態を表すisFollowingという名前のプロパティを宣言します。次に、Mutationを定義します。具体的な状態変更を表現するので、setFollowingといった名前になり、変更後のBoolを付属値として持ちます。
Fig. 1.3 Instagramのフォローボタン (https://www.slideshare.net/devxoul/hello-reactorkit Page 49より引用)
Fig. 1.4 Reactorで定義するAction, Mutation, State (https://www.slideshare.net/devxoul/hello-reactorkit Page 56より引用)
全体の流れは以下のとおりです。まず、Viewでフォローボタンがタップされます。Viewはこのタップイベントを受け取り、Action.followをReactorに伝えます。mutate関数はAction.followを引数に取り、副作用のあるUserService.follow()を呼び出します。このUserService.follow()は結果のフォロー状態を表すObservable<Bool>を返します。さらにこのBoolからMutation.setFollowingにマップしたObervableを返却します。ここまでがmutate関数です。非同期処理の実行が完了するとMutation.setFollowing(true)を引数に取りreduce関数が実行されます。reduce関数は新しいState、つまりstate.isFollowingがtrueになったStateを返します。最後にこの変更をObservableを介してViewが受け取り、フォローボタンの状態を「フォロー中」に更新します。以上がReactorKitで実現するデータフローのすべてです。
さて、Jeon 氏はもう少し詳細なユースケースについても述べられていましたが、他の発表者の内容と重複する部分もあるので割愛します。詳しくはスライドをご確認ください。
テストについて
さて、テストは非常に重要なパートです。ほとんどのライブラリは、テストコードを書く方法を考慮されていなければならず、なければ導入は困難です。もちろん、ReactorKitはその方法を提供しています。さらに、Jeon 氏はより明確にテストの対象や方法について示しています。これはReactorKitの魅力の一つです。
まず、テスト対象は何でしょうか。Jeon 氏はViewおよびReactorで以下のことをテストするべきだということを述べました。
View:
- ユーザインタラクションがあった時にActionが送られるか?
- Stateが変わった時にViewが更新されるか?
Reactor:
- Actionを受け取った時にStateが変更されるか?
さて、具体的にどのようにテストをする必要があるでしょうか。Jeon 氏はこの点についても具体的な方法を述べています。まず、Reactorにはstubというプロパティが用意されており、このプロパティを通して簡単にスタブを実現できるようになっています。このスタブの機能を利用することで、Reactorのstateに任意のStateを設定し、必要なActionを送ることができます。さらにどんなActionを受け取ったのかを記録することができます。これでテストの準備が十分であることがわかります。
Joen 氏は続けて具体的なコードも例示しています。Fig. 1.5 はフォローボタンを押して、Actionが発行されていることを確認する例で、Fig. 1.6 はStateの変更からフォローボタンの状態が変更されることを確認する例です。
また、Fig. 1.7ではReactorではfollowアクションが投げられた後、isFollowingがtrueになっていることを期待する例です。
Fig. 1.5 Viewでユーザインタラクションからアクションが発行されることを検証するテストコード (https://www.slideshare.net/devxoul/hello-reactorkit page125より引用)
Fig. 1.6 ViewでStateの変更でUIの状態が変更することを検証するテストコード (https://www.slideshare.net/devxoul/hello-reactorkit page131より引用)
Fig. 1.7 Actionを発行してStateの変更を検証するテストコード(https://www.slideshare.net/devxoul/hello-reactorkit page135より引用)
「ReactorKitを実戦投入してみて」Yu Sugawara, Picos
https://speakerdeck.com/yusuga/reactorkitwoshi-zhan-tou-ru-sitemite
菅原 祐 氏で「ReactorKitを実戦投入してみて」というタイトルでの発表です。菅原氏はPicosという制作会社の代表かつiOSエンジニアをされています。札幌を拠点として活動されており、今回はこのイベントのためにお越しいただきました。また、日本ではいち早くReactorKitを利用されている開発者の一人です。
この発表では、ReactorKitの利点と実践するにあたり発生した課題とそのアプローチについてお話されていました。
ReatorKitの利点
まず、ReactorKitの利点についてです。大きく2つあり、1つは強力な規約があること、もう一つは軽量であることと述べられていました。
多くのアーキテクチャではアーキテクチャを理解して、正しい実装に落とすことは容易ではありません。これらは具体的な規約を持たず、チームやプロジェクトに依存するためです。一方Fluxライクなアーキテクチャを実現するReactorKitでは柔軟性がありつつ、非常に強力な規約があることが強みです。そのため、導入してすぐにコードを書くことができます。
次に軽量であることも述べられています。作者のキーノートでも説明があった通り、ReactorKitは非常にシンプルなコンセプトです。実装として登場するプロトコルもView ProtocolとReactor Protocolの2つだけです。実際に導入して、責務の分割が分かりやすい粒度であったと実感したそうです。
また、RxTodoやCleverbotやDrrribleなど具体的なアプリケーションでの実装を確認できるのも魅力的だと語られていました。
実践投入して悩んだポイントとそのアプローチ
続いて、1.5人月規模のプロジェクトで導入したときに悩んだポイントとそのアプローチについてです。以下の5つの疑問について述べられていました。それでは一つずつ見ていきます。
- 画面遷移は誰が行う?
- Alert表示は誰が行う?
- Realmとの組み合わせ方は?
- NoActionのReactorは必要?
- UITableViewCellのReactorは誰が管理する?
画面遷移は誰が行う?
Reactorの登場により、状態管理の責務はReactorへ移動しましたが、画面遷移のロジックはどうでしょうか?例えば、MVVMでは、ViewModelというのが一つの答えでないでしょうか。一方でReactorKitを使った場合はどうでしょう?菅原 氏はViewレイヤに書くという結論に至っています。少なくともReactorはUIKitをimportしてはいけないためReactor内で行うのはNGのようです。
Fig. 2.1 Viewのbind関数内で画面遷移を定義 (https://speakerdeck.com/yusuga/reactorkitwoshi-zhan-tou-ru-sitemite Page 25より引用)
Alert表示は誰が行う?
例えば、編集画面で編集を中断しようとして編集内容が失われるような場合、「Are you sure?」のようなAlertを表示することが一般的ですが、このようなAlertは誰が行うのでしょうか?Alertの画面遷移ではあるので、VIewレイヤでしょうか?菅原 氏はService レイヤだと述べています。これを行う理由として、AlertのActionを型安全にできることを上げています。菅原 氏はこの考え方に賛否がありそうだとも付け加えていますが、ユーザの操作を伴う副作用もActionストリーム内の反応(React)の1つに過ぎないと考えることができると主張されていました。
Fig. 2.2 mutate関数でAlert表示をするサービスを呼ぶ (https://speakerdeck.com/yusuga/reactorkitwoshi-zhan-tou-ru-sitemite Page 31より引用)
Realmとの組み合わせ方は?
Realmをキャッシュなどの用途に利用されている開発者も多いかと思います。RealmとReactorKitをどのように組み合わせるのか?菅原 氏はServiceレイヤからのGlobal Eventを利用するのが答えだと述べています。さらにRealmは優秀な通知機能を持っているので、そのイベントをtransformでマージします。一方で、変更のActionがあった場合は、actionからServiceを呼び出しますが、mutationを返さずemptyにします。これにより、actionからservice、serviceからrealm、realmからtransformを経由してreduceにmutationが流れてくるようになります。
Fig. 2.3 サービスレイヤを通してGlobal Eventを利用 (https://speakerdeck.com/yusuga/reactorkitwoshi-zhan-tou-ru-sitemite Page 44より引用)
NoActionのReactorは必要?
続いての疑問は非常に興味深いです。Actionを持たない、つまりNoActionのReactorが必要なのかという疑問です。通常、ユーザのインタラクションを受け取り状態を更新するためにReactorを作成します。菅原氏の意見は、必須ではないが、いくつかの利点も考えられると述べています。具体的にはどういうことでしょか。例えば、Global Eventからの変更を受け取りたいなど、アクションなしでもリアクティブにしたいケースがあります。確かにこういったケースではNoActionになります。
UITableViewCellのReactorは誰が管理する?
UITableViewControllerとそのReactorがあり、さらに複数のUITableViewCellとそのReactorという構成を考えます。このときCellに対応するReactorは動的に増減しますが、これは誰が管理するのでしょうか?菅原氏の答えは、UITableViewControllerのReactorです。
Fig. 2.4 UITableViewControllerのReactorがStateとして持つChatViewSection (https://speakerdeck.com/yusuga/reactorkitwoshi-zhan-tou-ru-sitemite Page 54より引用)
まとめ
- ReactorKitの方にはめた設計は楽
- Protocolの準拠するだけなので柔軟な設計が可能
- 単方向ストリームはシンプルで整理しやすい
- アーキテクチャ構築のコード量を削減
「Architecture and ReactorKit」Yoichi Tagaya, Mercari
https://speakerdeck.com/yoichitgy/architecture-and-reactorkit
多賀谷 洋一 氏で「Architecture and ReactorKit」というタイトルでの発表です。多賀谷 氏は、DIフレームワークであるSwinjectの作者として知られており、現在は株式会社メルカリのエンジニアリングマネージャーをされています。
この発表では、ソフトウェアアーキテクチャとは何かについてを述べられ、Fluxの立ち位置を確認したのち、ReactorKitの良さについて語られました。
ソフトウェアアーキテクチャとは何か?
ReactorKitはFluxアーキテクチャに強くインスパイアされたフレームワークです。Fluxアーキテクチャの話をする前に、そもそもアーキテクチャ、つまりソフトウェアアーキテクチャとは何なのでしょうか。そのことについて多賀谷 氏は述べています。
「What is software architecture」とGoogleで検索すると、Peter Eeles 氏の What is a software architecture? というタイトルの記事が2番目にヒットします。(2018年7月現在) この記事の本文でも参照されている通り、IEEE 1471に以下の記述があり、アーキテクチャを定義しています。
Architecture is the fundamental organization of a system embodied in its components, their relationships to each other, and to the environment, and the principles guiding its design and evolution. [IEEE 1471]
日本語に翻訳すると、以下のような表現になります。
「アーキテクチャとは、システムの重要な構成のことであり、システムのコンポーネントや互いの関係、環境、その設計と展望を導く考え方を実現するものです。」
非常に抽象度が高いですが、なんとなく伝わってきます。さらに、Peter Eeles 氏の記事で述べられていることを簡単にまとめられていました。以下のとおりです。
アーキテクチャは、
- 構造を定義する
- 振る舞いを定義する
- 重要な要素についてのみ考える
- チーム構成に影響を与える
- 論拠に基づく意思決定を実現する
- ステークホルダーの複数の相反する要求の均衡を保つ
- 取り巻く環境に依存する
- 同じ課題を共有するアーキテクチャパターンに準拠することができる
多賀谷 氏は特に最後の3つについて重点的に説明されていました。
「ステークホルダーの複数の相反する要求の均衡を保つ」にいては、社内外のステークホルダーが沢山存在し、そして同時に彼らの要求をうまくバランスするためにアーキテクチャが機能し、そしてそれが重要であることを述べています。
次に、「取り巻く環境に依存する」では、会社の環境、例えば、株価やDAU、コードベースの古さや量、多くの新しい機能や多くのエンジニアなど、様々な変数に依存することを説明しています。
また、これらから分かることして、未来においてステークホルダーや環境がより複雑で大きなものになることによって、選択するアーキテクチャも変わりうるということです。
最後に「同じ課題を共有するアーキテクチャパターンに準拠することができる」についてです。(上記の「アーキテクチャパターン」は、発表および原文では「Architectual Style」という表現となっています) ここで言うアーキテクチャパターンは、つまり、MVCやMVVM、VIPPER、iOS Clean Architecture、Fluxなどのことを指します。これらのパターンは、それぞれの共有する課題を解決するために存在します。例えば、Fluxであればデータの更新フローの煩雑さを課題とし、一方向のデータフローによる状態管理というアプローチをしています。
Fig. 3.1 社外や社内のステークホルダー (https://speakerdeck.com/yoichitgy/architecture-and-reactorkit page 8より引用)
Fig. 3.2 影響されうる社内の環境 (https://speakerdeck.com/yoichitgy/architecture-and-reactorkit page 9より引用)
ReactorKitの良さ
さて、ここまででソフトウェアアーキテクチャについてとFluxの立ち位置について説明がありました。ここで多賀谷氏は、MVCパターンを実現するフレームワークとしてRuby on Railsを取り上げました。Ruby on RailsはWebアプリケーションフレームワークであり、高速な開発を実現するフレームワークであることを示しています。Ruby on Railsは強力なCoC(onvention over configuration)を持っており、高速な開発を実現する大きな要因の一つです。
そして、ここでReactorKitが登場します。ReactorKitはFluxパターンを実現するフレームワークであり、Railsと同様に高速な開発を実現し、未来のステークホルダーや環境においてもフィットすることを主張しています。これはRails同様に迷わない強い規約が要因にあります。この点は菅原 氏の発表でもあった「迷わず正しい設計をする」ということに繋がっているように感じました。
Fig. 3.3 ReactorKitは高速な開発を可能にする (https://speakerdeck.com/yoichitgy/architecture-and-reactorkit page 13より引用)
まとめ
- アーキテクチャはステークホルダーや環境に依存する
- ReactorKitはステークホルダーや環境の成長に対応する強いフレームワーク
「ReactorKit at Wantedly」Jiro Nagashima, Wantedly
https://speakerdeck.com/hedjirog/reactorkit-at-wantedly
永島 次朗 氏で「ReactorKit at Wantedly」というタイトルでの発表です。永島 氏はiOSエンジニアであり、現在Wantedlyではユーザグロースチームのリーダーをしています。
この発表では、ReactorKitがシンプルかつ強力であることを、Wantedly VisitのiOS Appにおける具体的なユースケースを通して説明しています。
導入されているiOS Appについて
Wanteldy Visitは、気になる会社に気軽に遊びに行くことのできるサービスです。iOS Appでは募集の検索やプロフィール、チャットなどの機能があり、これらは非常に多くの状態管理を必要としており、ReactorKitはこれらすべての面倒を見てくれます。昨年の2017年に1画面で導入し、2018年現在ではほとんどの画面で利用されています。
シンプル
実際に導入して感じたこととして、ReactorKitが非常にシンプルであることを強調しており、3つのメリットを上げています。「記述量が少ないこと」「理解しやすいこと」「コードレビューしやすいこと」です。特に、コードレビューしやすい点に関しては、どこに何が書かれているかが明確なためコードが追いやすいことが要因にあります。
強力
永島 氏は、ReactorKitがシンプルというだけでなく、強力であることも伝えています。実際の実装例として3つのケースを上げました。
- ブックマークの状態を表示する
- ブックマークの状態を他のViewコンポーネントへ伝搬させる
- 異なるViewコンポーネントで同じリソースを共有する
ブックマークの状態を表示する
まず、「ブックマークの状態を表示する」についてです。このケースでは、画面が読み込まれた時に API リクエストを実行し、その結果を表示します。
Viewのbind関数では2つのbindがあります。1つは、viewDidLoadが呼ばれた際にAction.loadをReactorに伝えるもので、もう1つは、state.isBookmarkingをブックマークボタンに伝えるものです。Reactorを見ると先程登場したActionとStateが定義されており、またMutationでMutation.setProject(Project)が定義されているのがわかります。mutateでは、API リクエストを行うprojectRepositioryの関数が呼びされておりProjectのObservableが返ってきます。ここで更にMutation.setProjectにマップすることでMutationに変換しています。次にreduce関数では、渡ってきたMutation.setProjectを処理しています。ここではstate.isBookmarkingを反映しているのがわかります。
Fig. 4.1 Viewのbind関数 (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page 16より引用)
Fig. 4.2 ReactorのAction, Mutation, State (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page18より引用)
Fig. 4.3 Reactorのmutate関数とreduce関数 (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page19より引用)
ブックマークの状態を他のViewコンポーネントへ伝搬させる
さて、次にこのブックマークの状態を他のViewコンポーネントでも表示したいケースについてです。Visit iOS Appでは、募集の一覧画面でもブックマークの状態が表示されているため、ここにも状態を反映します。一般に、詳細ページと一覧ページで状態を共有したいというケースは数多く存在するかと思います。こういったケースでは、共有するストリームを作成します。ストリームへの流し込みはRepositoryレイヤで行います。次に受け取りたいViewコンポーネントに対応するReactorのtransform関数を利用します。transform関数ではmutationのストリームにマージすることができるため、共有のストリームからMutationに変換してマージします。これだけで、状態を簡単に共有することができます。
Fig. 4.4 詳細画面での変更をEvent Streamを介して一覧画面のReactorに伝える (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page22より引用)
Fig. 4.5 Repositoryで共有のストリームに変更を通知 (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page24より引用)
Fig. 4.6 Reactorのmutate関数ではRepositoryを呼びemptyを流す (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page25より引用)
Fig. 4.7 Reactorのtransform関数で共有のストリームから変更のイベントを受け取る (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page27より引用)
異なるViewコンポーネントで同じリソースを共有する
最後に、異なるViewコンポーネントで同じリソースを共有するケースです。VisitのiOS Appでは各会社の情報を見ることのできる会社ページが存在します。この会社ページには、ページ内にタブがあり遷移することができます。例えば、一番最初に表示されているホームタブと募集タブでは、共通のリソースである募集のコレクションを表示します。もうすでにお気づきだと思いますが、このケースに追いても先程のブックマークと同様に共通のストリームを作成し、transform関数でMutationとしてマージします。これにより異なるViewコンポーネントで常に同じリソースを表示することができます。
Fig. 4.8 各企業の情報をみることのできる会社ページ (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page29より引用)
Fig. 4.9 募集一覧画面のReactorで取得した内容を共有ストリームを介してホーム画面にも反映 (https://speakerdeck.com/hedjirog/reactorkit-at-wantedly page32より引用)
まとめ
- 複雑な状態管理を実現できる
- 多くのユースケースはカバーできる
「try! ReactorKit」Satoshi Hachiya, RCUBE
https://speakerdeck.com/jp_pancake/try-reactorkit-1
八谷 賢 氏で「try! ReactorKit」というタイトルでの発表です。八谷 氏は、パンケーキさんという名前でも知られ、try! Swiftの運営をされています。また、Swift Package Managerのコントリビュートもされています。
この発表では、八谷 氏がReactorKitを使ってみて躓いた点についてまとめています。まだ、ReactorKitを利用されていない開発者向けには非常にありがたい内容です。
八谷 氏は try! Swift NYC 2017 に運営として参加されていましたが、その時にすでにReactorKitの作者である Suyeol 氏と合われていたそうです。また、最近だとWWDC 2018(の開催後)でも偶然出会い、お話されたそうです。実際にReactorKitに触れたのはその後のことだそうです。その時に感じた印象や躓いた点についてご紹介されました。
ReactorKitについて、ドキュメントが非常に読みやすく、サンプルコードが豊富にあり、いつでも始められることを強調していました。サンプルコードについては、ReactorKitのREADMEの最後にリンク一覧があります。また、菅原 氏が書かれた「ReactorKitを学ぶ」シリーズの記事も挙げられ、初学者向けの日本語ドキュメントが豊富にある点も述べられました。
さて、早速導入から始めます。CocoaPodsでインストールします。また、注意点としては、オフィシャルにCarthage対応されていない点です。
次に、導入したいView Controllerに対応するReactorを作成します。最初に、Actionを書きます。ActionはEnumなのでcaseを追加していきます。今回は、増加と減少なので、increaseとdecreaseを追加します。次に、Mutationを書きます。こちらもEnumで、変化の仕方を記述します。今回は、値を増やすか・減らすかなので、increaseValueとdecreaseValueを追加します。次にStateです。StateはStructで必要なプロパティを宣言します。今回は値の増減なので単純にvalueというIntのプロパティを宣言します。次にReactorのプロパティとしてinitialStateを初期化します。これは状態の初期値を表します。valueは0から始めたいのでvalueに0をセットしてinitalStateを初期化します。
続いてView Controllerです。View ControllerにViewというプロトコルを準拠させ、bind関数を書きます。このbind関数でボタンのタップのObservableをAction.increaseにマップしてそれをreactor.actionにバインドします。これで、インタラクションからアクションを発行することができました。非常に簡単ですね。
ここで八谷 さんは、いくつか躓いたケースについてご紹介されていました。
まず、「アクションが発行されない」という問題です。実は今までの流れで抜けているパートがあります。それは、View Controllerに作成したReactorをセットするというパートです。ここで重要なのは、そのView Controllerの外側でセットするという点です。
Fig. 5.1 ReactorKit READMEではReactorをViewの外から設定することを説明している (https://speakerdeck.com/jp_pancake/try-reactorkit-1 page26より引用)
次に躓いた点は、View Controllerが持つCollection Viewのコンテントオフセットからアクションを発行する際に起きたランタイムエラーでした。これはRxCocoaでのエラーで、そのView ControllerをUICollectionViewDelegateに準拠させる必要があったそうです。RxSwiftやRxCocoaを初めて利用される方を含め、こういった情報が非常に役立ちます。
さて、もう一度振り返り、「一方向のデータフロー」を体感します。ViewでボタンのtapからAction.increaseがReactorに伝搬します。ActionからMutationに変換します。そして最後にMutationからStateに変換し、ViewでStateをラベルのテキストに反映したら終了です。これでボタンを押したら1増えて表示されるということが実現できました。
まとめ
八谷 氏は全体としてコードが読みやすくなる点を指摘しつつ、特に以下の特徴をあげられていました。
- 責務を分けやすい
- Viewの状態を簡単に管理できる
- データフローを追いやすい
ここまでお読みになって簡単そう!と感じた方、ぜひtry! ReactorKitしましょう。
「Test-Driven-Development with ReactorKit in StyleShare」Wooseong Kim, StyleShare
https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare
Wooseong Kim 氏で「Test-Driven-Development with ReactorKit in StyleShare」というタイトルでの発表です。Wooseong 氏は、Suyeol 氏と同じStyleShareという会社でiOS エンジニアをされています。
この発表では、StyleShare社で実践されているReactorKitでのTDD(Test-Driven Development)についてお話されています。また、Wooseong 氏は日本語で発表されており、会場では驚きの声が上がりました。
Quick look: Quick / Nimble / Stubber
はじめに、QuickとNimbleそしてStubberの紹介です。Quickは、BDD(Behavior-Drive Development)フレームワークで、RSpecなどからインスパイアされています。Nimbleはexpect構文や各種matcherを提供しています。
最後にStubberについてです。Stubberは、Suyeol氏が作られたOSSライブラリで、メソッドスタブを簡単に実現することができます。例えば、UserServiceのfollowメソッドをスタブしたい場合、Fig . 6.1のように実現することができます。
Fig. 6.1 StubberでServiceのメソッドスタブをする例 (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page10 より引用)
StyleShareでの実例
さて、本題に入りましょう。StyleShare社で実践されているTDDでどのようにテストを書いているのかについてステップバイステップで説明をされていました。
StyleShareは、韓国ファッション&ビューティー情報を見ることのできるiOS Appを提供しています。Fig. 6.2 の通り、Beautyのタブでは美容情報が一覧で表示されています。このタブを実装することを例として説明しています。
Fig. 6.2 StyleShareのBeautyタブ (page13 より引用)
TDDですので、まずはテストを作成します。BeautyFeedEventsViewReactorSpecクラスを作成します。次に空のReactorであるBeautyFeedEventsViewReactorを作成します。もう一度Specに戻ります。まず、「ページがリフレッシュ中かどうか」という状態を持ち、それが初期状態でfalseであることを期待するコードを書きます。ここでテストを実行してみると、isRefreshingは未定義なので、Fig. 6.3 の通り、コンパイルエラーとなりhas no member ‘isRefreshing’と表示されます。
Fig. 6.3 初期状態でisRefreshingがfalseであることを期待するテストコード (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page17 より引用)
それでは、isRefreshingを定義しましょう。Fig. 6.4の通りに変更すると、今度はグリーンになっていることが確認できます。
Fig. 6.4 StateにisRefreshingを定義し、initialStateとしてfalseを設定 (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page18 より引用)
次に、「fetchアクションが呼ばれた後はisRefreshingになっていること」を期待するコードを書きます。次に、プロダクトコードを書きます。ActionとMutationの定義を追加し、mutateとreduce関数を実装します。もちろん、fetchが終わったらisRefreshingはfalseになってほしいですが、今は一旦空のexampleを書いておきます。
Fig. 6.5 空のexampleを作成 (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page22 より引用)
次は、「初期状態でコンテンツが空であること」を期待します。つまりbeautyFeedEventsが空であることを検証するコードを追記します。さらにプロダクトコードでstateとinitialStateを定義することでこのコードをパスすることができます。
Fig. 6.6 初期状態でコンテンツが空であることを期待するテスト (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page25 より引用)
Fig. 6.7 初期状態を設定し、Fig 6.6 のテストを満たす (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page26 より引用)
次にfetchのアクションを呼ぶことで適切なコレクションがセットされることを期待します。ここでServiceクラスを作成し、そのモックを作成します。モックは先程紹介したStubberを利用します。最後にmutate関数とreduce関数を実装すればテストをパスすることができます。
Fig. 6.8 Serviceのプロトコルとクラスを作成 (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page29 より引用)
Fig. 6.9 スタブクラスを作成 (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page30 より引用)
Fig. 6.10 モックデータを使って結果の状態を検証 (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page33 より引用)
さて、先程一旦空で作成したexampleに戻りましょう。同様にfetchアクションを呼び、「isRefreshがfalseに戻ること」を期待します。プロダクトコードでは、mutate関数を実装すればテストが通ります。最後に、Serviceクラスを実装すれば完成です。
このように、非常に簡単にTDDができることがわかります。
Fig. 6.11 取得が成功したあとにisRefreshingがfalseになっていることを期待するテストコード (https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare page38 より引用)
まとめ
- Quick / Nimble / Stubber でTDD環境を実現できる
- Reactorも高速にTDDできる
おわりに
さて、発表内容の解説は以上です。もうすでにReactorKitを利用されている方は、より踏み込んだ現場の知見を知ることができたでしょうか?また、初めての方にはReactorKitの魅力を感じて導入してみたいと感じてもらえたでしょうか?本記事を通してReactorKitの理解の一助となれば幸いです。最後になりましたが、お読みいただきありがとうございます。そして、一緒にコミュニティを盛り上げていきましょう!
Apendix
発表資料や参考資料などのリンク一覧です。
- https://github.com/ReactorKit/ReactorKit
- https://www.slideshare.net/devxoul/hello-reactorkit
- https://speakerdeck.com/yusuga/reactorkitwoshi-zhan-tou-ru-sitemite
- https://speakerdeck.com/yoichitgy/architecture-and-reactorkit
- https://speakerdeck.com/hedjirog/reactorkit-at-wantedly
- https://speakerdeck.com/jp_pancake/try-reactorkit-1
- https://speakerdeck.com/innocarpe/test-driven-development-with-reactorkit-in-styleshare