目次
はじめに
この記事はiOSアプリ開発の技術的なアドバイザーを業務で行ったときにしたアドバイスをぼやかし気味に振り返っておきます。
別業務のレガシーコード改善屋として自分の手を動かしている内容は下記に別の記事にしています。
本題
マルチモジュール
新規のiOSアプリ開発でも世の中的にマルチモジュール化をしている動きになっていて、そのなかでアドバイスしたことを書いておきます。
画面単位のマルチモジュール化って本当に長所が短所を上回りますかね?
- 例えば
- お店と商品を別モジュールにしたとする
- お店の詳細画面に商品を出したいときもあるし、商品の詳細画面からお店をたどりたい時もある
- モジュール同士が循環する
- 技術的に循環を回避のやりようはある
- 本来モジュール化で解決したかったことに対して、モジュール化することで別の解決しないといけないこと複雑さを生んでいる
- 技術的に循環を回避のやりようはある
- モジュール同士が循環する
- お店の詳細画面に商品を出したいときもあるし、商品の詳細画面からお店をたどりたい時もある
- お店と商品を別モジュールにしたとする
isowordsの構成に学ぶホストアプリケーションとその他の分割
- ホストアプリケーションのターゲットの構成は
- SwiftUI.Appだけにして、それ以下のコードは別のモジュールにしてPacakge.swiftで管理すると物理ファイルがそのままなので楽
別にモジュール化しないといけないとも思いませんが、やるんだったら最低限ホストアプリケーションのモジュールにはSwiftUI.App一つ、その他最低限のものをPackage.swiftで別モジュールにしておき、それを細分化していけばいいと思います。
例
例として別モジュールはModules
という名前とかにしてAppFeture
ディレクトリの下に分類しておく。1ホストアプリケーションのターゲットとAppFeature
、ValueObjects
、SideEffect
、UICommon
ターゲットにわける。
import PackageDescription
let package = Package(
name: "Modules",
platforms: [.iOS("17.0")],
products: [
.library(
name: "AppFeature",
targets: ["AppFeature"]
),
],
dependencies: [ ... ],
targets: [
.target(
name: "AppFeature",
dependencies: [ ... ],
path: "Sources/Features/AppFeature"
),
.target(
name: "ValueObjects",
path: "Sources/ValueObjects"
),
.target(
name: "SideEffect",
dependencies: [ ... ],
path: "Sources/Libraries/SideEffect"
),
.target(
name: "UICommon",
path: "Sources/UICommon"
),
/* 残りはテストコードのターゲット */
],
...
自分が分割したいのは機能と値と副作用とViewの共通パーツが最低限で、それ以上に分割することもあるけど最低限そっからでもいいかと思ってます。
あとで細かくするのはできるしそんなに難しくない
もちろん「そもそも分割するのは後から困らないようにするためで最初っから細かく画面ごとにモジュールを分割してることであとで楽になる」という意見もあるし、できるならそうしてもいいかもしれないが、画面ごとにモジュール分割することは先述の通りお勧めしないし、別の課題を生んでスケジュールを遅らせるきっかけになることはできるだけ避けたい。アプリがヒットしてから人を増やしてモジュールを細かく分ける作業に専念してもらうことは経験上簡単です。ビルドができるようにしてテストコードをパスするようにするのに近いから。
個別機能を外注先に作ってもらいたいなら機能ごとに分けることは良さそう
ただ、個別の機能を外注先に作ってもらうときに外注先がある程度自由に機能ごとにモジュールを分割したいからあらかじめ細かくするのは良い作戦でしょうね。なんでかって言うと具体的には外注先がString型にDate型とかCalendar型を使うextensionを生やされても、それってそのモジュールでしか汚染が広がらないから気しないようにするとかね。
結論として、モジュールの構成に目的を持ってやるというのが前提なら、あなたがたの組織によって作るモジュール構成は変わるよ、と思います。なのでモジュールを「水平方向」「垂直方向」のどちらに分割すべきかという疑問自体が意味がなく、解決したい問題を解決する方向に分割するのが良いはずです。
XcodeのDependency機能よりPackage.swiftの機能に統一した方がいい
前述のisowords的な構成にやる前提ですが
- Xcode内にもPackage.swiftのように依存を指定できる機能がある
- これPackage.swiftと両方同じ記述をしたらまぎらわしいからPacakge.swift統一したほうがいいよ
- 例外
- Firebase ios SDKのライブラリはXcodeの機能から導入しホストターゲットから依存を設定していないと、ホストターゲットのスキーマの時SwiftUIプレビューがライブラリが見つからなくてビルドできない
- ただこれは各モジュールをターゲットで切り替えてSwiftUIプレビューはできるのでそれでいいならそれでいい
- Firebase ios SDKのライブラリはXcodeの機能から導入しホストターゲットから依存を設定していないと、ホストターゲットのスキーマの時SwiftUIプレビューがライブラリが見つからなくてビルドできない
- 例外
- これPackage.swiftと両方同じ記述をしたらまぎらわしいからPacakge.swift統一したほうがいいよ
TCA関連
Action Boundaries
TCA使う上で何よりも役立つのがTCA Action BoundariesのTips。
https://www.merowing.info/boundries-in-tca/
enumをView, Delegate, Internalに分割するのが良いです。
もちろんこれらとは別にalertとか別の分類が追加されてもいい。Action Boundaries さえできてればあとはおまけですよ。