Articles

Xcode でビルド構成を管理する

Resources
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20.Join I’m sorry!Swift 開発について学んでいる 000 人以上の開発者たち

無料コピーをダウンロードする

A brand new Xcode project provides two build configurations, Debug and Release. ほとんどのプロジェクトでは、さまざまな理由から 1 つまたは複数の追加のビルド構成を定義します。 これは新しいことではなく、ビルド構成を使用して、デプロイされる環境の特定のニーズに合わせてビルドを調整することは良い習慣です。

このエピソードでは、API キー、資格情報、その他の機密データなど、ビルド構成に固有のデータを安全に管理する方法を説明します。 ビルド構成を管理する戦略はいくつかありますが、特にセキュリティの観点から考慮すべき重要な違いがあります。

構成の追加

Xcode を起動し、iOS > Application セクションから Single View App テンプレートを選択して新しいプロジェクトを作成します。 Xcode にプロジェクトを格納する場所を伝え、[作成] をクリックします。

Setting Up the Project

左側のプロジェクト ナビゲーターを開き、上部にあるプロジェクトをクリックします。 プロジェクト] セクションでプロジェクトを選択し、プロジェクトの詳細を表示します。

Build Configurations in Xcode

Debug 構成は開発中に通常使用し、Release 構成は App Store または TestFlight ビルドの作成に使用されます。 これはおそらく、あなたにとって新しいことではありません。

多くのアプリケーションはバックエンドと通信しており、ステージング環境と本番環境を持つことは一般的なプラクティスとなっています。 ステージング環境は開発中に使用されます。 Debug 構成をダブルクリックして、Staging に名前を変更します。

Build Configurations in Xcode

Production 環境用の 3 番目の構成を追加してみましょう。 テーブルの下部にある + ボタンをクリックし、[Duplicate “Staging” Configuration] を選択して、構成に Production.

Duplicating a Build Configuration

Duplicating a Build Configuration

環境間をすばやく切り替えるのを容易にするために、それぞれの構成に対してスキームを作成しましょう。 上部にあるスキームを選択し、メニューから [スキームの管理…] を選択します。 Configurations という名前のスキームを選択し、もう一回クリックします。 Stagingに名前を変更します。

Update Schemes

スキームを選択した状態で、下部にある歯車のアイコンをクリックし、[複製]を選択します。 スキームに Production という名前を付けます。 左側の [実行] を選択し、[ビルド構成] を [Production] に設定します。

Update Schemes

以上です。 これで、ステージングとプロダクション用のビルド構成ができました。 スキームを使用すると、ビルド構成をすばやく簡単に切り替えることができます。

ユーザー定義のビルド設定

先ほど述べたように、特定のビルド構成に固有のデータを管理するには、いくつかのソリューションがあります。 このエピソードでは、ある程度複雑なプロジェクトで私が使用しているソリューションを紹介します。 私が考えているソリューションを並べる前に、開発者がよく使用する別のソリューションを紹介したいと思います。

左側のプロジェクト ナビゲーターでプロジェクトを選択します。 Targets] セクションから [Configurations] ターゲットを選択し、上部の [Build Settings] タブをクリックします。

Target Build Settings

[Build Settings] タブには [Configurations] ターゲットの構築設定が表示されます。 このリストは、自分で定義したビルド設定で拡張することが可能です。 上部の + ボタンをクリックし、[ユーザー定義設定の追加] を選択します。

Adding a User-Defined Setting

ユーザー定義設定に BASE_URL と名前を付けます。 ベース URL を定義することは、バックエンドとやり取りするアプリケーションでは一般的です。

Adding a User-Defined Setting

ユーザー定義設定を定義する利点は何ですか。 ユーザー定義設定を使用すると、ビルド構成ごとに BASE_URL の値を設定することができます。 ユーザー定義設定の左側にある三角形をクリックすると、ビルド構成のリストが表示されます。

Adding a User-Defined Setting

Production および Release の値を https://cocoacasts.com に、Staging の値を https://staging.cocoacasts.com に設定します。

Adding a User-Defined Setting

名前からわかるように、ビルド設定はビルド プロセスで利用可能です。 あなたのコードは、定義したビルド設定に直接アクセスすることはできません。 これは一般的な誤解です。 しかし、解決策はあります。 Info.plistを開いて、新しいキーと値のペアを追加してください。 キーを BASE_URL に、値を $(BASE_URL).

に設定します。

Updating Info.plist

プロジェクトの Info.plist にキーと値のペアを追加する価値は何でしょうか。 key/value ペアの値は、ビルド時に更新されます。 Xcode は、現在のビルド設定を検査し、プロジェクトの Info.plist の BASE_URL キーの値を設定します。

実際に試してみましょう。 AppDelegate.swift を開き、application(_:didFinishLaunchingWithOptions:) メソッドに移動します。 Info.plistファイルの内容にアクセスする必要があります。 これは、BundleクラスのinfoDictionaryプロパティで可能です。 私たちが関心を持っているバンドルは main bundle です。 その名前が示すように、infoDictionary プロパティはキーと値のペアの辞書で、BASE_URL キーの値にアクセスします。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Bundle.main.infoDictionary?) return true}

上部にある Production スキームを選択し、シミュレーターでアプリケーションを実行します。 コンソールの出力を確認します。

Optional(https://staging.cocoacasts.com)

一番上のステージングスキームを選択し、シミュレータでアプリケーションを実行します。 コンソールの出力を確認してください。 かなりいい感じですね。 そうでしょうか。

Optional(https://staging.cocoacasts.com)

セキュリティについて一言

多くの開発者は、このソリューションが非常に便利であると感じています。 そして、それはそうです。 しかし、1 つの問題があります。 App Store からダウンロードされたアプリケーションから Info.plist ファイルを抽出するのは簡単です。 もし、Info.plistファイルにAPIキー、認証情報、その他の機密情報を保存していたらどうなるでしょうか? これは、避けるべき重大なセキュリティ リスクをもたらします。

同じ柔軟性、型安全性、およびセキュリティの向上を提供するシンプルなソリューションを紹介したいと思います。 新しい Swift ファイルを作成し、それを Configuration.swift と名付けます。

Creating a Swift File

Creating a Swift File

名前 Configuration とタイプ String の生の値を持つ enum を定義する。 各ビルド構成に対して、stagingproduction、および release というケースを定義します。

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release}

Configuration enum の実装を続ける前に、Info.plist ファイルを更新する必要があります。 Info.plist を開き、キーと値のペアを追加します。 キーは Configuration で、値は $(CONFIGURATION) です。 CONFIGURATIONの値は自動的に設定されます。 私たちはそれを気にする必要はありません。 その名が示すように、この値はビルドが作成されるビルド構成の名前と同じです。

Update Info.plist

Configuration.swift を再確認してください。 ビルドの構成、つまりプロジェクトの Info.plist ファイルに格納されている値に簡単にアクセスできるようにしたいのです。Configuration型の静的な定数プロパティcurrentを定義してください。 Info.plistファイルに格納されているConfigurationのキーにアクセスし、Stringインスタンスにキャストしています。

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } }()}

Info.plist ファイルから値を使用して Configuration インスタンスを作成します。 初期化に失敗した場合は、別の致命的なエラーをスローします。 Info.plistファイルに格納されている値を小文字にしていることに注意してください。 Configuration インスタンスはクロージャから返されます。

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } guard let configuration = Configuration(rawValue: rawValue.lowercased()) else { fatalError("Invalid Configuration") } return configuration }()}

では、クロージャに一対の括弧を付けることを忘れないでください。 AppDelegate.swiftを開き、現在の設定を出力します。 Staging スキームを選択し、アプリケーションを実行し、コンソールの出力を検査します。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.current) return true}

staging

構成の拡張

私が名前空間を作成するために列挙型を活用することが好きなことは、おそらくご存知でしょう。 現在の Configuration 列挙型の実装をどのように改善できるかをお見せしましょう。 最も簡単な解決策は、URL 型の静的で計算されるプロパティ baseURL を定義することです。 また、このプロパティを静的な定数プロパティとして定義することもできます。 それは個人的な選択です。 各ビルド構成のベース URL を返すために switch ステートメントを使用します。

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } guard let configuration = Configuration(rawValue: rawValue.lowercased()) else { fatalError("Invalid Configuration") } return configuration }() // MARK: - Base URL static var baseURL: URL { switch current { case .staging: return URL(string: "https://staging.cocoacasts.com")! case .production, .release: return URL(string: "https://cocoacasts.com")! } }}

ここで、いくつか指摘したい詳細があります。 まず、defaultケースを使用していません。 私は、各ビルド構成に対して返される値について明確にしています。 これにより、問題を発見しやすくなり、コードがより読みやすく、直感的に理解できるようになります。 次に、感嘆符を使用して、URL 構造体のイニシャライザーが返す値を強制的にアンラップしています。 これは、私が感嘆符を使って値を強制的にアンラップする数少ないシナリオの 1 つです。 これは便利ですが、より重要なのは、ベース URL が nil.

AppDelegate.swift を開き、baseURL 計算プロパティの値を表示することと等しくなってはならないことです。 スキームを Staging に設定して、アプリケーションを実行し、コンソールで出力を検査します。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.baseURL) return true}
https://staging.cocoacasts.com

このパターンには、最初に実装したソリューションに比べていくつかの利点があります。 私たちは Swift の型安全性を利用し、もはやオプションの値を扱っていません。 また、Xcode のオートコンプリートの恩恵を受けることができます。 これらの利点は微妙なものですが、時間が経つにつれてその良さがわかるようになります。

ミックスに名前空間を追加することにより、ケーキにチェリーを乗せましょう。 新しい Swift ファイルを作成し、それを Configuration+DarkSky.swift と名付けます。

Creating a Swift File

Creating a Swift File

Configuration enum の拡張機能を作成し、その拡張機能に名前 DarkSky で enum を定義する。 Dark Sky は、私が時々使用している気象サービスです。

import Foundationextension Configuration { enum DarkSky { }}

DarkSky enum は、String 型の apiKey という静的で一定のプロパティを定義しています。 現在の構成で切り替えを行い、ビルド構成ごとに異なる値を返す。 先ほど述べたように、このプロパティを静的な変数プロパティとして宣言することもできます。 それはあなたの判断次第です。

import Foundationextension Configuration { enum DarkSky { static let apiKey: String = { switch Configuration.current { case .staging: return "123" case .production: return "456" case .release: return "789" } }() }}

このアプローチには、いくつかの利点があります。 Dark Sky API の構成は、うまく名前空間化されています。

AppDelegate.swift を開き、Dark Sky 天気予報サービスの API キーを出力します。 スキームを Staging に設定し、アプリケーションを実行し、コンソールの出力を確認します。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.DarkSky.apiKey) return true}
123

次は? 採用するのは簡単で、一度実装すれば、拡張するのも簡単です。 これは、さまざまな理由から、私が楽しんで使用しているパターンです。 ぜひ試してみてください。