https://timeedev.connpass.com/event/291690/
⽇経 iOS プロジェクトの マルチモジュール戦略Go Takagi 202 3/09/13After iOSDC LT Night 〜 ピクシブ×⽇経×タイミー 〜
View Slide
Me ( Go Takagi )‣ID• shimastripe / shimastriper• bento.me/shimastripe‣Work at• 株式会社 ⽇本経済新聞社‣ iOS エンジニア‣ iOSDC NOC チーム‣Like• Swift / ⾃動化 / 柴⽝2電⼦版広報⽝デンシバiOSDC2023UIContentConfiguration!!
iOSDC20 23
スポンサーセッション⾯⽩かった!
SPM MultiModule5ApplicationMonolithApp FeatureFeatureFeatureUsecase CommonUsecase CommonRepository Common.................................Swift Package Manager の各パッケージApp
既存プロジェクトの移⾏例の紹介‣ 必ずしもすぐマルチモジュール移⾏できる状態ではない...(ですよね)• 既存アーキテクチャとの兼ね合い• そもそも依存がきれいに分割されているか‣ プロジェクトの状態を正確に把握する必要性がある• どういう効果を期待しているのか‣ ⽇経がどういう技術選定をして移⾏しているか• ベースの基盤刷新も兼ねつつ、移⾏しています6
⽇経電⼦版のアプリ7
OSの機能を数多くサポート8アプリ内課⾦ WatchOSサポートWidgetsApple Pencil サポートステッカー
KPIツリーと施策効果から⾒るアプリの役割9ݱͷࢪࡦͷࣄۀߩݙΛ໌֬ʹࣄۀՁ (LTV)ܧଓ݄ F V๚සফඅهࣄຊղࢭNet ARPPUηοτ୯ՁηοτߪೖൢചखྉܦIDܾࡁൺిࢠ൛ϓϥϯൺ՝ۚऀ MAU৽نདྷ๚ऀ࠶དྷ๚ऀແྉτϥΠΞϧਃࠐॳճ՝ۚ՝ۚిࢠ൛ͷࣄۀՁΛߴΊΔͨΊʹɺ՝ۚऀΛ૿͢ɺ՝ۚ୯ՁΛ্͛Δɺܧଓ݄Λ૿͢͜ͱ͕ඞཁ
成⻑し増加する画⾯‧Extension間のコード共有‣ マクロ分岐• #ifdef IS_APP 等があちこちに......‣ TargetMembership• 依存関係の把握が必要‧ビルド時間も伸びる‣ WatchApp との兼ね合い• UserDefaults や Keychain が繋がっていない‣ ⾮同期処理‧DataBindingの管理• App は RxSwift、AppExtension は Combine でそれぞれ重複実装していた• Concurrencyに移⾏したい........10
過去にEmbeddedFrameworkを作ったが‣ EmbeddedFramework の性質‧プロジェクト構造の不理解• GodCommonを作ってしまって結局戻した11https://speakerdeck.com/shimastripe/embedded-frameworkfalsesusume
改善したい、抱えている痛み‣ 古いモジュールの改善‧それに伴う⼗分なドキュメントの整備• メンテしづらい‧AppExtension の制約等の考慮• テストを書きつつ現代ではいらないコードを消したい‣ 依存⽅向をきちんと管理• 無視した変更が⼊れづらい設計を保証する仕組みにしたい‣ ⾮同期処理周り• RxSwift‧Combine のコードを Concurrency に移⾏したい‣ ビルド時間の改善• 画⾯数が多いため、ビルド時間が⻑くて開発効率に響いている12
改善の変遷
AppExtension をまず整理‣ App の Subset であるため取り組みやすかった• Core になる仕組みを整理していく• 前処理Macroの整理‧共有している最⼩限のロジック群を把握‣ WatchOSの考慮• Keychain‧UserDefaults など永続化周りを抽象化してAppの場合と住み分ける‣ Combine ベースの⾮同期処理を Concurrency に置き換える• API‧DB などの Data 層の処理まで⼀旦 Concurrency 移⾏• ⼀定サイズを移⾏後、Usecase や UI 層にも⼿を付け移⾏していく• App も RxSwift 向けに変換して利⽤する形で部分的に移⾏‧徐々に割合を増やしていく14
Package.swift でレイヤー依存関係を定義15// MARK: - Utility// ֤ϨΠϠʔͷύοέʔδΛ༻ҙ͢Δศརؔprotocol TargetProtocol {var category: String { get }var name: String { get }var hasTest: Bool { get }var testDependencies: [TargetDependency] { get }var allDeps: [TargetDependency] { get }/// default extensionvar dependency: TargetDependency { get }var targets: [Target] { get }}
レイヤー間の依存関係を制御16extension Data {static let orgUserRepository: Self = .init(name: "OrgUser",layer: .repository,dataDeps: [.keychainKit],domainDeps: [.model, .repositoryProtocol],hasTest: true)}
MultiModule に移しつつコードを刷新する‣ global な Extension の整理• MultiModule に移して思わぬビルドエラーを踏んで把握‣ ⾮同期処理• Concurrency で書き直して、RxSwift を持ち込まない• Combine は場合によって許容‣ DI• pointfree/swift-dependencies を採⽤して @Environment ベースで DI‣ テスト• スコープを絞って整理が進むのできちんと書いていく17
開発環境はどう変化してきたか18Project CLI ツールパッケージマネージャー CINikkei.xcodeproj Fastlane / MakefileCarthage Bitrise(3年前)
開発環境はどう変化してきたか19Project CLI ツールパッケージマネージャー CIXcodeGen MintSwiftPM Bitrise (Fastlane)(1年前)
開発環境はどう変化してきたか20Project CLI ツールパッケージマネージャーCIXcodeGen+ MultiModule+Swift PlaygroundsMintSwiftPM + RenovateBitrise (Fastlane)(今)
Project 構成 (workspace)21設定ファイル (yml)Swift PlaygroundsSPM MultiModuleApp (本体)
MultiModule + Swift Playgrounds へ移⾏中22ApplicationMonolithApp FeatureFeatureFeatureUsecase CommonUsecase CommonRepository Common.................................Swift Package Manager の各パッケージApp
MultiModule + Swift Playgrounds へ移⾏中23ApplicationFeatureFeatureFeatureUsecase CommonUsecase CommonRepository Common.................................保存記事AppMore...紙⾯選択画⾯App該当箇所のみビルドしてミニアプリ⽣成
Swift Playgrounds ミニアプリ‣ アプリを画⾯単位で確認したい• 通常分割は⼤変• エントリーポイントを変える仕組みもない• 設定ファイルもたくさん必要‣ Swift Playgrounds をミニハック• iPad (+ Mac) で Swift だけでアプリを作れる• 簡単なアプリしか作れない...?• マルチモジュールを Import してミニアプリを作れる• Package.swift の書き換えは想定されていない⽅法なことだけ注意24https://www.apple.com/jp/swift/playgrounds/Demo.swiftpm├── Package.swift└── Sources/MyApp/MyApp.swiftエントリーポイントを容易に追加できて嬉しい
Xcode Preview との使い分けは悩み中‣ Preview 安定しない問題• 特定のライブラリに依存して失敗するようになりやすい• 回避できるけど問題は"気づいたら"壊れているのが⾟い‣ 現状のミニアプリの利点• 安定してビルドして確認できる• 設定をユースケースに応じて動的に変えたりリッチなPreviewが作れて嬉しい• ミニアプリで有効な機能はApp側のDebug機能に移すことも25
Stricted Concurrency Checking‣ MultiModuleでもまだ ON にはしてない• 古いコードからの移⾏がつらくなるため• 度々⼿元でオンにしてみて様⼦⾒• 特に UI レイヤーで ON にすると⼀気に⼤変な量のエラーに 😇26
TestPlanを⾃動整備‣ TestTarget‧TestModule がどんどん増えていく• 内部の JSON はシンプル• 以下のコマンドでモジュールの⼀覧は取れる• xcrun --sdk macosx swift package --package-path MultiModule describe --type json• ビルド時に jq で TestPlan の⾃動アップデート27{"containerPath": "container:MultiModule","identifier": "UseCase","name": "UseCase"}
Xcode Template を整備‣ 標準ベースで書き直したモジュールに Template を⽤意• APIRequest‧DB• swift-dependencies 周りの Template• ただ基本シンプルなものしか作れない• より複雑なTemplateが必要になったら Sourcery を検討する予定28https://github.com/krzysztofzablocki/Sourcery
DoCCを整備 (したい)‣ いくつか問題点を抱えてまだ導⼊できず• plugin が platform: Mac 専⽤• Xcode の generate docs でも• 特定のライブラリの docc ファイルを⾒てエラー.......• 複数 Scheme をまとめるドキュメント⽣成ができない29https://github.com/apple/swift-docc-plugin/issues/38DoCC の運⽤ Tips ぜひ知りたいです!
まとめ‣ Project の状態は様々• MultiModuleへの移⾏は⼀般に時間がかかる• 痛みを認識し、そこから取り組んでいったほうが効果が実感できて良い• アプリが⼤きくなるとより継続性が⼤事‣ ⽇経のマルチモジュール移⾏→標準ベースのリアーキテクチャも兼ねる• OSの機能をリッチに活かしながらモダンな技術を利⽤できる環境• OSの機能はアプリチームが⼀番詳しいので積極的に起案して作ってます• まだまだ⼀部だけで移⾏中。興味を持った⽅、弊社にぜひ声かけてください!30