Edited at

受託開発での iOS アプリプロジェクト新規作成プラクティス(上編:Xcode 編)


前書き


  1. 本記事は受託開発前提で書いております。そのため、受託開発における特殊な要件がいくつかあります。もちろん通常の開発にも通用する部分は多いですが、どこまで流用するかは読者の皆さん自身にご判断をゆだねます。

  2. 本記事のサンプルとして使われたプロジェクトはこちらの GitHub リポジトリーからダウンロードできます。

  3. 本記事は上編:Xcode 編で、プロジェクトの作成を説明します。CI 運用については下編をご覧ください。


要件


  1. 基本開発環境は Xcode を利用します。

  2. ライブラリーの管理は Carthage を優先に利用し、Carthage で対応できないものは CocoaPods を利用します。

  3. 開発ツールの管理は基本 Homebrew を優先に利用し、Homebrew が利用できないものは BundlerMint 等を利用します。

  4. 動作環境は社内開発環境(Development)、納品先検証環境(Staging)と本番環境(Production)の三つがあると想定します。

  5. 受託開発のため、社内開発環境と納品/本番環境はそれぞれ違う Bundle ID や開発者証明書を利用する1と想定します。

  6. 開発メンバーの入れ替えがそれなりに発生すると想定されるため、環境構築の属人化を避け、なるべくスクリプトにまとめてコマンド一つで構築されるようにしたいです。


手順


1. GitHub から新規リポジトリーを作成

いきなり Xcode からプロジェクトを作成しないのは、GitHub から作成した場合、予め iOS プロジェクト向けの .gitignore ファイルを作成してもらえるからです。もちろん自分で .gitignore ファイルを作って中身書いたり、もしくはあとで GitHub から落とすのも問題ないですが

スクリーンショット 2019-01-23 19.49.42.png


2. GitHub に作ったプロジェクトをローカルに Pull

基本的に SourceTree とかの git クライアントツールを使えば大丈夫です。もちろん慣れた方は git コマンドで落とすのも問題ありません。


3. CocoaPods の Pods/ と Carthage の Checkouts/ も .gitignore に追加

ライブラリー管理ツールで入るソースは基本的にバージョン管理させる必要がないので、これらを .gitignore に追加しておく


4. Xcode プロジェクトを作成

作成先は先ほど落としてきたディレクトリーに Xcode プロジェクトを新規生成します。もともと git が有効になっているディレクトリーのため、Source Control のチェックボックスが無効になるはずです。

スクリーンショット 2019-01-23 20.06.42.png

ただここで一つだけ面倒な事がありまして、git のディレクトリーにプロジェクト作る場合、もしプロジェクト名が git のリポジトリー名が重複していれば、同じ名前の階層が結構深まってしまったりしますので、個人的にはいつもプロジェクト作ったら即 Xcode プロジェクトの階層を一つ上に戻しています。


5. Xcode ワークスペースを作成

どのみちあとで CocoaPods が勝手にワークスペースを作ってしまいますが、個人的にはあの動きあまり好きじゃないので、作るなら自分が作ります。Xcode File メニューから「Save as Workspace…」でセーブできますので非常に楽です。


6. Xcode プロジェクトと関係ないリポジトリーファイルをワークスペースに追加

GitHub でリポジトリー作成時設定によってに勝手に作られる LICENSE ファイルや README.md ファイルから、ライブラリーやパッケージ管理のための Podfile なども、Xcode から直接編集できると環境構築作業が非常に楽になるので、とりあえずいれておきたいです。せっかくワークスペースも作ったのでとりあえず最大限に活用したいですね。追加方法も簡単です、自分はいつも Xcode の Project Navigator から「New Group [without Folder]2」で Environment の名前のグループを作って、そこにあらゆる環境構築関連のファイルを入れています。

スクリーンショット 2019-01-23 20.39.36.png


7. Build Configurations を作成

CocoaPods 使うと、既存の Build Configuration に応じて色々自動生成してくれますが、Build Configuration に変更を入れると CocoaPods は古い設定を自動で消してくれないのでとても面倒です3。もちろんどうにか手動で直すのは可能ですが、そんな面倒なことは先に回避できたらしたいですね。というわけで先に Build Configurations を入れておきます。

よくあるのは自社開発ようの Development 環境、納品先テスト用の Staging 環境、そして実際のユーザが利用する Production 環境の 3 種類があると思われます。そして同時に実行目的に応じて Debug ビルド環境と Release ビルド環境もあります。これらの環境の分け方は様々ありますが、比較的に一番開発コストがかからない(※個人の意見です)方法は全て同じ Target にして、Build Configuration のみ分けて、それぞれを違う Scheme で分けるやり方かと思います。

というわけで、早速プロジェクトの Info に行って、「Configurations」を増やします。パターンとしては下記の 6 種類が必要とされます:

Development
Staging
Production

Debug
Debug-Development
Debug-Staging
Debug-Production

Release
Release-Development
Release-Staging
Release-Production

基本的に Xcode は最初から DebugRelease があるので、それぞれをコピーして必要な動作環境を入れれば問題ないかと思います。

スクリーンショット 2019-01-25 19.45.42.png


8. Build Configurations を設定

これらの Build Configurations を作れば、一番嬉しいのは必要な応じて様々な設定が可能になることです。よくあるのはソースコード中、このような切り替えが使いたい物でしょう:

#if DEVELOPMENT

let url = devURL
#elseif STAGING
let url = stgURL
#else if PRODUCTION
let url = proURL
#endif

この切り替えを有効にするためには、Active Compilation Conditions を追加すればいいです。初期状態では Debug の Build Configuration では DEBUG が入っています。ところが先ほど 6 種類の Build Configurations を作ったので、それらに合わせて DEVELOPMENTPRODUCTION のフラグを入れてあげればいいです。

スクリーンショット 2019-01-25 19.47.01.png

ところが、Build Configuration を分けることによって得られたメリットは何もこれだけではありません。必要に応じて違う Bundle ID や違う entitlements ファイルから違う開発者証明書まで、ありとあらゆるプログラムと関係ないことの設定が可能になります。例えば要件 5. はまさにこのように設定が可能になります。

スクリーンショット 2019-01-25 17.56.36.png


9. Scheme の設定

Build Configurations だけ設定しても、いつどういう時にどれを使うかがわからないので、Schemes を設定します。これによって左上の Scheme 切り替えによって手軽に試したい環境を切り替えます。Xcode はすでにどんな時に Debug と Release をどう使い分けるかを設定してありますので、それにちなんでそれぞれの Scheme に適用だけすれば簡単にできます。と言うわけで 「Product > Scheme > Manage Schemes…」に行けば、現在の Scheme を Duplicate して Development、Staging と Production の Scheme をそれぞれ作りましょう;もちろん Shared にチェックするのも忘れずに行いましょう、これによってチームメンバーと Scheme の設定が共有できます

スクリーンショット 2019-01-28 17.10.35.png

これで「Product > Scheme > Edit Scheme…」のところ行けば各命令でどの Build Configuration を使うかの設定ができるようになります。

スクリーンショット 2019-01-25 19.50.49.png


10. 必要なパッケージやライブラリーを明示して構築

必要な環境は全て明示した方が、CI にも優しければ、途中からジョインするメンバーにもわかりやすいので、Podfile や Cartfile だけでなく、Gemfile や Brewfile も忘れずに作成しておきましょう。

ちなみに具体的な各ライブラリーやパッケージの導入についての説明は物によりますのでここでは割愛しますが、Mint が使う Mintfile と、Mint を使う DIKit だけ少し面倒なことをしないといけないのでちょっと説明を入れます。DIKit もしくは Mint を使わないのでしたらこの説明を読み飛ばして大丈夫です。

DIKit は自身の dikitgen を使って、ビルドする前に DI に必要なソースファイルを自動生成しています。この dikitgen はもし Mintfile を使わずにそのまま $ mint install ishkawa/DIKit dikitgen で入れるのなら問題ないのですが、先ほど言いました通り Mintfile があった方が、どんな管理ツールを使ってどんなパッケージをインストールしているのかがわかりやすいので Mintfile を使いたいです。しかし Mintfile を使った場合、パッケージのリンクを行ってくれない4ので、使いたい場合は $ dikitgen がそのまま使えず、$ mint run dikit dikitgen で使う必要があります;しかしその場合、今度は逆に自動生成されたファイルには Mint の出力 Swift ファイルにまで含まれてしまい、ビルドエラーになってしまいます。

それを解決するためには、自分は Xcode DIKit の Build Phase では敢えて下記のようにもう一回 Mint で DIKit を入れています:

$ which dikitgen >/dev/null || mint install ishkawa/DIKit dikitgen

$ dikitgen $(SRCROOT) > $(SRCROOT)/path/to/place/DIKit/auto-generated/file.swift

これはもし dikitgen がリンクされてい場合のみ Mint で DIKit をインストールします;ただし実際の動きとしてはもしすでに Mintfile で $ mint bootstrap が済んだ場合は実際にもう一回インストールを行わず、リンクだけしてくれます。


11. 環境構築スクリプトを作成

ここまで必要なパッケージやライブラリーを全て管理ツールのファイルに記載しておきましたが、利用するにはまだそれぞれ呼ばないといけないのが微妙に面倒なので、一発で環境構築できるスクリプト5も作っておきましょう。自分の場合は下記のようなスクリプトを作りました:


bootstrap.sh

#!/bin/sh

# setup Gems, Brews and Mints
echo "Install Gems and Brews"
bundle install
brew bundle
mint bootstrap

# install dependencies via CocoaPods
echo "Install dependencies via CocoaPods"
pod install

# install dependencies via Carthage
echo "Install dependencies via Carthage"
carthage bootstrap --no-use-binaries --cache-builds --platform ios

echo "Enjoy!"

open EnterpriseProjectSample.xcworkspace


スクリーンショット 2019-01-25 16.52.10.png

こうすれば、$ sh bootstrap.sh だけ叩けばすぐ環境構築ができ、ビルドを確認したり開発に手をつけたりできます。





  1. Apple の開発者登録は機種ごとに年間最大 100 台までしか登録できず、そのため自社にクライアント様のデバイスを登録するのも、クライアント様に自社のデバイスを登録するのも何かとデバイスの登録上限が気になる事が多いです。(もちろん技術上 Enterprise プログラムで In-House 配布すれば開発端末登録の問題が無くなりますが、ライセンス上発注会社の Enterprise 証明書で In-House 配布して受託開発会社にインストールさせるのは OK ですが、逆に受託開発会社の Enterprise 証明書で In-House 配布して発注会社にインストールさせるのは NG です;そして発注会社がわざわざ App Store 配布不可の Enterprise プログラムに登録するのは非常にレアなケースと考えられます。) 



  2. 現在のカーソル位置によって命令が変わります。カーソル位置がそもそもフォルダーがない参照にある場合は「New Group」の方が正しい(下の方が「New Group with Folder」になっている)ですが、そうじゃない場合は「New Group without Folder」の方が正しい(上の方が「New Group」になっている)です。 



  3. これが個人的に CocoaPods があまり好きではない理由の一つです。やはり完全に自動化できないものなら手動で設定したいですね。 



  4. Mintfile の利用には $ mint bootstrap の命令を使いますが、これはリンクをしない仕様とのことです: https://github.com/yonaskolb/Mint#usage 



  5. ちなみにこれは同じチームの他のメンバーからいただいたアイデアです。