iOS/Androidのディレクトリ配置のベストプラクティス
Xcodeを用いたiOS(Swift)で、MVVMモデルの例で紹介します。
ナビゲーターエリアのファイルツリーのベストプラクティス
はじめに
Xcodeを開いて左側にあるナビゲーターエリア。
ここに表示されるファイルツリーのディレクトリとファイルの構成を最適にしたいですよね。
新規開発のときには特に、その後の基準となるため重要です。
既存プロジェクトやサンプルを見たりして、結果的に収まりが良くなっていたりすると思いますが、改めて、学習サイトを通じてスタンダードと思える形を紹介します。
尚、Xcodeを用いたiOS(Swift)で、MVVMモデルの例で説明しますが、Androidにも通ずるところは多くあると思います。
ディレクトリ構成
MVVMモデルとして大枠のディレクトリはこうなります。
- Model
- View
- Controller
- ViewModel
- API
- Utils
Modelディレクトリ
モデルを配置します。
データの格納庫となる構造体が主に当てはまります。
ビューとの関係付けは、ビューモデルが担当します。
Viewディレクトリ
UI表示に関わるビューを配置します。
画面に関わる部分は、ViewControllerとして、コントローラーが担当しますが、ここでは、パーツ単位でのビュークラスを管理します。
上の例では、UITableViewCellのセルやヘッダー、UITextFieldなどの標準UIパーツをカスタマイズしたUIなどを管理しています。
尚、プロフィール画面に関わるものはProfileディレクトリにグループ化しています。
画面単位でグルーピングすると分かりやすくなるでしょう。
Controllerディレクトリ
コントローラー(ViewController)を配置します。
上の例では、メイン画面、プロフィール画面、ログイン画面、登録画面とあり、認証関連のログイン画面と登録画面は、Authenticationというディレクトリでグループ化しています。
ViewModelディレクトリ
ビューとモデルの橋渡し的存在のビューモデルを配置します。
上の例では、認証関連のログイン画面と登録画面の両方におけるビューモデルで、AuthenticationViewModelとして管理しています。
一つのViewModelファイルに統一している理由は、共通する持ち物があるためで、内部では、AuthenticationViewModelプロトコルを定義し、LoginViewModelとRegistrationViewModelに分けて、それぞれがAuthenticationViewModelを継承する形にしています。
それぞれのViewModelが肥大化するようであれば、ファイルも分割した方が良いかもしれません。
APIディレクトリ
APIに関するファイルを配置します。
シンプルなのは、クエリとレスポンスを受けるためのクロージャーを渡すstaticメソッドで、リクエストと通信を経てのレスポンス(エラー含め)を返す振る舞いを持ったイメージですね。
上の例では、認証に関わるAPIとユーザー情報を取得するAPIをサービスをそれぞれAuthenticationService、UserServiceとして作成しています。
Utilsディレクトリ
ユーティリティファイルを配置します。
機能拡張(extension)や共通的に使われるものを中心に定義します。
他のプロジェクトでも使い回せることをイメージすると良いでしょう。
ここでは、以下のファイルを定義しています。
- Extensions.swift
- Constants.swift
Extensions.swiftでは、既存クラスに機能拡張させる記述をまとめています。
例えば以下のような拡張です。
private let formatter: DateFormatter = {
let formatter: DateFormatter = DateFormatter()
formatter.timeZone = NSTimeZone.system
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.calendar = Calendar(identifier: .gregorian)
return formatter
}()
public extension Date {
// Date->String
func string(format: String = "yyyy-MM-dd'T'HH:mm:ssZ") -> String {
formatter.dateFormat = format
return formatter.string(from: self)
}
// String->Date
init?(dateString: String, dateFormat: String = "yyyy-MM-dd'T'HH:mm:ssZ") {
formatter.dateFormat = dateFormat
guard let date = formatter.date(from: dateString) else { return nil }
self = date
}
}
Date(dateString: "2021-02-10T10:10:00Z") // Date
date.string(format: "yyyy/MM/dd") // 2021/02/10
Constants.swiftでは、そのプロジェクトで使用する定数などを定義します。
従来の#define定義とか、他言語では、public static finalとか、constを使って定義していたものですね。