リクルート住まいカンパニー Tech Blog

ITのちからで暮らしをよくしたい、エンジニア・デザイナーが発信するTech情報メディア

RxSwiftをつかってMVVMアーキテクチャを実装する

f:id:recruit-sumai:20170309102500p:plain

こんにちは!今年度4月に入社した、SUUMOスマホサイト開発チームの沼田です。

入社してからはSUUMOスマホサイトで Infrastructure as Code を実践したり、DevOpsの基盤を整備しているのですが、最近社内でRxSwiftを布教する機会があったので、その際につくったサンプルプロジェクトを使って、RxSwiftとMVVMアーキテクチャを簡単に解説します。

実際に動くコードあるので、コードベースで解説していきたいと思います。Xcodeで⌘+click や option+clickをつかいながらコードを読むのも良いと思います。 サンプル:https://github.com/takuwan0405/RxSwiftSample

TL;DR

  • MVVM = View(UIや外観の実装) + ViewModel(Viewのデータや状態を保持) + Model(ビジネスロジックを実装、データを処理・保持)
  • 責務を明確に分担することで、ファット・複雑になりがちなコードをシンプルに表現できる
  • MVVMアーキテクチャにはデータバインディングのためのライブラリが必要で、RxSwiftやReactiveCocoaが有名
  • 今回は、Alamofire(HTTP通信)とHimotoki(JSON parser)を使ってATNDのAPIを叩き、勉強会の情報を取得する
  • サンプルプロジェクトはgithubのリポジトリにあげてあるので、Xcodeで動かしながら読むことを推奨

MVVMアーキテクチャとは

MVVMとは、View・ViewModel・Modelの三層でなるアーキテクチャのことです。 MVVMでアプリを設計・構築することで、各クラスは他クラスの挙動を意識せず書くことができ、各クラスで記述するべきコード内容(責務)が明確になるといったメリットがあります。

MVVMアーキテクチャを実装するためにはデータバインディングができるライブラリを利用する必要があり、iOSの場合は、RxSwiftReactiveCocoaSwiftBondReactiveKitあたりが有名です。

データバインディングの具体的な動きは、本記事のコードを見て頂きたいと思うのですが、各層を双方向にバインディング(感覚としてはKVOみたいなもの)することで、各クラスの独立性を高く保つことを実現しています。

まずViewは、UIKitを利用した外観を実装する部分で、UIViewController、UIView、UITableViewController、UITableViewCellなどが該当します。 UI部分の実装のみを担当し、ロジックに関わる部分はViewModelやModelに委譲するのが原則です。 MVCで構築したiOSアプリケーションでは、巨大なViewControllerに対峙しなければいけないことが多々ありましたが、MVVMでのViewはロジックを担当しない分、ファットなViewControllerになるリスクは大きく下がることになります。

ViewModelは、Viewの状態を保持したり、Modelから受け取ったデータをView向けに整えるプレゼンテーションロジックを実装する部分です。 Viewの状態とは、具体的にいうと、UIButtonがタップされた、UITextFieldに文字が入力された、UIActivityIndicatorが表示されている、UIScrollViewがスクロールした、といったことです。 こうしたViewの状態をViewModelに対してバインド(通知)することで、例えばUIButtonがタップされたときに、ViewModelで実装したコードが動き出す、みたいな実装をすることになります。

Modelは、データの取得・保持を担ったり、ビジネスロジックを記述するところです。 MVVMのModelについては誤解が多いそうなのですが、基本的にはMVCのModelと責務は同じであると私は思っています。 ただ、ModelはViewModelからバインド(監視)されているので、ModelはViewModelがバインドできる対象を用意する必要があります。

f:id:recruit-sumai:20170124112332p:plain

文字で長々と説明してしまったのですが、図で表現するとこんな感じになります。 この図で注意したいのは、ViewとViewModelは一対一、ViewModelとModelは一対多であることです。 ViewModelはViewの状態とデータを保持・実装する必要があるので、ContainerViewを使ったりしない簡単なアプリであれば、Viewの数だけViewModelが必要になります。

RxSwift事始め

MVVMアーキテクチャを実装するために今回利用するRxSwiftですが、RxSwiftはリアクティブプログラミングのためのライブラリです。 リアクティブプログラミングとはなにか、という疑問に対する解説は「Q. (関数型)リアクティブプログラミングとは何ですか?」という記事に譲りたいのですが、リアクティブプログラミングを用いることで、“時間とともに変化する値”を、ストリームと呼ばれるKVOのような仕組みに流し込むことができたり、従来であればコールバックやNSNotificationで書いていたような処理を簡単に書けたり、同期・非同期・並列処理を簡単に切り分けたりすることができます。

なんでこう説明を省くかというと、実際に触って体感しないと大抵の人は理解できないためです。 かくいう私も、Rxを触りつつ少しづつリアクティブプログラミングとはなにかを理解していっています。

ということで、サンプルコードを見ていきましょう。 1秒ごとに発火するイベントのcounterがある、以下のようなコードがあったとき…

RxCounter.swift

subscription1subscription2は、counterで0.1秒ごとにイベントが発火されるたびに、subscribeNextメソッドを通じてイベントを受け取ります。 subscribeNextメソッドなどを通じてイベントがsubscribeされているとき、「ストリームが流れている」といった表現をします。 dispose()メソッドでは、subscription1subscription2のイベントの監視をストップさせており、ストリームを断ち切っています。 subscription1では0.5秒後に、subscription2では1.0秒後にdispose()をしています。

こうした処理の結果、上記コードの実行結果は以下のようになります。

gist600d776c1c440cebf407804a8ebb9e4f

これでリアクティブプログラミングのストリームについて、少しでも理解が進めば良いなと思うのですが、次項から、今回つくった具体的なコードについて見ていきたいと思います。

今回つくったプロジェクトでは、ATNDというリクルートが開発・運用しているサービスのAPIを利用し、ATNDに登録されているイベントの一覧をUITableViewで表示しています。

今回利用したライブラリは下記の通りです。

Cartfile used in RxSwiftSample (https://github.com …

View:RxSwiftでUIKitを扱う

f:id:recruit-sumai:20170124112325p:plain

まずはMVVMでViewに当たるクラスを作成します。

RxSwiftはRxCocoaでUIKitをラップしています。 今回は使用していませんが、UIButtonがタップされたらrx_tapで、UITextFieldに文字が記入されたらrx_textでイベントを取得することができます。

以下が、今回書いたViewControllerのコードです。

EventsViewController.swift (https://github.com/tak …

tableView.rx_contentOffsetでtableViewがスクロールされた時にイベントを取得し、取得した値はViewModelのscrollEndComingという変数にバインド(送信)しています。 (この記述では、Viewにロジックを書いてしまっているのですが、原則通りにやろうとすると込み入りそう、RxSwiftの理解にはつながらないと思ったので、そのままにしています。)

tableView.rx_itemsWithCellIdentifierは、UITableViewDataSourceとUITableViewDelegateをラップしていて、この数行だけで簡単なTableViewを作成することができます。 ViewModelのevents変数に格納されている値をもとに、セルを生成しています。

また、tableView.reloadData()をしているところは何をしているかというと、ViewModelのevents変数に格納されている値が変更されたら、都度reloadData()を走らせるような処理を定義しています。

補足で、ObservableとDriverの違いですが、UIの更新に関わるところはDriverで、それ以外はObservableでという認識でいてもらえれば大丈夫です。 詳しくは、RxSwiftのドキュメントを見て下さい。

ViewModel:Viewの状態を表現

f:id:recruit-sumai:20170124112322p:plain

Viewの状態を保持しているViewModelは以下のようになっています。

ViewState.swift (https://github.com/takuwan0405/R …

EventsViewModel.swift (https://github.com/takuwan0 …

ViewStateというenumは、Viewの状態を.Working、.Blank、.Requesting、.Errorの4つに分類して表現しています。 EventsViewModelの中でうまく切り替わるようにしているのですが、ViewでViewStateをバインドして、.RequestingのときはHudを表示させておく、.ErrorのときはErrorTypeの内容を表示する、といった処理を書くことも可能です(今回は手が回らず作れていません)。

scrollEndComing.asObservable()のところでは、Viewからバインドされて送られてくる値をもとに、APIをコールしています。

eventModel.responseStateについては、ModelのresponseStateにはAPIの返却値が格納されるようになっているので、ModelでAPIをコールして値を取得する度に、処理が走るような流れになります。

.subscribeOn(scheduler.serialBackground)や.observeOn(scheduler.main)が気になるところではあると思うのですが、この命名通り、同期処理と非同期処理を切り分けています。 subscribeOn以前は非同期で、observeOn以降は同期で処理するように宣言しています。 同期・非同期・並列処理については、以下のように設定しています。

RxScheduler.swift (https://github.com/takuwan0405 …

Model:Alamofire * RxSwiftでAPIをコール

f:id:recruit-sumai:20170124112320p:plain

AlamofireでATNDのAPIを叩いて、勉強会のデータを取得します。 APIを叩く部分は、こんな感じです。EventsRequest.GetEvents(count).subscribe{…} でAPI通信の結果を取得できます。

Alamofire with RxSwift (https://github.com/takuwan …

実際にAlamofireでリクエストをしている、APIRequestのrequest()のような非同期処理では、リアクティブプログラミングを使っていない場合はコールバック処理を書いたり、NSNotificationをつかって通知するというのが一般的なデザインパターンだったと思いますが、RxSwiftではObservable.createでストリームを生成することで、そのようなデザインパターンは不要になります。

次は、APIをsubscribeしたりparseしているところです。 JSONのパーサーにはHimotokiというライブラリをつかっていて、EventListResponseをHimotokiで丸々パースするための記述です。

EventsModel (https://github.com/takuwan0405/RxSwif …

JSON parser with Himotoki (https://github.com/taku …

ApiResponse.swift (https://github.com/takuwan0405/ …

EventsModelrequestStateには、APIで取得した値を格納しています。requestStateは、より具体的にはRequestStateというenumで、enumに値を格納しています。 このrequestStateはViewModelからバインド(監視)されているので、APIの結果が格納されたら、ViewModelのストリームが動き出すといった処理の流れになります。 enumに値を格納できるのは、swiftならではで良いですね。

 

おわりに

リクルート住まいカンパニーはSUUMOだけでなく、SUUMOから派生したサービスや新規事業サービスが多数あるのですが、RxSwiftをはじめとしたリアクティブプログラミングをプロジェクト作成時から導入しているサービスはありません。 これからRxSwiftを導入していくアプリがあるのですが、どこから導入していけば良いのか、既存のプロジェクトの設計にどう寄せていくのか、というところの知見まで、溜めていければと思っています。

リクルート住まいカンパニーでは、一緒にSUUMOのiOSアプリをつくっていく仲間を募集しています!詳しくはこちらをご覧ください!

 

参考