こちらの勉強会の登壇資料です
iOS Clean Architecture勉強会 sponsored by Sansan https://connpass.com/event/158269/
巨大な機能をVIPER + MicroViewControllerでいい感じに実装した話Sansan株式会社髙橋 佑一朗
View Slide
- 髙橋佑一朗 (@ChaaaaaaaaaaanU)- Sansan 株式会社- iOS アプリエンジニア- Flutter やってます- ゲーム大好き- DbD, Bloodborne, Dark Souls, Pokemon, Smabro, etc...Who am I?
- Why VIPER?- What’s happened?- What MicroViewController- How do we combine them?- Summary- Next StepAgenda
Why VIPER?
- アプリが複雑化し ViewController が FatViewController へ進化- 更に各ロジックがほとんど ViewController に記述されていて密接に絡み合っている- テストも書けないし、コードの見通しも悪くなってしまった- どこかを直すとどこかが壊れるピ◯ゴラスイッチ状態Why VIPER? - 課題感
- 昔の Sansan のアーキテクチャWhy VIPER? - 移行前
- 単一責任の原則に則った、責務の分離が容易になった- チームの共通認識として実装時のコミュニケーションが円滑になった- 各コンポーネントは Interface に依存させているためテストが容易に- ViewController の責務が View の表示とユーザイベントの制御だけになり激痩せWhy VIPER? - 導入した結果
What’s happened?
- 名刺詳細の人物詳細へのリニューアル- 名刺に紐づく人物の情報を表示する画面- 最新の名刺情報と経歴、メモ、コンタクト、接点のある同僚など、表示する情報がとても多い- 更に自分が持っている名刺についての情報を表示するタブがもう一つある- 情報量的にとてもリッチになり、様々な情報が一つの画面で閲覧できるためユーザ的には価値が高いWhat’s happened? - 人物詳細
どう実装する?
単一の VIPER Module として実装すると Fat になるのは火を見るより明らか
- 機能が大きすぎて一つの VIPER Module として実装するのは無理がある- 各機能毎に独立した ViewController を用意して一つ一つの Module を小さく保ちたい- 機能毎に分割するとそこそこ ViewController の量が増えるので各ViewController の通信の Interface を統一したい- TableViewCell に ViewController を attach するのは意外と面倒そうWhat’s happened? - 課題感
What MicroViewController?
- Mercari 発のアーキテクチャで iOSDC 2018 にて発表- 画面はもちろん画面を構成するボタンやラベルまで全て ViewController で構成するアーキテクチャ- 画面を構成する全てのパーツが自身に紐づくロジックを持っており、画面遷移など複雑になりがちな処理をすっきりさせられる- 全てのUIパーツが独立した ViewController であるため並列での作業が行いやすい- MicroViewController の導入を支援する Mew というライブラリが公開されているWhat MicroViewController?
- // MicroViewController のイメージWhat MicroViewController?
How do we combine them?
How do we combine them? - 人物詳細の構造
How do we combine them? - VC間の通信
How do we combine them? - VC間の通信public protocol Injectable {associatedtype Inputfunc input(_ input: Input)}Mewより抜粋: https://github.com/mercari/Mew
How do we combine them? - VC間の通信eventHandler(output)childVC.input(input)ordequeued()
How do we combine them? - 第一層- 人物詳細の大元のモジュール- ここはまだ標準的な VIPER- この層の役割は以下の三つ- 人物詳細画面のコアとなる人物の基本データを取得する- 人物詳細画面全体にかかわるエラーやイベントの制御- 人物詳細全体の遷移処理を行う> 遷移に必要なデータと遷移のイベントは子や孫から渡ってくる
How do we combine them? - 初期化と依存性の注入
How do we combine them? - 遷移処理
- PersonDetail モジュールの子供達- ここから Router がいなくなる- この三つのモジュールは PersonDetail の Router でインスタンス化される- この層の役割は主に以下の三つ- 子供達の管理- 子供達が自身を描画するのに必要データの取得- 子供から受け取った各種イベントを PersonDetail へ伝播させるHow do we combine them? - 第二層
How do we combine them? - モジュールの組み立て
How do we combine them? - 子の生成
- Summary, OwnBizCard モジュールの子供達- 大家族- ここの ViewController は 自身で依存性の解決を行う- この層の役割は主に以下の二つ- 親から受け取ったデータを表示する(正常系、エラー系共に)- 遷移などのイベントを必要なデータと共に親へ伝えるHow do we combine them? - 第三層
How do we combine them? - 自身の組み立て
- Instantiatable と Router の責務が被ってしまった (依存性の解決)- 更に遷移は大元の Router で行うようにしているため必要性がなくなってしまった- 各 ViewController から遷移させるようにすれば Router の必要性は出てくるがどっちつかずになってしまいそう- 今は以下のルールで運用- 原則 Instantiatable を利用しない- Instantiatable を利用しなくてはならない場合は Router は作成しない- 遷移は Interactable 経由で大元の Router にイベントを伝えて遷移させるHow do we combine them? - なぜ Router は消えたか?
Summary
- メリット- 当初抱えていた課題間は大方解消されていてかなりやりやすかった- Input, Output の Interface が統一されているのでデータの受け渡しについて混乱することはなかった- 面倒な ViewController の管理も Mew が引き受けてくれるのでとても楽Summary - この構成のメリット
- デメリット- Instantiatable と Router の落とし所を見つけるのが少し大変だった- モジュールが多くなるのでファイル数とコード量が凄まじい- 二つのアーキテクチャを正しく理解しないといけない(自分もチームのメンバーも)- 基本的にデータの受け渡しが Input, Output 経由でのフロー同期なので距離が離れた viewcontroller のデータの受け渡しはかなり冗長- Mewのメンテナンスがちょっと心配Summary - この構成のデメリット
Next Step
Next Step - 今後- Instantiatable を拡張して Router を渡せるようにしたい (もう少し VIPER に寄せたい)- ファイル数とボイラープレートの量が尋常じゃないのでもう少し減らしたい- Qiita にそんな記事があったような
Thank you so much!