🍢

個人開発でXcodeプロジェクトの新規作成時にやること

2025/01/06に公開

はじめに

2025年の1月現在で、iOSアプリの個人開発において自分がプロジェクトで設定することを書いてみます。Xcode 16.2です。

下記の記事に触発されました。
https://zenn.dev/miharun/articles/d2b47fea1c885a

前提として今の環境で自分ならこうするというものを書いておきます。仕事でどうやるかはチームとかで決めれば良いと思います。

.editorconfig

Xcode 16から.editorconfigファイルを置くことで、プロジェクトごとにXcode設定を変更できるようになっています。

# editorconfig.org

root = true

[*]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
  • [*]
    • 全てのファイル拡張子に適用
      • もし設定ファイルやドキュメントで別ルールを除外したい場合が出たら拡張子指定
        • 例: [.md]
  • indent_sizeは2
    • SwiftUIは特にインデントして右側にコードが行ってしまう
      • editorconfigが出る前は4を変えてしまうとプロジェクトごとに設定を変更する必要があった
        • 仕事用は4で個人用は2にするのが面倒だったが、個人用を2にできる
          • 徐々に仕事用も2に説得していける
    • TypeScriptやら他言語は大抵2なのでそれにあわせたい
  • trim_trailing_whitespaceはtrue
    • 行末の空白を自動削除
  • end_of_lineはLF
    • macOSを使うので改行コードはLFでいいと思う
    • Unix系もLFなのでGitHub Actionsのファイルを考えてもLFでいいと思う
  • insert_final_newlineはtrue
    • ファイル末尾が改行されてなければ改行

trim_trailing_whitespaceをプロジェクトで共有できるのは個人開発というか仕事で役に立ちます。いままでXcodeにもありましたが、それをいちいち説明するが面倒でした。

.editorconfigをプロジェクトに追加

.editorconfigはルートに置けばいいですが、さらにXcodeのプロジェクト内で開けるようにします。

Xcodeでコード書くときに設定をプロジェクトに追加しないのはいちいち別のエディタで開く必要がありかったるいです。たまに仕事でyamlファイルとか扱いますが、それも同じ。もちろんビルド対象になってないことを自分の目で確認することが必要です。

不要なSupported Destinationを消しMac(Designd By iPhone)は残す

iPhoneアプリだけでいいならiPad/VisionOSのサポートを消します。
デフォルトでMac(Desgined By iPhone/iPad)があると思いますがこれは残す。

iPhoneアプリをMacアプリとして起動しデバッグするメリットは、書いたコードの実行がiOSシミュレータを起動するよりも早いからです。つまり、簡単なことを確認したいだけのときいちいちiOSのシミュレータを起動してられないことがあるからです。

もちろん見た目はmacアプリなので表面的なデザインの確認はできませんし、iOSシミュレータにしかない機能は使えません。iOSデバイスにしかない機能ももちろん確認できません。さらに注意点として、macアプリの実機で動作させるので実機実行の証明書関連をクリアにする必要はあります。

Local Packageにコードを書けるようにする

構成として最小限にするため、アプリのホスト側はAppに準拠する構造体と、必要であればAppDelegateのみにします。

  • アプリのホスト(AppAppDelegateSceneDelegate)のみ
    • Local PackageとしてSwift Packageに全部書く

機能はModulesというLocal Packageにコードを書く。アプリのホスト側はModulesを呼び出すだけ。

なぜこうするかというと、大抵の機能やらはアプリの本体側に書く必要がないからです。シンプルな構成にしたいのと、Local Pacakgeに書いておいたらそれをあとは自由に組み替えればいいからです。あと外部のライブラリをPacakge.swiftでコードとして書きたいからです(XcodeのPackage DependenciesをGUIから設定したくない)。

また、コード自体がLocal package側にあるわけだからテストコードもそちらにかけます。そしてswift testが実行できるテストを作れる状態にすることもメリットです。swift testでテストできるのであればiOSシミュレータを起動する必要がなく、iOSシミュレータの起動ができないなどのトラブルも避けることができるし、そもそもテストの実行時間が短縮されますし、iOSシミュレータに依存したテストにならないので安定します。

ただ注意点もあるので後述しています。

XcodeでLocal Pacakgeを使う方法

やり方は2つあります。

  1. XcodeにLocal PackageをAddする
    • さらに細かくAdd手順は2つ
      • Project側をXcodeで開いて、Local PacakgeもXcodeで開いてDrag And Drop
      • 左下の+ボタンからAdd FilesでPacakageのフォルダを指定
  2. XcodeのPacakge DependencyとしてGUIからLocal Pacakgeを追加する

後者の2がAppleのドキュメントでよく説明されています。なので後者でもいいけど、Local PackageのテストコードをSchemeに設定する方法が私にはわからないので前者の方法を使っています。

もし前者の1のやり方がダメとなっても、どちらにせよ、後者のやり方にするのも簡単なので気にせず前者のやり方をしています。

Local Pacakgeを使う際に残りやること

あとはどうでもいいこととして、少しやることはあります。XcodeのGUIで設定します。

  • Modules側のテストをxctestplanに登録
  • Package側をビルドできるようにSchemeに登録

swiftLanguageModes(Swift Language Version)の指定

swiftLanguageModesを指定するのはここまでの設定をしてるなら2箇所あるはずです。

  • Xcode自体のホスト側の指定
  • Local Package側の指定

Xcode 16を使っている時点でツールバージョンはSwift 6であり、Swift 6でビルドできるはずです。その上でswiftLanguageModesを6にするか5.xにするかという設定をします。

swiftLanguageModesを6にすると、Swift 5時のUpcomming Feature FlagをまとめてYESにできる、ぐらいの認識でいいと思います(間違っていたら指摘をお願いします)。

swiftLanguageModesのデフォルトである5の場合を確認

Projectの設定からUpcomming Featureで検索するとデフォルトでNoになっているはずです。

これはあくまで確認するだけです。この設定で個別に変更してもいいですが、swiftLanguageModesで6に指定します。

こうするとUpcomming Featureで関連するものたちがYesになるはずです。

初手なんで対してコードが存在しないはずで、全部Yesでいいと思いますが、どうしても警告が出て解決できない場合に個別にNoにしたりすればいいと思います。

Local Package側もPackage.swiftにswiftLanguageModesとしてv6を指定してみます。

.target(
    name: "Modules"
    swiftSettings: [
      .swiftLanguageMode(.v6)
    ]
),

あんまりよくわからないswiftLanguageModesとは何かについては別の記事で書いてます。

https://zenn.dev/yimajo/articles/c5fb186157bc94

vscodeやcursorで動かす

Sweetpad

拡張でSweetpadをインストールすることでxcodebuildコマンドを呼び出してくれたり、他のツールと組み合わせてくれます。

https://sweetpad.hyzyla.dev/

初手としてコードや設定が少ないうちにビルド成功するのをみたほうがいいです。初手でSweetpadのビルド失敗すると言うことは、なんかしら拡張の設定やらmacOS自体の何かしらが失敗しているはずで、状況の切り分けが明確になります。

あまり詳しく調査できていませんが、Objective-Cを含むプロジェクトだとビルド失敗すると思います。

docs/を作ってそこにmdドキュメントを書く

iOSアプリに限らず個人開発の場合、プロジェクト内にドキュメント用のフォルダをつくってドキュメンをためます。

inkdropとかObisidianに書いても分散してしまうので、もうプロジェクトのことはプロジェクト内に書く。

Localization

デフォルトだとEnglishです。

まず日本語版しか作らないならJapaneseを作成してEnglish消す。

記憶がたしかではないですが、Englishのまま日本語をハードコードすると、App Storeのアプリ詳細で英語にしか対応してないように見えるんじゃないかな(実際は起動すると日本語)。違いましたかね?

あとでやればいいこと

DerivedDataのパスをプロジェクト配下にする

プロジェクト固有の話じゃないんですが、XcodeのGUIから.build/DerivedDataを指定すると、プロジェクト配下からアクセスできる場所にDerivedDataを置けます。簡単に消したいので近くにあればいい。

Explict modulesを有効にするのは測定してからの方がいい

Xcode 16からExplict modulesを有効にすることができますが、Xcode 16.1で試したところ、クリーンビルド後のPlanningのフェーズが遅いです。もしかしたらめちゃくちゃ小さいProjectだと違いがないどころか遅くなっていて、大きなProjectならビルドが速く終わる損益分岐点があるかもしれないです。とにかく何も考えずにやらない方がいい。絶対に測って確認すべきです。

Xcode 15か14あたりからAssistantを有効化にするとビルド時のステップを視覚化できます。

他のブログ記事でも速くなってないとか書かれています。

https://useyourloaf.com/blog/xcode-explicitly-built-modules/

https://bitrise.io/blog/post/demystifying-explicitly-built-modules-for-xcode

どうでもいいこと

話が脱線しますが、細かくて他の記事にするのもどうかという細かいことを書いておきます。

キーボードショートカットキーとキーマクロ

.editorconfigでインデントを2にしたのをファイルごとに指定する方法について書いておきます。

Xcodeのエディタ設定に従ってインデントを変更してくれるショートカットがあり、選択してそれを実行することでインデントを変更することができます。

私はインデントの一括変更のショートカットキーはKeychronのキーボードマクロに登録していて、このショートカットと、cmd aのファイル全指定をM3キーに割当ててます。

脱線しまくりですが、テスト実行も「直近のテストを実行する」というショートカットキーが存在するのでそれも同じようにM5キーに割り当て。「カーソル位置のテストのみを実行」をM4キーに割当ててます。

swift testコマンド

macOSからswift testコマンドでLocal Packageのテストを実行するとき、SDKとしてはmacOS SDKを使ってmacOS用のバイナリを作っているようで、Package.swiftのplatformsにiOSを指定してもそれは適用されません。

そのため、macOSでリリースしたいわけじゃなくても、そのPackageのplatformsにmacOSを指定すると良いと思ってます。

let package = Package(
    name: "Modules",
    platforms: [
        .iOS("18.0.0"),
        .macOS(.v12) // 最終的にmacOSでリリースしたいわけじゃないが書いておく
    ],

考え

なんでこうなるかというと、swift buildは純粋にswiftの機能だからだと思います。xcodebuild testはiOS用のコードをクロスコンパイルしていたり、シミュレータを起動してテストを行えるが、swift buildはそれ自身を純粋に実行しようとするプラットフォームで動作するバイナリを作ってテストする機能だからだと思います。

めちゃくちゃ脱線しますが、Package.swiftをSwift 6とSwift 5で分けているOSSがあるように、テスト用のPackage.swiftがないとテスト実行できないなら、CI上でそれを作るというアイデアがあるかもしれないですね。

おわりに

基本的に人がどうやろうと自由だと思いますが、たいてい複雑なことをしようとしないのがベストな気がします。ただ無駄なものは削除したほうがわかりやすいし、デフォルト動作が暗黙的な気がするなら明示したい。個人開発でも過去の自分に属人化してしまうのでdocsつくったほうがいい。

あと標準で足りないことをオプトインで無闇に有効にするなら効果を測定しないと結局個人開発でスピードを落としてたら自己満足しかない。そういう意味では、 swiftLanguageModesも5でいいし、モジュール分割も何もやらないのが一番かもしれないですね。

関連

Schemeをわざわざ分けて使わないでくださいという話も別に書いてます。

https://qiita.com/yimajo/items/6a2167d0c5844ea9f664

Discussion