Open11

マルチモジュール勉強メモ~EmbeddedFramework~

さしもんさしもん

Embedded Frameworkとは

  • プロジェクトのマルチモジュール化
    ざっくりいうとフレームワークのこと
    通常新しくProjectを作成するとTargetは一つだけど、Project内の要素をFrameworkとして抜き出して使うことができる

例えばUI,Core,Data,CommonというレイヤーでProjectが構成されていて、これをEmbedded Frameworkを使ってレイヤーごとにtargetを切り分けたりする

通常 マルチモジュール化 マルチモジュール化

使用する場合の注意点

Embedded Frameworkを使ってアプリ内の要素をFrameworkとして切り出した場合
通常Frameworkを使うとき同様に使用するファイルでimportする必要がある

なのでFrameworkとして切り出された側で、他ターゲットから利用することを想定しているメソッドなどの場合はアクセスコントロールとして先頭にpublicをつけなければならない
これはアクセスコントロールがデフォルトではinternalであるため

メリット

  • コード共有
    共通分をFrameworkとして切り出すことで、他のプロジェクトで利用したり、UIだけ別に作って機能が同じであるアプリをいくつも横展開したりできる
    他にも一つのサービスをiOSアプリだけでなくwatchOSやtvOSなど複数端末で使えるようにしたりなどマルチプラットフォームへ展開することもできる

  • レイヤー分割・依存関係の矯正
    先に書いたとおり、切り出されたターゲットはFrameworkとして扱われるため
    通常Frameworkを使うとき同様にimportしないとコンパイルエラーとなり使うことができない

これがレイヤー感の依存関係の矯正にどう影響するかというと
各レイヤーの依存関係は予め設定することができるから

例えば以下のように設定してみました

メインターゲット Core Data Common

この場合メインターゲットはimport Coreやimport Data, import Commonを使用したいファイルで宣言することで各レイヤーにアクセスことができます

同様にCoreでDataを使いたい場合はimport Dataやimport Commonを必要に応じて宣言、Dataではimport Commonを必要に応じて宣言することでそれぞれ利用することができます
逆にこの依存関係だとCommonからDataだったりDataからCoreだったり、Coreからメインターゲットへアクセスすることはimportを使って宣言してもコンパイルエラーで弾かれてしまいます

なのでEmbedded Frameworkを使うとレイヤー分割や依存関係の矯正ができるといえます

以下の記事がとても参考になります
https://qiita.com/mono0926/items/e29cd17789fd1d1548aa#embedded-frameworkへの分け方の例

  • ビルド時間の短縮

Swift1.2で差分ビルドが導入されビルド速度は改善されたはず
ただ自分は実感してないけど、過去には差分ビルドがうまく走らずに少しのコード修正にも関わらず全体のビルドが走ったりすることもあったらしい
*ただこれに関しては自分の実感がない&参照していた記事が古いのでもしかしたら2021年では問題ないのかもしれない

推測でしかないけど、1つのターゲットにすべてがまとまっている&コード量が多い場合に差分ビルドが走らないことがあったのかもしれない

というのもEmbedded Frameworkを使うことで差分ビルドが効いたという内容は見かけるから

Embedded Frameworkで依存関係を明確にすることでコンパイラにそれが伝わり
一つ一つのレイヤーごとに差分を確認させることができる...???

またメインターゲット実行時に差分ビルドが効きやすく速くなるだけでなく、XcodeのScheme設定で特定のFrameworkを選択することで選択したFrameworkのみのビルドやテスト実行などを行えるのでビルドが速くなる

SchemeをそれぞれのFrameworkに設定してビルドしてみるとわかります
依存関係にあるものまでしかビルドは行わないのでScheme設定が下位レイヤーになればなるほど依存関係がすくなくなりビルドが速くなります

テストをしたい場合はこのScheme設定を対象のFrameworkにするとよさそうです

メインターゲット Core Data Common
さしもんさしもん

差分ビルドがうまく機能せずに少しの修正で全くさわってない箇所まで再ビルドがかかるのであれば
確かに無駄

考えてみると、全てのソースファイルをアプリケーションターゲットでビルドするのは無駄です。APIからデータを取得するコードやデータモデルなどは、最初に定義してしまえばほとんど修正することはありません。
https://devblog.thebase.in/entry/2018/05/09/113141

さしもんさしもん

PreProcessor
Compilerへプログラムを送り込めるように変換
マクロをそれぞれの定義へ置換
依存関係の発見
PreProcessorのディレクティブ(#if DEBUGなど)を解決

差分ビルドがうまく効かないっていうのは
このPreProcessorが正確に依存関係を認識できないから
全部つながっているように見えて起こるのかも?
Embedded Frameworkを使うとこの依存関係をPreProcessorが明確に認識できるから差分ビルドが効くようになるのかも

https://qiita.com/shiz/items/b8d5126210de736d7986#preprocessor

さしもんさしもん

【メモ】
> Dynamic Frameworkが増えるにつれてアプリの起動時間が長くなることがあります。
Dynamiic Frameworkを使うとアプリの起動時間が長くなる?

ModuleをDynamic Frameworkではなく Static Framework としてビルドして使用することで起動時間の最適化が可能?
https://medium.com/eureka-engineering/create-merged-framework-to-cut-appstartuptime-72ee67b2bbab

StatickFramework化して起動時間短縮?
https://rizumita.medium.com/xcodeプロジェクト内のdynamic-frameworkをstaticビルドする-fcefd9bb1b48

さしもんさしもん

https://tech.recruit-mp.co.jp/mobile/post-12398/
extension使うとビルド時間って長時間化するの?

さしもんさしもん

> Module毎に言語のMigrationが出来る
知らなかった

いまあるFlutterとかKMMの着想はここなのかな

さしもんさしもん

[Cons] Main Interfaceなどの仕組みが扱いづらい
しかし、現状はStoryboard上ではBundle ResourcesをModule間で共有できる仕組みがないので

自分の場合はアプリ全体で使うasset管理がこれで手こずった
結局Assets.xcassetsをメインターゲットとCommonモジュールにもおいて対応
両方に置いたのは基本的にはassetsをCommonに置くんだけど
AppIconがメインターゲットで使うからどうしてもメインターゲットでも必要だったため

いずれにしてもstoryboardとかMain Bundle Resourceに該当するものはEmbedded Frameworkを使う場合は気をつけないといけないな

さしもんさしもん

上記の設計には『下のレイヤーは上のレイヤーのことを知らない』という原則があります。しかし、SwiftではPackage単位ではなくModule単位で名前空間が管理されているので、同一のModuleに複数のレイヤーが含まれていると下のレイヤーの人が上のレイヤーの人を呼び出すことを機械的に防ぐことは出来ません

下位レイヤーから上位レイヤーアクセスできるじゃん....そういうもんなんだなってプログラミング始めたばかりのときにおもってたけど他言語だとできないことが多いのか。しらなかった

さしもんさしもん

EmbedFrameworkを使いすぎると起動が遅くなる件について

これはEmbedded Frameworkに限ったことではなくて、ほかにもCarthageを利用してダイナミックフレームワークを追加していくと起動時間が長くなっていくようです
Appleは多くとも6つのダイナミックフレームワークにすることを推奨しているとか