【iOS】VIPERのDIに着目して整理する
上記の記事を参考に学習しました。
初学者にわかりやすく、細かく解説されているので、学習しやすかったです!
上記の記事を参考に作成したサンプルコードです。
実際にxcode上で、JumptoDefinition callers..などを用いて、どのような構造になっているのかを、学習するとより理解を深めることができるので、おすすめです。
今回のサンプルコードでは、
Entity、UseCase、Router、Presenter、View、Interactor(今回は使っていない)
の関係が、依存性逆転の原則(Dependency inversion principle) に基づいて、成り立っている。
その部分を理解するためには、実際の挙動時の処理の順番を追い、どのような関係が成り立っているのかを深掘りする必要がある。一部コードを省略しながら、DIの部分だけをピックアップして振り返っていく。
前提として、

こちらの図を頭の中でイメージしながら、コードを読むとより理解し易いです。
実際にDIしているところを見に行きましょう
SceneDelegateで、呼び出しが始まっていました
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else {
return
}
window = UIWindow(windowScene: windowScene)
window?.makeKeyAndVisible()
guard let articleListViewController = UIStoryboard(name: "ArticleList", bundle: nil).instantiateInitialViewController() as? ArticleListViewController else {
fatalError()
}
articleListViewController.presenter = ArticleListPresenter(
view: articleListViewController,
inject: ArticleListPresenter.Dependency(
router: ArticleListRouter(view: articleListViewController),
getArticlesArrayUseCase: UseCase(GetArticlesArrayUseCase()))
)
let navigation = UINavigationController(rootViewController: articleListViewController)
window?.rootViewController = navigation
}
}
これでは、まだ分かりづらいですね。
guard let articleListViewController = UIStoryboard(name: "ArticleList", bundle: nil).instantiateInitialViewController() as? ArticleListViewController else {
fatalError()
}
articleListViewController.presenter = ArticleListPresenter(
view: articleListViewController,
inject: ArticleListPresenter.Dependency(
router: ArticleListRouter(view: articleListViewController),
getArticlesArrayUseCase: UseCase(GetArticlesArrayUseCase()))
)
これで少しはわかりやすくなったでしょうか。
この中で、どういった処理が行われているのだろうか。
まず最初の部分の
guard let articleListViewController = UIStoryboard(name: "ArticleList", bundle: nil).instantiateInitialViewController() as? ArticleListViewController else {
fatalError()
}
このコードは、ArticleListという名前のStoryboardから初期ViewControllerをインスタンス化して、それをArticleListViewControllerのインスタンスとして取得しようとしています。
次に
articleListViewController.presenter = ArticleListPresenter(
view: articleListViewController,
inject: ArticleListPresenter.Dependency(
router: ArticleListRouter(view: articleListViewController),
getArticlesArrayUseCase: UseCase(GetArticlesArrayUseCase()))
)
この部分がややこしいです。
articleListViewControllerは、presenter を保持しています。
その保持しているpresenterに対して、ArticleListPresenterを生成して、代入します。
どんなArticleListPresenterを代入しているかというと、
ArticleListPresenter(
view: articleListViewController,
inject: ArticleListPresenter.Dependency(
router: ArticleListRouter(view: articleListViewController),
getArticlesArrayUseCase: UseCase(GetArticlesArrayUseCase()))
)
ArticleListPresenterを生成する際の引数として、
- 引数の
viewに対して、最初に作成したarticleListViewControllerを設定 - 引数の
injectに対して、ArticleListPresenter.Dependencyを設定
上記を行って生成している。
ArticleListPresenter.Dependencyはどのように生成しているかというと、
ArticleListPresenter.Dependency(
router: ArticleListRouter(view: articleListViewController),
getArticlesArrayUseCase: UseCase(GetArticlesArrayUseCase())
)
- 引数の
routerに対して、最初に作成したArticleListRouterを設定 - 引数の
getArticlesArrayUseCaseに対して、UseCaseを設定
ArticleListRouter(view: articleListViewController),
-
ArticleListRouterの生成時に、articleListViewControllerを設定
UseCase(GetArticlesArrayUseCase())
-
UseCaseの生成時に、GetArticlesArrayUseCase()を設定
→実際にAPIを用いて記事を取得したり、ダミーデータを返すだけに変更したりするために、引数が設定できるように実装している。
コードを用いた説明は以上になります。
ここから先は、少しまとめながら、自分でも理解できていない部分を振り返っていく。

上図の関係で成り立っている。
Presenterは、旗振り役であるため、各要素の抽象化された部分を知っている必要があります。
そのためにPresenterの中に、ArticleListViewControllerやArticleListRouter、UseCaseなどを注入しています。
今回のややこしさが生まれている原因は、ArticleListViewControllerが沢山絡んでくるからだと感じている。
ややこしいポイント
-
ArticleListViewControllerのpresenterに、色んな要素を詰め込んだPresenterを代入している。
→結局はViewControllerが、全部知ってるんかい!ってなりました。だけど、画面を表示するためには、ViewControllerに全部入れて置く必要があるだよなー、と我に返りました。 -
ArticleListRouter(view: articleListViewController)では、RouterにarticleListViewControllerが代入されている。
→Viewを知っていて良いのでしょうか?と思ったが、知っていなければ画面遷移自体を行えない。また、サンプルコードでは、ArticleListRouterを生成する際に、UIViewControllerに準拠していればどんなViewControllerでも代入可能であるため、このあたりは少し問題なのでは?と感じたりもした。
正直上手く説明できているか自信がないため、間違えている部分があればご指摘お願いいたします!!
他にも良い方法があれば、コメントいただけると大変うれしいです。
良かったと思ったら、いいねやTwitterのフォローよろしくお願いいたします!
個人でアプリを作成しているので、良かったら覗いてみてください!
Discussion