iOSアプリをリニューアルした時のアーキテクチャ

最近、iOSアプリをリニューアルしたのでその時に工夫した内容を書いておこうと思う

前提

  • 仕様書はない
  • iOSアプリでObjective-Cで書かれている
  • Swiftに全てのコードを書き換える。SwiftUIは使わない。
  • 複数アプリが一つのソースコードでプロジェクトで管理されている

最後の部分はあまりないがほとんど同じアプリがコピペで別のリポジトリで管理されるよりはマシな状況である。二つのBuild SettingsでTarget Membershipで分けるのは、読み取りづらいものになっていた。

解決したい問題

  • 典型的なBaseViewController 問題がある
  • FatViewControllerでもある

巨大なViewControllerにベタが記されていて、何が行われているか見通しが悪くなっていた。

解決方法

ViewControllerから処理を剥がすためにフレームワークに分けて、それぞれの役割をはっきりとさせたかった。単純に分けただけでは役割とは異なる箇所にコードを書いてしまうことができるため、フレームワークごとに依存するライブラリを限定して、役割に合っていないフレームワークで書くことを難しくした。さらに、フレームワークごとの依存関係を限定することで、間違った使い方をできないようにした。

3つのフレームワークとアプリに分ける

DomainとInfrastructure、PresenterのフレームワークとそれをまとめるApplicationのアプリに分けることにした。

DomainとInfrastructure、Presenterはそれぞれの役割に集中できるようにフレームワークに分離して、依存する対象をはっきりさせるよにした。たとえば、InfrastructureからPresenterは依存してすることはプロジェクトの設定で絶対にできないようになっている。

Applicationは、複数のアプリの差分(アプリのアイコン等)を持っており、コードの量は最小限になるようにした。

Domain

ドメイン駆動設計で言うところのDomainのこと。ビジネスロジックを可能な限りModelに書くようにした。Respositoryはprotocolになっている。Respositoryを経由してModelを取得するようにしている。 DomainRegistoryは、全てのRepositoryのオブジェクトを持っているか生成するのが役割です。 DomainはRxSwiftのみに依存している。SwiftUIを採用していればRxSwiftに依存せずに別の設計になっていたかもしれない。

import RxSwift

public struct XxxModel {
    let value: String
}

public protocol XxxRepository {
    func load() -> Obserable<Model>
}

public protocol DomainRegistory {
    var xxxRepository: XxxRepository { get }
}

Presenter

Presenterは、主にDomainとUIKitに依存している。
DomainRegistoryが持っているRepositoryを使ってDBや通信の操作するようにしました。これによって、Presenterは本番環境から取得されているか、ステージング環境か、モックかを気にすることなくUIに集中できるようにしました。

Infrastructure

Infrastructureは、主にDomainとDB、API通信クライアントに依存している。
RepositoryImpがRepositoryの実装になっている。通信やキャッシュ、Entityへの変換を実装している。

Application

ApplicationはDomainRegistoryの実装であるDomainRegistoryImpを作成と、環境変数の注入が主な役割である。各アプリ特有の設定や本番とステージング環境の切り替えを行なって、InfrastructureやPresenter現在の実行環境がどうなっているかを気にしなくていいようする役割である。

実際どうだったか

全体の見通しは非常に良く、どこになくを書くべきかが明確なので、失敗しづらい構造になっていました。
今回のアプリはAPI通信で取得したデータをキャッシュして表示する画面がほとんどだったので、ほとんどがアーキテクチャに合わせて構築するだけの部分が多かったため、大きく問題になることはありませんでした。ユーザーの入力が複雑になった場合にRepositoryが複雑になるかはわからなかった。

やらなかったこと

  • 流行りDI コンテナ
    • ここには書かなかったが、RxSwift等を導入したため一同導入するものを減らしたかった
  • SwiftUI
    • iOS12をサポートすると決めていたため