既存の Xcode プロジェクトを SwiftPM でマルチモジュール化する最初のステップ
本記事では Swift Package Manager(SwiftPM) を用いて、既存の Xcode プロジェクトをマルチモジュール化する最初のステップについて説明します。
「最初のステップ」であるため、SwiftPM で本格的なプロジェクトのマルチモジュール化を行う際に起きるかもしれない問題の解決策などについては説明しません🙏
本記事では以下を理解できるようになることを目指しています。
- SwiftPM を用いたマルチモジュール化の始め方
- SwiftPM を用いたマルチモジュール化の基本的な流れ
以降では非常にシンプルなプロジェクトを参考に、SwiftPM を用いてマルチモジュール化を行う方法について説明します。
マルチモジュール化していくプロジェクト
まずマルチモジュール化していくプロジェクトについて触れておきます。
例としてはシンプルすぎますが、以下のような作りたての SwiftUI プロジェクトを例にします。(マルチモジュール化すべきかどうかは置いておきます)
.
├── Sample
│ ├── Assets.xcassets
│ ├── ContentView.swift
│ ├── Preview Content
│ └── SampleApp.swift
└── Sample.xcodeproj
├── project.pbxproj
├── project.xcworkspace
└── xcuserdata
Xcode 上の Project Navigator で示すと以下のようになっています。
Sample プロジェクトを SwiftPM でマルチモジュール化する
では実際に少しずつマルチモジュール化を行っていきます。
App ディレクトリに全てのファイルをまとめる(任意)
今から説明する手順はやってもやらなくても良いのですが、後の作業が楽になるため実施しておきます。
まず前準備として、ルートディレクトリに存在しているファイルを全て任意のディレクトリにまとめてしまいます。
そのために、ルートディレクトリで以下のコマンドを叩きます。
$ mkdir App
$ ls
App Sample Sample.xcodeproj
$ mv Sample Sample.xcodeproj App
$ ls App
Sample Sample.xcodeproj
これで App
ディレクトリに全てのファイルがまとまりました。
swift package init
を叩く
ルートディレクトリで 次にルートディレクトリで swift package init
を叩きます。
$ ls
App
$ swift package init
Creating library package: Sample
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/Sample/Sample.swift
Creating Tests/
Creating Tests/SampleTests/
Creating Tests/SampleTests/SampleTests.swift
$ ls
App Package.swift README.md Sources Tests
これによりいくつかのファイルやディレクトリが作成されました。
Xcode プロジェクトを整理する
ここまで来たら既存のプロジェクトファイルを開き、整理していきます。
$ open App/Sample.xcodeproj
開くと、一番最初の状態と何も変わっていない状態のプロジェクトが表示されると思います。
ここから先ほど swift package init
で生成されたファイル等を利用しつつ、プロジェクト内を整理します。手順についても軽く先に示しておきます。
- Xcode プロジェクトに
Package.swift
が格納されているディレクトリをドラッグ&ドロップする - Project Navigator で表示されて欲しくないディレクトリに空の
Package.swift
ファイルを追加する
それぞれについて説明していきます。
Package.swift
が格納されているディレクトリをドラッグ&ドロップする
Xcode プロジェクトに SwiftPM を用いたマルチモジュール化を行っていくために、プロジェクトに Package.swift
が格納されているディレクトリ(今回の例では Sample
)をドラッグ&ドロップしてみましょう。
以下がその操作イメージになります。
gif を見ていただけるとわかりますが、Xcode プロジェクトに Package.swift
が入ったディレクトリをドラッグ&ドロップするだけで Xcode プロジェクトが Swift Package として認識してくれていることがわかります。(手元で Xcode 13.0 で試しましたが認識してくれませんでした。Xcode 13.2 では動作することが確認できました。またうまくいかない場合は Xcode を再起動するとうまくいく場合がありました。)
Package.swift
ファイルを追加する
Project Navigator で表示されて欲しくないディレクトリに空の これで Xcode プロジェクトに Swift Package を導入することができましたが、現状のままだと少し問題があります。
Project Navigator 上でディレクトリ構成を見てみると、App
ディレクトリが丸ごと見えてしまっているという問題が発生しています。
この問題を解決するためには Project Navigator 内で見せたくないディレクトリ直下に、空の Package.swift
を追加する必要があります。
空の Package.swift
を該当のディレクトリに追加することによって、そのディレクトリを Xcode に独自の Swift Package であると解釈させることができ、結果として Project Navigator に表示されないようにすることができます。
では早速 Xcode 上の Package 内に表示されてしまっている App
ディレクトリに、空の Package.swift
を追加しましょう。
import PackageDescription
let package = Package(
name: "",
products: [],
dependencies: [],
targets: []
)
そして Xcode を再起動します。
すると、Project Navigator 上から無事 Package 内の App
ディレクトリが消えていることがわかります。
マルチモジュール化する
ここまででマルチモジュール化を行うための準備は全て整いました。
後はマルチモジュール化の作業を実際に行うのみです。
現状 swift package init
によって作成されたディレクトリ構成は以下のようになっています。
一方、Package.swift
は以下のようになっています。
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
// package 名
name: "Sample",
// .library という形式でモジュールを追加していく
products: [
.library(
name: "Sample",
targets: ["Sample"]),
],
// ライブラリなどの依存関係を定義する
dependencies: [
],
// target や test 用の target を追加していく
targets: [
.target(
name: "Sample",
dependencies: []),
.testTarget(
name: "SampleTests",
dependencies: ["Sample"]),
]
)
コメントで軽く説明は書きましたが、説明のために一旦 Sources
, Tests
ディレクトリにあるファイルの全てを削除し、Package.swift
もそれに合わせて修正します。
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Sample",
products: [
],
dependencies: [
],
targets: [
]
)
現在モジュール化すべきファイルはほとんどない Sample プロジェクトですが、モジュール化の一連の流れを説明するために ContentView.swift
をモジュール化してみます。
まず Sources
ディレクトリに AppFeature
ディレクトリを作成します。
Sources
に新たなモジュールが追加されたので、Package.swift
ファイルを以下のように修正します。
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Sample",
platforms: [.iOS(.v13)], // SwiftUI を利用しているため追加
products: [
.library(name: "AppFeature", targets: ["AppFeature"])
],
dependencies: [
],
targets: [
.target(name: "AppFeature")
]
)
次に ContentView.swift
をモジュール化するために、作成した AppFeature
ディレクトリに移動します。
モジュールに追加したファイルに対して、モジュール外からアクセスするためには public な修飾子を付与する必要があるため、ContentView.swift
を以下のように修正します。
import SwiftUI
public struct ContentView: View {
// 外部からインスタンス化するために public initializer も追加
public init() {}
public var body: some View {
Text("Hello, world!")
.padding()
}
}
後はメインアプリターゲットに存在している SampleApp.swift
から AppFeature
モジュールをimport できるようにすれば無事モジュール化が完了します。
メインアプリターゲットから import できるようにするために、まずメインアプリターゲットの「Frameworks, Libraries, and Embedded Content」から「AppFeature」を追加する必要があります。
これでメインアプリターゲット内のコードから AppFeature
を import することができるようになるため、後は必要な部分で import するだけになります。
// これを追加
import AppFeature
import SwiftUI
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
ここまでで無事ビルドが成功すると思います👏
おわりに
最初に説明させて頂いた通り、本記事では SwiftPM を用いたマルチモジュール化の非常に基本的な部分についてのみしか説明していません。
そのため、大きなプロジェクトに速攻適用できるようなものではないですが、SwiftPM を用いたマルチモジュール化の基本的な流れを理解する助けにはなるかなと思っております。
本記事が今後 SwiftPM を用いてマルチモジュール化を行う方の助けになれば幸いです。
Discussion