🛠️
ローカルPackageを持つXcode Projectのディレクトリ構成
Swift Package Managerが登場してからSwift Packageを使ったマルチモジュール構成が流行っていますが、そんなローカルPackageを持つXcode Projectのディレクトリ構成はどのようにするのが一番いいのか考えましょう。
パターン1
Xcode ProjectのあるディレクトリとローカルPackageを並列に配置するパターン
RepositoryRoot
├── AppProject
│ ├── App
│ ├── App.xcodeproj
│ └── AppUITests
└── LocalPackage
├── Package.swift
├── Plugins
├── Sources
└── Tests
パターン2
ルートをXcode Projectのあるディレクトリにし、その中にローカルPackageを配置するパターン
RepositoryRoot(≒AppProject)
├── App
├── App.xcodeproj
├── AppUITests
└── LocalPackage
├── Package.swift
├── Plugins
├── Sources
└── Tests
パターン3
ルートをローカルPackageのディレクトリにし、その中にXcode Projectのあるディレクトリを配置するパターン
RepositoryRoot(≒LocalPackage)
├── AppProject
│ ├── App
│ ├── App.xcodeproj
│ ├── AppUITests
│ └── Package.swift
├── Package.swift
├── Plugins
├── Sources
└── Tests
考察
- パターン1はアプリ本体とローカルパッケージの責務分離をディレクトリで示せるので、開発者の理解に優しい
- Xcode ProjectのBuild Phases > Run Scriptのワークスペースを基準に考えるとパターン2が最適解
- ワークスペース=ルートディレクトリになるので、相対パスで親ディレクトリに遡ってパスを辿る必要が発生しない
- SwiftLintやSwiftFormatなどのツールを適用する際に、ルートディレクトリ配下の
.swift
ファイルを対象にするだけで全てカバーできる(.swiftlint.yml
や.swiftformat
などのコンフィグファイルの配置場所もルートディレクトリ一箇所にまとまる)
-
パターン3の場合、Xcode ProjectのあるディレクトリをローカルPackageの管轄外として扱うために、ダミーの
Package.swift
ファイルを配置しないといけない
Discussion
僕は完全に一緒ではないですが、パターン1のような形式でやっています
というのもRelease/Debug/Mockごとに
xcodeproj
を作って環境変数やDIする値を管理しておりルートで
xcworkspace
を使っているので、その下にプロジェクトとPackageが並ぶ形になっています。参考: https://speakerdeck.com/d_date/swift-package-centered-project-build-and-practice?slide=91
は〜なるほど、弊社チームにもxcworkspace使っているプロジェクトあります。
ただkntkさんのケースの場合xcconfigファイルを使う方法で事足りませんか?
ReleaseUITestsとDebugUITestsで異なることをやっていたりするんでしょうか?
環境変数などについてはこの例で済むと思います。
自分は各xcodeprojでDIコンテナを定義することで
ReleaseとMockでコンパイルするソースを完全に分離するという設計にしていて
(Releaseでは実際のRepositoryのみに依存し、MockではMockのRepositoryにのみ依存する、これによってコンパイル時間の削減・アプリバイナリサイズの削減などができます)
依存するフレームワークとコンパイルする.swiftファイルがxcodeprojごとに変わるのでxconfigでは対応できないという認識でいます。
Release/Env.swift
Mock/Env.swift
Release用とMock用の実装本体はLocalPackageの中にあって、xcodeprojでそれを選択的にDIコンテナで使うというのであっていますか?
そうです。
RepositoryProtocol: protocol
Repository: Release用の実装本体
RepositoryMock: Mock用の実装本体
のようにLocalPacakge内にtarget分割しています。