🌟

【iOS】VIPERのDIに着目して整理する

に公開

https://dev.classmethod.jp/articles/developers-io-2020-viper-architecture/
https://dev.classmethod.jp/articles/developers-io-2020-viper-architecture-comment-res-2/

上記の記事を参考に学習しました。
初学者にわかりやすく、細かく解説されているので、学習しやすかったです!

https://github.com/muranakar/VIPERSample
上記の記事を参考に作成したサンプルコードです。
実際に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の中に、ArticleListViewControllerArticleListRouterUseCaseなどを注入しています。

今回のややこしさが生まれている原因は、ArticleListViewControllerが沢山絡んでくるからだと感じている。

ややこしいポイント

  • ArticleListViewControllerpresenterに、色んな要素を詰め込んだPresenterを代入している。
    →結局はViewControllerが、全部知ってるんかい!ってなりました。だけど、画面を表示するためには、ViewControllerに全部入れて置く必要があるだよなー、と我に返りました。

  • ArticleListRouter(view: articleListViewController)では、RouterarticleListViewControllerが代入されている。
    →Viewを知っていて良いのでしょうか?と思ったが、知っていなければ画面遷移自体を行えない。また、サンプルコードでは、ArticleListRouterを生成する際に、UIViewControllerに準拠していればどんなViewControllerでも代入可能であるため、このあたりは少し問題なのでは?と感じたりもした。


正直上手く説明できているか自信がないため、間違えている部分があればご指摘お願いいたします!!

他にも良い方法があれば、コメントいただけると大変うれしいです。
良かったと思ったら、いいねやTwitterのフォローよろしくお願いいたします!

https://sites.google.com/view/muranakar
個人でアプリを作成しているので、良かったら覗いてみてください!

Discussion