こんにちは。FLINTERS10周年記念ブログリレーの53日目の担当をします、佐野です。
GANMA!のiOSアプリ開発に長く携わり色々なコードの変遷を見てきた中でも、現在チームでも推し進めているマルチモジュール化に至るまでに多くの変化があったので話していこうと思います。
目次
- Embeded Frameworkの導入(4年前〜2年前)
- Swift Package Managerの導入(2年前)
- マルチモジュール化の加速(1年前~現在)
- まとめ
Embeded Frameworkの導入(4年前〜2年前)
GANMA!では4年ほど前はクリーンアーキテクチャが導入されておりましたが、全てメインターゲットに置かれておりました。これを各レイヤーごとに分離していくためにEmbeded Frameworkが導入され、ApplicationLayer, DomainLayer, DataLayerなど5つのframeworkが作られました。
これらのEmbeded Frameworkに対してメインターゲットにあるファイルを適切に移動していく作業を機能開発と合わせながら少しずつ行ってきました。これによりレイヤーごとにビルドが行えるようになったので開発効率が少しよくなりました。依存が多く一筋縄では行かないようなファイルは依然としてメインに残ったままですが、よく使われるクラスを優先的に多くのファイルを移動させることができました。
一方で、Embeded Frameworkへの分離を進めていく中で、各レイヤーにファイルを移動したりファイルの作成削除を行う時にxcodeprojファイルのコンフリクトが頻発し作業効率が悪いという課題感はありました。
Swift Package Managerの導入(2年前)
上記のような課題感からやSwift Package Manager(以降、SwiftPM)への導入を検討していましたが、SwiftPMに対応しているSDKが少なくSwift Package側に移動できないことを懸念して見送る期間が続きました。 2年ほど前になるとSwiftPMに対応したSDKも増えたので導入を決断しました。 その際の、SwiftPMの導入でまずやったこととしては
Carthageを捨てて、CocoaPodsとSwiftPM体制に
Embeded FramewworkをそのままSwiftPMに移動
を主に行いました。
このタイミングでのSwiftPMの導入の詳細ややり方に関してはこちら
GANMA!にSwift PMを 導入した話 - Speaker Deck
のスライドにまとまっているので参照してみてください。
SwiftPMを導入して得られた効果は色々なところにあるので説明していきます。
コンフリクトしない!
SwiftPackageにあるファイルはxcodeprojファイルのようなxmlファイルにファイル情報を保持したりはしないので、SwiftPackage側のモジュールにファイルを移動してもコンフリクトがほぼ発生しませんでした。これにより機能開発のついでに移動する作業がとてもフットワーク軽くできるようになったと感じます。
ファイルを移動していくとimport分を書いていく作業が面倒ではありますが
@_exported import SomeModule
と書いてあげると、これを記述したモジュール内ではimport文を書かなくてよいので、無闇に使用することには注意ですが、有効です。
モジュールが作りやすい!
Embeded Frameworkを作成するとxcodeprojファイルにも情報が書き込まれるため、コンフリクトを恐れてなかなかモジュールを作成しづらい状況でしたが、SwiftPMでは上述の通りSwiftPackage側のファイル情報は保持されずPackage.swiftにモジュールやフレームワークの定義を書くだけでOKです。これならコンフリクトしたとしても大した情報ではないのですぐ直すことができます。
.target( name: "SomeModule", dependencies: [ "OtherModule" ] )
Featureモジュール
2年ほど前は、新画面はSwiftUIで作成し始めていた一方で、Viewに関連するファイルが全てメインターゲットにあったためプレビューが安定しないという課題がありました。The Composable Architecutureの導入も同時期に検討してた影響もあり、GANMA!チームはクリーンアーキテクチャのレイヤーのモジュールに各ファイルを移動していくというよりはFeatureモジュールを作り、マルチモジュール化を進めていくことに興味を持ってきました。
そこで、新画面を作る際はその機能のFeatureモジュールを作成し、ここに関連するファイルを置いていくようにしました。ただメインターゲットにたくさんのxibやstoryboardがあるため画像や色などのAssetをメインから剥がすことは困難だったため以下のようなSwiftGenで自動生成されたアクセサのみAssetモジュールに分離しFeatureモジュールからもAssetにアクセスできるようにしました。
public enum Asset { public enum Imaegs: String { case someImage = "someImage" } } extension UIImage { public convenience init!(asset: Asset.Images) { self.init(asset.rawValue: module, bundle: .main, compatibleWith: nil) } }
これによりFeatureモジュールでの開発は可能になったのですが、これだと実際のAssetはモジュールにないためSwiftUIプレビュー時に使用できません。かといってSwiftUIのコードを少し修正するたびにアプリ全体をビルドをするのは非効率です。特に、SwiftUIの練度がまだ低かったのでちょっとコードを書いて、見た目を確認するというニーズが多く求められました。これを解決するためにミニアプリを作成することにしました。
ミニアプリの作成
本番用のプロジェクトとは別にプロジェクトを新規作成し特定のFeatureモジュールのみ依存させた軽量なアプリを作成しました。アプリ全体をビルドしないためビルド時間はかなり高速で、数ヶ月かけて行うような大規模機能開発においてサーバーサイドのAPIがまだ完成していない時にViewだけ先行して開発を進める時など特に有用です。 色や画像のアセットは本番用のプロジェクトのアセットのパスをミニアプリのプロジェクトでも使うことで二重で定義することなく共用することも可能です。
マルチモジュール化の加速(1年前~現在)
これまでのSwiftPM導入やマルチモジュール化に向けた整備の効果により、SwiftPM導入前は5つのモジュールしかなかったですが、今では40ものモジュールが作成されるようになりました。やはりPackage.swiftに数行書くだけでモジュールを作成できる簡単さは大きく影響していると思います。 細かくモジュール分割をしたことによりモジュールの再利用性が高まり、その例の一つとしてApp Widgetの対応が簡単に行うことができた効果もありました。 ミニアプリも使用しながら、快適に実装できるようになってきたと実感しています。
一方で、まだやりきれていないところや苦戦しているところもあるのでいくつか紹介していきます。
Swift Packageに移行がなかなか終わらない
- SwiftPMに対応されていない広告系のSDKが多く移行したくてもできない
- 依存するファイルが多く簡単に移行できないファイルの移行が進まない
- xibなどのリソースの移行に手間が必要
といった理由から、機能開発と並行して対応するにはコストが大きく、既存のメインターゲットにあるコードを適切なモジュールに移していく作業が捗らないものも少なくありません。
この場合には実装はメインターゲットに残したまま、protocolのみモジュールに移動することで回避を行なうことはできますが、protocolと実装クラスが別の場所にあるのは混乱を生むので暫定措置としましょう。 このようなファイルはいつまでも残り続けるので、機能開発のついでにやろうとは思わずしっかり時間を確保することが大事です。
新旧アーキテクチャの混在
前述のように過去のコードの移動をしきれていないこともあり、クリーンアーキテクチャのレイヤーのモジュールとFeatureモジュールとの棲み分けがうまくできていないところもあります。共存する前提での各モジュールの責務をチームで認識をそろえていく必要がある、とモジュールが増えてきたこともあり感じています。
SwiftUIプレビューを活用できていない
ミニアプリでFeatureモジュールを軽量に動作させることはできていますが、Assetファイルがメインターゲットにあるためプレビューで表示させることができていません。アクセサーのみモジュールに切り出しましたが、実際の画像ファイルなどもFetureモジュールに移動させていくなど、解決策を考えていきたいと思っています。
まとめ
現在のThe Composable Architectureでマルチモジュールを進めている状況に至るまでに、ここ4年間で少しずつ使用技術を新しくし、古いコードのリファクタリングを積み重ねられていたため、このような選択肢を取れたのだと感じています。既存のプロジェクトに対して一気にマルチモジュール化を進めていくことは時間的に難しいと思いますが、段階的に移行していくことも可能ですのでマルチモジュール化を検討している皆様にGANMA!プロジェクトでの歴史が参考になればと思います!