SwiftPMマイクロパッケージ分割 - abceedのケーススタディ

[Globeeアドベントカレンダー](https://qiita.com/advent-calendar/2023/globee)最終日です。
最後はSwift Package Managerを使ったマイクロパッケージ分割の話にしました。

基本的にはこちらの記事の **「And you !!」** のケーススタディだと思っていただけるとよさそうです。
2年以上前の資料なのにいまだに参考になるのですごいです。バイブルですね。
https://speakerdeck.com/d_date/swift-package-centered-project-build-and-practice?slide=100

それではご笑覧ください。

# 経緯
## SwiftUIのプレビューが重い

UIKit全盛期に始まったabceedは、長いことモノリシックなアプリターゲット1つに全てのコードを詰め込む方式でやっていました。
ところがSwiftUIを導入したことで、ビルドが遅いことが課題になりました。
コードベースが大きくなっていたため差分ビルドも効きづらく、1行変更すると1分くらい待たされるということもザラでした。
Previewを活用した素早いイテレーティブな開発のメリットを享受するには、対策を考える必要があります。

## UIComponents.framework

第1段階として、dynamic frameworkのターゲットを導入しました。
dynamic frameworkの扱いはCarthageやCocoaPodsで慣れていましたので導入しやすく、またSwift Packageの知見も今よりなかった頃なので、とりあえず新しく作るUIだけでも開発効率を上げたいという目的でした。
当面の目的は達成できたものの、当然ながらその後開発を続けるとUIComponents自体が肥大化して行き詰まる未来が見えてきました。
ビルドが遅くて困るという状況までは到達してませんでしたが、だんだんとファイルの管理もしづらくなっていき、最適な状態ではありませんでした。

# Swift Package分割の開始

そこでPackage分割のプロジェクトを開始しました。
どうしてこの手法を取ったのかの解説は上記のバイブルに譲りますが、まずは以下のようなベースとなるパッケージが生まれました。
それぞれの依存ライブラリも一緒に記載します。

- **AbceedUICommon**: 共通利用するUI部品
  - AbceedCore
  - AbceedCommon
  - SDWebImage
  - SDWebImageSwiftUI
- **AbceedCore**: 共通利用するビジネスロジック
  - AbceedCommon
  - APIKit
  - ObjectMapper
  - KeychainAccess
  - Atomics (Swift Atomics)
- **AbceedCommon**: 汎用的なショートハンド関数など
  - 依存ライブラリなし

```mermaid
graph TD;
    AbceedUICommon --> SDWebImage;
    AbceedUICommon --> SDWebImageSwiftUI;
    AbceedUICommon --> AbceedCore;
    AbceedUICommon --> AbceedCommon;

    AbceedCore --> APIKit;
    AbceedCore --> ObjectMapper;
    AbceedCore --> KeychainAccess;
    AbceedCore --> AbceedCommon;

    classDef package fill:#f9f,stroke:#333,stroke-width:4px;
    classDef library fill:#ccf,stroke:#333,stroke-width:2px;
    class AbceedUICommon,AbceedCore,AbceedCommon package;
    class SDWebImage,SDWebImageSwiftUI,APIKit,ObjectMapper,KeychainAccess,SwiftAtomics library;
```

# ドメインパッケージ

さらに上記のようなコアライブラリに依存したドメインごとのパッケージを作成していきました。
複雑すぎるのでかなり省略していますが、以下のような依存関係になりました。

```mermaid
graph TD;

    AbceedCore --> APIKit;
    AbceedCore --> KeychainAccess;
    AbceedCore --> AbceedCommon;

    AbceedUICommonResource --> AbceedCommon;

    AbceedUICommon --> AbceedCommon;
    AbceedUICommon --> AbceedCore;
    AbceedUICommon --> AbceedUICommonResource;
    AbceedUICommon --> SDWebImage;

    OnePhrase --> Umbrella;
    OnePhrase --> AbceedCore;
    OnePhrase --> AbceedCommon;
    OnePhrase --> AbceedUICommon;

    Checkin --> AbceedUICommon;

    Chat --> AbceedUICommon;

    OptionSetting --> AbceedUICommon;

    MoviePlusPhrase --> Umbrella;
    MoviePlusPhrase --> AbceedCore;
    MoviePlusPhrase --> AbceedCommon;
    MoviePlusPhrase --> AbceedUICommon;

    VideoScenes --> Umbrella;
    VideoScenes --> AbceedCore;
    VideoScenes --> AbceedCommon;
    VideoScenes --> AbceedUICommon;

    Umbrella --> APIKit;
    Umbrella --> KeychainAccess;
    Umbrella --> RxSwift;
    Umbrella --> SDWebImage;
```

正直知見が足りないので不要な複雑性を孕んでいるかもしれません。
今回図示したことで改めてそれが浮き彫りになった気がするので、今後見直していきたいです。

## ハマりどころ
マルチモジュールに関してはビルドを通すのはやはりそれなりに大変で、慣れて仕舞えばいいのですがコツを得るまでには結構頭を悩ませると思います。

また、同じパッケージを別々にインポートしていたことによるシンボル重複ランタイムクラッシュにも遭遇したことがありました。
これは`UIComponents.framework`が中途半端に残っていたのが原因かもしれないので、完全に移行すれば大丈夫かもしれません。
実はちょうど今`UIComponents.framework`の削除作業を終えてQAフローに載せるところなので、QAの結果を待ちたいと思います。

# 進捗

最後に、現状どれくらいパッケージ化できているかの数値を載せておきます。

| target | Swiftコード行数 |
|---|--:|
| メインのターゲット | 215,504 |
| Package | 28,394 |

現状で13%ですね。
いにしえのxibファイルもまだまだ残っているので、厳密にはもっと低いと思います。
最終的には99%以上にするのが目標かなと思いますが、既にパッケージを導入されているみなさんは進捗どのくらいでしょうか？
ぜひ計測[^1]してシェアしてください。

[^1]:計測にはこちらを使いました。 https://github.com/AlDanial/cloc

# 余談: ウィジェット開発

ところで、今月ついにabceedにウィジェットを追加しました。

https://note.com/globee/n/nf18a9a71703b

このウィジェットターゲットの依存関係はこのようになっています。
![Frameworkd and Libraries](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35008/1f987596-9011-10f4-e48e-75ced804b309.png)

`OnePhrase` 以外の不要な機能ドメインに依存しなくて良いので、気持ちよく開発を進めることができました。
iOS 17以上にさせてもらったので、今年のWWDCで発表された新機能をフルに使うことができました。
つまづいたところもありましたが、マイクロパッケージ分割のおかげでPreviewが爆速で本当に助かりました。

こっそりクリスマスバージョンも追加してあるので、興味ある方はぜひ使ってみてください。
![abceedウィジェット クリスマスバージョン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35008/72acbfda-cb2d-8c74-efb4-8eca505696e2.jpeg)

# まとめ

うまくクリスマスネタで締めることができました。
マイクロパッケージ分割の事例を紹介しましたが、いかがだったでしょうか。
パッケージにすることでソースコードがpbxprojに登録されなくなりますので、これだけでも利点があると思います。
ただそれよりも、コードの適切なまとまりや理想的な依存関係を考えながら開発するようになるので、コードベースの見通しがよくなっていくように感じます。
人数が多いと調整が大変かもしれませんが、みなさんもぜひ現場のプロジェクトに取り入れてみてください。

[Globeeアドベントカレンダー](https://qiita.com/advent-calendar/2023/globee)は他にも面白い記事がたくさんありましたので、ぜひ見に行ってみてください。
ありがとうございました。
それではみなさん良いクリスマス＆年の瀬を！

# ♦︎ 採用情報
現在株式会社Globeeではエンジニア採用を積極的に行っています。
まずはカジュアル面談にて弊社の「教育」にかける思いや「ものづくり」の考え方についてお話できればと思いますので、皆様お気軽にご応募ください。
https://globee.notion.site/Globee-d184eefc3561480292481299c64b04bc