はじめに
はじめまして。ウォンテッドリー株式会社で、サーバーサイド・iOSエンジニアをしている永田健人(@ngtknt)です。今回は6月28日に開催された「ReactorKit Meetup Japan」について紹介します。
まず、「ReactorKit」について簡単に説明します。ReactorKitは、iOSでリアクティブかつ一方向なデータフローを実現するフレームワークです。リアクティブプログラミングとFluxの組み合わせにより、よりシンプルでわかりやすい状態管理を実現しています。詳しくは作者Suyeol Jeon氏のキーノートにて紹介します。
さて、本イベントの開催経緯も簡単に説明します。
ご存じの通り、クライアントサイドでのユーザ体験はよりリッチなものになってきています。ユーザ体験がリッチになればより複雑な状態管理が求められます。iOS開発においても、この複雑さに対応するためのアーキテクチャやその実装について長く議論されてきました。
一方、WebフロントエンドでもFluxやReduxの登場で、ページ内での状態管理やアプリケーション全体を通してのグローバルな状態管理をどうするのかなど、非常に複雑な状態管理をわかりやすく記述するための設計が確立しつつあります。Fluxでは状態変更に必要な責務を適切に分割し、わかりやすく宣言できるようにしたこと、流れが一方向であることが、読みやすさに大きく貢献しているように思います。さらに、Reduxではページ間での状態共有や変更の依存関係をうまく表現するための仕組みを実現しています。
これらの流れはiOSでも起きており、FluxではReactorKit、ReduxではReSwiftなどで実現されています。さらにReactorKitはプロダクトで採用される機会が増えてきており、現場での導入事例や知見を共有するフェーズにあります。そこで今回はReactorKitを利用している5名の開発者に登壇いただき、知見を共有してもらいました。さらに作者であるSuyeol Jeon氏よりReactorKitの制作理由やコンセプトなどを発表いただきました。登壇いただいた皆さま、この場をお借りして感謝いたします。ありがとうございます。
それでは、各発表の内容を紹介していきます。
「Hello, ReactorKit!」Suyeol Jeon, StyleShare
ReactorKitの作者であるSuyeol Jeon氏のキーノートで「Hello, ReactorKit!」というタイトルです。Jeon氏はReactorKitの他、ThenやURLNavigatorといったOSSプロジェクトの作者であり、RxSwiftのコントリビュータでもあります。本イベントのために韓国より参加いただきました。
キーノートでは、ReactorKitを作った理由や全体のコンセプト、データフロー、さらにテストについて紹介がありました。
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の関係となります。
データフロー
Reactorには、mutateとreduceの2つの関数があります。ReactorはActionを受け取るとmutate関数を呼び出し、副作用のある処理を実行してMutationを返すObservableを返却します。reduce関数はこのMutationを受け取り、新しいStateを作成し返却します。ここで新しく登場したMutationは、具体的な状態の変更方法を抽象化したものです。さらに、mutate関数はObservableを返却するため、非同期処理を含むことができます。また、MutationはReactor内部に閉じたオブジェクトでありViewからは参照されません。
より具体的な実装は「Instagramのプロフィール画面にあるフォローボタンをタップしてフォロー状態にする」という状態変化を例に紹介されました。
例えば、プロフィール画面のView ControllerをProfileViewControllerとすると、ReactorはProfileViewReactorといった名前になります。まず、Actionを定義します。ユーザインタラクションは、フォローなのでActionの名前はfollowとなります。Stateはフォローしているかどうかという状態を表すisFollowingという名前のプロパティを宣言します。次に、Mutationを定義します。具体的な状態変更を表現するので、setFollowingといった名前になり、変更後のBoolを付属値として持ちます。
全体の流れは以下の通りです。まず、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を受け取ったのかを記録することができます。これでテストの準備が十分であることがわかります。
Jeon氏は続けて具体的なコードも例示しています。Fig. 1.5はフォローボタンを押して、Actionが発行されていることを確認する例で、Fig. 1.6はStateの変更からフォローボタンの状態が変更されることを確認する例です。
また、Fig. 1.7はReactorではfollowアクションが投げられた後に、isFollowingがtrueになっていることを期待する例です。