SwiftPackageでマルチモジュール開発

今回はマルチモジュール開発の話。  
最近の**SwiftPackageを使ったマルチモジュール開発**について調査しました。

https://developer.apple.com/documentation/xcode/swift-packages/

# はじめに
開発が大きくなったり複数人で進めていく場合、モジュール分割してないとよくわからない状態でも実装を進められてしまいます。最初はなんとなく動作しているのでうまく実装できているような気になりますが、その状態で進めると**依存関係がどんどん複雑になり、不具合が多くなってしまう原因**になります。
  
モジュールを分割することで、

* ある程度の実装方法を強制できる
* モジュール単位での依存関係が把握できる
* 関心のあるモジュールのみ把握すればいい　等

以上のメリットがあり、開発しやすくなります。  

## プロジェクトでモジュール分割

musicLineでは複数のプロジェクトを作成して、マルチモジュール開発を実現してました。
しかし、プロジェクト設定で依存関係やライブラリの種類を設定することが大変でした。

<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/musicline_developer/20230405/20230405074753.png" width="300px" title="複数のプロジェクト">    
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/musicline_developer/20230405/20230405103238.png" width="250px" title="モジュールの依存関係"> 


<a href="https://play.google.com/store/apps/details?id=jp.gr.java.conf.createapps.musicline">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/musicline_developer/20230519/20230519095723.png" title="3分作曲-musicLine- Androidアプリ"> 
</a>


# SwiftPackageでモジュール分割

以前よりもモジュール分割を細分化し、SwiftPackageでモジュール分割した結果です。


<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/musicline_developer/20230405/20230405075237.png" width="300px" title="パッケージマネージャーの使用">    
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/musicline_developer/20230405/20230405103850.png" title="モジュールの依存関係"> 

SwiftPackageを使用することで、フォルダ階層を分けるだけでモジュールを分割できました。
以前までのプロジェクト設定等の煩わしい作業がなくなり、スッキリしました。

<br>
ソースコード等の詳細はこちら

https://musicline-developer.hatenablog.com/entry/2023/04/05/154526
 
## ポイント
### 1.　フォルダを階層化したい

パッケージ設定で`.target`によりターゲットを作成すると、デフォルトは`Sources`の直下のフォルダを参照しますが、`path`ラベルよりフォルダのパスを設定できます。

例えば、`CoreModel`の場合
```swift
.target(
    name: "CoreModel",
    path: "Sources/01.Core/CoreModel")
```

`Package.swift`ファイルから相対パスで指定します。  


### 2.　モジュールにリソースを含める

モジュールにリソースを含める場合、`resources`ラベルでリソースリストを指定します。
リソースは以下の2種類あり、モジュールフォルダからの相対パスを指定して作成します。

* `.process`  
フォルダ階層を除去して、Bundle直下にファイルをコピーする
* `.copy`  
フォルダ階層そのままで、フォルダごとコピーする

https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package

例えば、`Common`モジュール直下の`data`フォルダをリソースに追加したい場合、以下のように設定します。
```swift
.testTarget(
    name: "CommonTests",
    dependencies: ["Common"],
    resources: [.process("data")]
)
```

また、プロジェクトからモジュールに移行するときに、参照するBundleに注意です。  

```swift
// プロジェクトの場合
Bundle(for: type(of: self))

// モジュールの場合
Bundle.module
```

### 3.　なぜかAssetsからリソースを取得できない 
Assetsでリソースを登録しているのに、SwiftUIのColorやImageがリソースから取得できない状態になっていました。
原因は異なるBundleを参照しており、Assets内のリソースに指定したリソースが無いため、取得できていませんでした。特にどこのBundleを参照しているかを意識していなかったのですが、**デフォルトではメイン（現在実行中のコードを含むBundle）を参照するようで、モジュールのBundleを参照することで解決**しました。

```swift
// プロジェクトの場合
Color("color_name")

// モジュールの場合
Color("color_name", bundle: .module)
```

https://developer.apple.com/documentation/foundation/bundle#:~:text=The%20main%20bundle%20represents%20the%20bundle%20directory%20that%20contains%20the%20currently%20executing%20code

### 4.　リリース時にPreview Contentフォルダを除外したい
プロジェクトみたいに`Development Assets`に登録して、リリース時にフォルダを除外するような挙動はできないようで困りました。  
`exclude`ラベルで一部のフォルダを除外できるようですが、デバッグ時には欲しいファイルなのでこれは使えず。。  
最終的には**ターゲットを新しく作成して、ライブラリを別にすることでこの問題を回避**しました。でもこの方法だと、スキームが無駄に多くなってしまうことが気になるところです。  

Previewする時のクラスはファイルに書き出さずに、Viewファイルの中で`PreviewProvider`クラスと一緒に`#if DEBUG`で定義した方がスマートかもしれません。  
いい方法があればコメントください。。