【詳解】Firebase iOS SDKをSwift Package Manager (SwiftPM)で導入する

# 📦 FirebaseのSwift Package Manager対応
Xcode 11よりSwift Package Manager (以下、SwiftPM)でライブラリなどの依存をXcodeのGUI上で管理できるようになりました。
かねてよりbetaとしてSwiftPM対応していたFirebaseもv8.0.0よりついに正式に対応されました :tada:

SwiftPMはApple純正ツールであり、Xcodeとシームレスにライブラリ管理を実現できる点はCocoaPodsやCarthageと一線を画します。
ビルドのための下準備が不要で、プロジェクトのビルドを実行すれば依存パッケージもそのフローの中で追加されます。
そのため、特に新規のライトなiOSプロジェクトなんかでは第一の選択肢に上がってくるのではないでしょうか。
そんな、SwiftPMを用いてFirebase iOS SDKを導入する手順を示したいと思います！

# 🔨 導入手順
もちろん、GoogleのFirebase Documentationにもしっかりとドキュメントが存在しています。
[Install Firebase with Swift Package Manager  |  Firebase Documentation](https://firebase.google.com/docs/ios/swift-package-manager)

基本的にはそれを読めばOKなのですが、色々と端折られていたり、実際のユースケースにおいてつまづく箇所なども少なくありません。
そういったよくあるシチュエーションの手順も含めた情報として機能すればと思い、筆を走らせています。

## 🖥 今回のシチュエーションとゴールについて
今回は、よくある`Debug` / `Release`の2つのBuild Configurationを持ったプロジェクトに対し、Firebaseのサービス群の中でもほとんどのアプリで入れることになるであろう、`Google Analytics`と`Crashlytics`を導入することをゴールとしたいと思います。

それでは、さっそくいってみましょう！

## 1. SwiftPMでのPackage追加
実は、SwiftPMでのPackage追加方法には下記の2つの方法があります。

- Xcode上からGUIで導入する
- `Package.swift` に記述して導入する

今回は、Xcode上からGUIで導入する手順で説明します。
`Package.swift` を用いたこの手順については、公式のFirebase Documentationに記載されているのでそちらを参考にして導入後、次の手順からお読みください :bow:

まずは、Firebaseを導入したいプロジェクトファイルをXcodeで開き、メニューバーよりAdd Packagesを選択します。
<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/c84b6097-639c-df1e-e2de-e1d5220faf7d.png">


すると、下記のようなウィンドウが現れるので、右上のサーチバーにフォーカスを合わせ、下記のGitのURLをペーストします。

```
https://github.com/firebase/firebase-ios-sdk.git
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/52b07f28-5fcf-161b-d51a-c100a5d92845.png)


すると、firebase-ios-sdkが表示されるので、Dependency RuleとAdd to Projectに任意のものを選択して右下のAdd Packageを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/7e2cb2fb-dca0-274d-b946-f147698200b0.png)

なお、Dependency RuleはCocoaPodsやCarthageと同じようなメジャーバージョンが上がらない最新を入れるための記述 `Up to Next Major Version` や直接BranchやCommitを指定することができるなど柔軟なルールが選択可能です。
今回は、デフォルトのUp to Next Major Versionかつminimumに8.0.0を指定した状態とします。
詳しい内容が気になる方はこちらをご覧ください。
[Adding Package Dependencies to Your App | Apple Developer Documentation](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app)
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/f771b860-c15e-31b5-2c59-ffcb6b28265b.png">

Add Packageを選択後、インジケーターが表示されパッケージのフェッチが行われるので、しばらく待ちます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/727ec183-e09f-f899-7e44-c72606ed4cbf.png)

フェッチが完了すると、Package Productを選択する画面が表示されます。
今回は、`FirebaseAnalyticsWithoutAdIdSupport`と`FirebaseCrashlytics`にチェックし、Add Packageを選択します。
`FirebaseAnalyticsWithoutAdIdSupport`は、IDFAが不要（iOS 14.5のApp Tracking Transparencyの対応が不要）なFirebaseAnalyticsのパッケージです。
詳しくは下記のDocumentationをご覧ください。
[Get started with Google Analytics  |  Firebase Documentation](https://firebase.google.com/docs/analytics/get-started?platform=ios#add-sdk)
<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/d6e5ad80-d43f-1dc7-a723-6b8fb0bcb33d.png">

すると、Project Navigatorの下部にPackage Dependenciesという欄が表示され、諸々の依存がプロジェクトにインストールされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/5eba69db-551f-a2f3-59e3-27aec96707d5.png)

これで、この手順は完了となります。
ここまで一行もコードを書いてないですね :clap: 

## 2. Firebaseプロジェクトを開発用/本番用の2つ作成する
Google AnalyticsやCrashlyticsといった解析系サービスは、開発用と本番用で分けておくことに越したことはありません。
開発中のAppの各種イベントやクラッシュがプロダクトの方に紛れてしまっては、解析や集計に支障が出る恐れがあるからです。
そこで、今回は開発用および本番用の2つのFirebaseプロジェクトを作成していきます。

### 2.1 開発(Debug)用のBundle Identifierを作成する
Firebaseプロジェクトを作る前に、やっておくことがあります。
それは、Debug用のBundle IdentifierをRelease用と分けることです。
これによって、異なるFirebaseプロジェクトを設定することが可能になります。

下記の画像にならって、DebugコンフィグのBundleIDを書き換えます。
一応手順をテキストでも起こしておきます。

1. Project Navigatorからプロジェクトを選択
2. TARGETS下のAppを選択
3. Build Settingsタブを選択
4. 検索バーから `bundle identifier` と入力し、Return
5. Packaging配下のProduct Bundle IdentifierのPulldownを選択
6. Configurationごとの設定が可能になるので、DebugのBundleIDのsuffixに`.debug`をつける（任意の名前でOKです）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/a74b9eab-79a4-ec96-bd70-69e541ac5228.png)

Appの中にExtensionなど複数のTargetが存在する場合は、同じ手順をTarget分、繰り返します。

:warning: この際、watchOSに対応しているアプリなどは`Watck Kit Companion Bundle Identifier`といった`Info.plist`に直接BundleIDを記述している場合がありえます。その場合、DebugコンフィグのBundleIDを分けると突合できずにBuildに失敗してしまうので、`User Defined Settings`に変数を作成して定義し、Info.plistのValueにはそれを指定し、定義した変数の値はConfigurationごとに設定するといった手順が必要になる場合があるので、覚えておくと良いでしょう。

### 2.2 Firebaseプロジェクトの作成 / Google Analyticsの追加
:warning: この手順は開発用と本番用で2回同じ動作を行います。それぞれ読み替えて手順を行ってください。

[Firebaseコンソール](https://console.firebase.google.com/u/0/)を開き、プロジェクトを追加を選択します。
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/c02ecff7-fdc3-3543-9fd5-61f81b23b706.png">

開発用は`<プロジェクト名>-dev`、本番用は`<プロジェクト名>`といった名前で作成し続行を選択しましょう。(もちろん名前は任意です)
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/383f1c31-b77a-295c-5ec3-4158e8fd0901.png">

続いて、Google Analyticsを有効にするか聞かれるので今回は導入するためチェックのまま続行します。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/9e065b85-c52c-27dc-1cc4-f89ffa0f5f7e.png">

Google Analyticsのアカウントをpulldownより選択します。アカウントがなければ新しいアカウントを作成を選択して新規作成します。
選択できたらプロジェクトを作成を選択するとプロジェクトの作成が完了します。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/333dbfc6-9626-26bd-c87f-dfecbfe415bf.png">

この画面が表示されたら続行を押すとプロジェクトのコンソール画面が開きます。
<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/95e977ae-d3dd-e9cf-cf51-8f4bdcacce25.png">

コンソール画面が開いたらサイドバーよりDashboardを選択しAnalyticsの画面に遷移します。
そして、iOSを選択してアプリを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/1b54ddb5-bc7e-b4cf-7895-07708dfb7af4.png)

iOSアプリへのFirebaseの追加の画面に遷移するので、それぞれの項目で必要な手順を以下の表に示します。
:warning: Firebase上に表示されてる手順に従わず、必ず下記表の手順を実施するようにしてください。

この手順1はFirebase上の案内通りに行ってOKです。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/65240c3a-07d1-e23a-3210-3ae3f80e6380.png">

続いて、手順2ではファイルをダウンロードしたら、開発用は`GoogleService-Info-Debug.plist`に、本番用は`GoogleService-Info-Release.plist`にリネームしておきます。
プロジェクトへの追加はまだやらなくて良いです。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/4f4b92c9-83c7-a06d-e310-6961bf7b9709.png">

手順3、4、5は何もせず次の手順へ進んでOKです。

一応、上記の手順を表にまとめましたので、参考にしてください。

No. | 手順
--- | ---
1 アプリの登録 | Firebase上の手順通り
2 設定ファイルのダウンロード | `GoogleService-Info.plist`をダウンロードし次へ<br />ファイルは開発用は`GoogleService-Info-Debug.plist`に<br />本番用は`GoogleService-Info-Release.plist`にリネームしておく<br />（後の手順でプロジェクトにファイルを配置するのでここではしない）
3 Firebase SDK の追加 | 何もせず次へ
4 初期化コードの追加 | 何もせず次へ
5 次のステップ | コンソールに進むを選択

これでFirebaseプロジェクトの作成及びAnalyticsの追加は完了です。

### 2.3 Crashlyticsの追加
:warning: こちらの手順も開発用/本番用のそれぞれのプロジェクトで実施してください。

サイドバーからCrashlyticsを選択し、Crashlyticsの画面に遷移したらCrashlyticsを有効にするを選択します。
手順はこれだけです。（実際にクラッシュを飛ばす手順は後で示すので何もしなくて良いです）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/9da405e7-dd95-6638-7fb4-6f5eb93a4b37.png)

これで、Firebase側の設定は全て完了となります。

## 3. アプリ上でFirebaseを起動する
最後に実際にプロジェクトをビルドしてFirebaseによる解析を開始する手順を示します。

### 3.1 GoogleService-Info.plistの配置
Analyticsの追加の際のFirebase上の手順には、下記のようにGoogleService-Info.plistファイルをXcodeプロジェクトのルートに移動してくださいという説明がありましたが、この手順を本手順では実施しませんでした。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/4f4b92c9-83c7-a06d-e310-6961bf7b9709.png">

その理由は、**開発用と本番用の2つのplistファイルを同居させたいから**でした。

まずは、2.2の手順で実施しておいたリネーム済みの開発用 (`GoogleService-Info-Debug.plist`) と本番用 (`GoogleService-Info-Release.plist`) の2つのplistファイルをプロジェクトに追加します。
この際、Xcodeプロジェクトのメインターゲットのルートディレクトリから `Firebase` というディレクトリを切って、その中にドラッグアンドドロップします。
なお、このディレクトリの名前や構成は任意のもので構いません。

下記のように配置できればOKです。
<img width=320 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/239d76a5-2cf1-703e-02a3-b60798c798c3.png">

続いて、配置したファイルがビルド時にターゲットのルートに配置されるようなScriptを配置します。
Build Phases上でRun Script Phaseを追加することによって、環境ごとのビルド時に該当のplistファイルがルートに配置されます。
以下のようにして、Run Script Phaseをプロジェクトに追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/caf814fa-c463-64cc-8cb1-104c98e023e7.png)

Scriptはこんな感じです。
（ディレクトリ構成を任意のものにした場合はパスを読み替えて記述してください）

```sh
if [ "${CONFIGURATION}" == "Release" ]; then
  cp "${PROJECT_DIR}/${PROJECT_NAME}/Firebase/GoogleService-Info-Release.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
else
  cp "${PROJECT_DIR}/${PROJECT_NAME}/Firebase/GoogleService-Info-Debug.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
fi
```

このScriptを貼り付け、下記のようになっていればOKです。（Run Scriptの名前は任意です）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/bf0674b4-369d-ebda-9aff-689f0aa3910a.png)

なお、本手順は下記の記事を参考にさせていただきました :pray: 
[開発版とリリース版でFirebaseのGoogleService-Info.plistを切り替える - Qiita](https://qiita.com/takehilo/items/4437bed61665e2c955ed)

### 3.2 Google Analytics用の設定の追加

さて、再掲ですが、こちらのドキュメントには注釈 (`Product Specific Considerations`) が書いてあります。
[Install Firebase with Swift Package Manager  |  Firebase Documentation](https://firebase.google.com/docs/ios/swift-package-manager#product-specific-considerations)

案内の通り、`-ObjC` linker flagを追加していきます。

```
Google Analytics requires adding the -ObjC linker flag to your target's build settings.
```

下記のようにして、 `Other Linker Flags` に `-ObjC` flag を追加してあげればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/82be24c8-15c4-18b0-e167-acfe5b0457f8.png)

### 3.3 Crashlytics用の設定の追加

つづいて、Crashlytics用の設定を追加していきます。
`Product Specific Considerations` には下記のような記載があります。

`Crashlytics requires you to upload debug symbols.`

`You can use a run script for Xcode to automatically upload debug symbols post-build. Find the run script here:`

```sh
${BUILD_DIR%Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run
```

3.1の手順と同じやり方でRun Script Phaseを追加します。
下記のようなScriptが設定できていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/18d60276-2f30-06bf-e313-6bbb99062e3d.png)

また、Crashの解析にはdSYMファイルが必要となりますが、DebugビルドはデフォルトではdSYMを生成する設定となっていません。
下記の手順でDebugビルドでもdSYMを生成するように `debug information format` を `DWARF with dSYM File` に変更しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/885b27d6-37a6-6c34-d8d9-557f44855779.png)


### 3.4 アプリ起動時にFirebaseを起動させる

こちらの手順も先ほど実施しなかった手順となります。
`AppDelegate` の起動時に最初に通るDelegateメソッドの `didFinishLaunchingWithOptions` に下記のコードを加えます。

```swift:AppDelegate.swift
import Firebase // Firebase関連の処理を呼びさせるようにimport

...


final class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // Firebaseの起動
        FirebaseApp.configure()

        return true
    }
}
```

:warning: SwiftUI Appを利用しているプロジェクトでは、AppDelegateがない場合もあるかと思います。
`App.swift` の中に下記のような設定を加えることで、AppDelegateにライフサイクル処理を委譲できるオプションがあるので、それを利用するのが良いでしょう。

```swift:SampleApp.swift
import SwiftUI
import Firebase

@main
struct SampleApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

final class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // Firebaseの起動
        FirebaseApp.configure()

        return true
    }
}
```

これで全ての準備が完了です！お疲れ様でした。

## 4. 動作検証
お待たせしました。いよいよ動作検証に入っていきます。

### 4.1 Google Analyticsの動作確認
アプリを起動するだけでAnalytics上にイベントが自動的に飛ぶようになっています。
まずはプロジェクトをDebugコンフィグでBuildしてRunしましょう。
しばらくしたら、Firebaseコンソール上からDashboardを開いて確認すると、データの反映を確認できると思います。
（反映されない場合は、アプリをバックグラウンドにしてからフォアグラウンドに復帰させるなどの手順をお試しください）

念の為、Releaseコンフィグでも同様にちゃんとデータが反映されるか確認するのが安全です。

`first_open` のイベントが飛んでいるのが確認できます :eyes:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/0bb42dd9-0076-f798-1cab-ffa0edc5b27f.png)


### 4.2 Crashlyticsの動作確認
Crashlyticsの確認には、Crashを発生させないことには話になりません。
そこで簡単なCrashの発生方法を示しておきます。

#### UIKit Appの場合
UIKitは公式に書いてあるやり方でやるのがいいでしょう。
[Get started with Firebase Crashlytics  |  Firebase Documentation](https://firebase.google.com/docs/crashlytics/get-started?authuser=0&platform=ios#force-test-crash)

```swift:ViewController.swift
import UIKit

final class ViewController: UIViewController {
  override func viewDidLoad() {
      super.viewDidLoad()

      // Do any additional setup after loading the view, typically from a nib.

      let button = UIButton(type: .roundedRect)
      button.frame = CGRect(x: 20, y: 50, width: 100, height: 30)
      button.setTitle("Test Crash", for: [])
      button.addTarget(self, action: #selector(self.crashButtonTapped(_:)), for: .touchUpInside)
      view.addSubview(button)
  }

  @IBAction func crashButtonTapped(_ sender: AnyObject) {
      let numbers = [0]
      let _ = numbers[1]
  }
}
```

#### SwiftUI Appの場合
SwiftUIのケースも載せておきます。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        Button(
            action: {
                let numbers = [0]
                let _ = numbers[1]
            },
            label: {
                Text("Test Crash")
            }
        )
    }
}
```

上記の実装を行ったら、実際にBuildしてRunし、一度Debugは中止します。
その後、Debugせずに普通にアプリを起動させ、このCrashを意図的に発生させるようにボタンをタップします。
<img width=320 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/9f7b062c-ee3a-80e3-7486-5d47a94f44a2.png">

そしてさらに、もう一度アプリを起動ししばらく待ちます。
:warning: Crashをさせた後、再度アプリを起動してしばらく経過させるのがコツとなります。このタイミングでCrashの情報がFirebase上に送信されます。もし送信されてなさそうであればアプリをバックグラウンドにしてからフォアグラウンドに復帰させるなどの手順をお試しください。

下記のようにCrashlyticsの画面に該当のクラッシュ情報が送信されたのが確認できました！
Crashlyticsも同様にReleaseビルドでも動作検証をしておくことをオススメします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128422/ba83fdeb-a169-67e0-5a42-e573062f706e.png)


# おわりに
いかがだったでしょうか。
だいぶ詳細に説明したのでかなり長くなってしまいましたが、公式にはない実際のユースケースに沿った手順も含めて示すことができたので、ハマりやすい罠もだいぶ踏み抜けたような気がします。
この資料がどなたかの役に立てば幸いです！お読みいただきありがとうございました :bow: