📦

.xcworkspace と Swift Package を活用して XcodeGen 等に頼らずともほぼコンフリクトしないプロジェクト設計

5 min read

TL;DR

  • Xcode Project(.xcodeproj)による開発では project.pbxproj によるコンフリクトが多発する
  • XcodeGen などのツールでコンフリクトしないようにしてきたが、外部ツールに頼らない方針でいきたい
  • Xcode Workspace(.xcworkspace)と Swift Package を使って Xcode Project(.xcodeproj)内でのファイル操作をなくし、コンフリクトが起きないようにする

https://github.com/treastrain/SPMAndXCWorkspacePlayground

まえがき

2021年5月19日に行われた CA.swift #14 〜いま考える次世代の設計〜 にて、@ry-itto(Ryoya Ito) さんが発表した「isowords の構成で3週間開発して得た知見」で紹介されたファイル等構成がおもしろく、実際に自分の手で試してみました。

https://cyberagent.connpass.com/event/211541/
https://youtu.be/MQWW84lg9xc?t=1110
https://speakerdeck.com/ryitto/ca-dot-swift-number-14

概要

Xcode を使ったアプリケーション開発において、Xcode Project(.xcodeproj)による開発では project.pbxproj によるコンフリクトが多発し、それを防ぐために XcodeGen などのツールを使用してきました。
しかし、外部ツールに依存すると、例えば年に1度の Xcode のメジャーアップデートが来た際に Xcode の新機能等を使うにはそのツールの対応を待つ必要があります(新しい App Extension が登場し、TARGET に追加したい場合など)。
また、CI / CD を使う場合などでは xcodebuild のみで動作できる方がよいです。

https://github.com/yonaskolb/XcodeGen

主にファイルの追加や削除によって project.pbxproj の変更が発生する Xcode Project(.xcodeproj)とは異なり、Swift Package ではファイルの追加や削除を行ってもファイル構成を管理する project.pbxproj やその役割を持つファイルが Swift Package には無いため、これについて心配する必要がありません。

さらに、Swift tools version 5.3 以降では各リソースファイルも Swift Package に加えられるようになりました。

そして、Xcode Workspace(.xcworkspace)や Xcode Project(.xcodeproj)に Swift Package を追加できるようになったことから、これらをうまく組み合わせることで、Xcode Project(.xcodeproj)内でファイル操作をしないことによるコンフリクトの軽減が見込まれます。

以下に Xcode Workspace(.xcworkspace)と Swift Package を使って、SwiftUI の iOS・macOS Multiplatform App のプロジェクトを始めるとした場合の構成手順を掲載します。こちらで構成したプロジェクト一式は GitHub で公開しています。

https://github.com/treastrain/SPMAndXCWorkspacePlayground

構成手順

  1. Xcode Workspace(.xcworkspace)を作成する

  2. Xcode Workspace(.xcworkspace)を作成した状態

  3. App のための Xcode Project(.xcodeproj)を作成する

  4. このとき、「Add to」と「Group」に先程作成した Xcode Workspace(.xcworkspace)をを選んで「Create」する

  5. Xcode Workspace(.xcworkspace)のなかに MyApp.xcodeproj を作成した状態

  6. Swift Package を作成する

  7. このとき、「Add to」と「Group」に先程作成した Xcode Workspace(.xcworkspace)を選んで「Create」する

  8. Xcode Workspace(.xcworkspace)のなかに MyApp.xcodeproj と MyAppPackage を作成した状態

  9. MyApp の ContentView.swift を MyAppPackage へ移動する

  10. 外部からアクセスできるように internal なところを public 等に変更する

  11. MyAppPackage のプラットフォームを設定する (今回は ContentView.swift で SwiftUI を使用しているため iOS 13.0 以降等にした)  また必要に応じて dynamically linked library にする

  12. MyApp の TARGET で MyAppPackage を追加する


  13. import MyAppPackage でインポートする

  14. Build、Run して動作を確かめる

これ以降の開発では、基本的に Swift Package の方に実装を行います。外部のライブラリを使いたい場合も、Swift Package(上記例では MyAppPackage)の Package.swiftdependencies に追加します。Swift Package ではなく CocoaPods や Carthage を使用しなければならないライブラリの場合は、.xcframework としてそれらのライブラリを書き出し、Package.swifttargetsbinaryTarget として追加する方法をとります。

おわりに

@ry-itto(Ryoya Ito) さんが発表してくださるまでこのような構成を取れることを知らず、今後使っていきたいと思う内容でした。

また、これまでであれば Mint を使用して使ってきたようなツールが欲しい場合であっても、Mint ではなく Swift Package の dependencies に追加すればよい、という点も非常に興味深いと感じました。

この記事で紹介しなかったメリットやデメリットについても述べられていますので、YouTube 上のアーカイブや SpeakerDeck、また @ry-itto(Ryoya Ito) さんによるサンプルリポジトリが非常に参考になりますので、下の関連項目からチェックしてみてください。

関連項目

https://github.com/ry-itto-playground/SPMProjectSample

https://github.com/ry-itto-playground/SPM-cocoapods

https://uhooi.medium.com/ca-swift-14-6c1d129ea892