Firebase Cloud Messagingを使ってアプリに通知機能を実装する

# はじめに
本記事はFirebaseのプロジェクトが作成済みでアプリにすでにFirebaseを導入していることを前提とした内容となっています。また、iOSアプリを対象にしています。
Firebase Cloud Messaging(以降FCM)とは[公式ドキュメント](https://firebase.google.com/docs/cloud-messaging)によると

> Firebase Cloud Messaging（FCM）は、メッセージを無料で確実に送信するためのクロスプラットフォーム メッセージング ソリューションです。

ということです。通常アプリにプッシュ通知を送るにはアプリの設定以外にもAPNs(GCM)に通知送信をするためのアプリケーションサーバーを用意しないといけません。(Pusherなどのアプリで代用も可能)FCMであればAPNsに通知を送るための設定をFirebaseのコンソールから簡単に行うことができます。


# プロジェクトの設定
## Cloud Messagingに必要なライブラリのインストール

```
// Podfile
pod 'Firebase/Messaging'
// install
$ pod install
```
## プッシュ通知に必要なCapabilityを設定
* TARGETS -> Signing & Capability -> + を押下
* その中からBackground ModesとPush Notificationsをダブルクリックで追加
* Background ModesのRemote notifiationsにチェック
![スクリーンショット 2020-09-05 19.32.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/0250e2f6-bc3b-b740-d074-86369136cfbb.png)
この時点で**プロジェクト名.entitlements**が追加されます。
中身を見ると
![スクリーンショット 2020-09-06 11.42.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/64aab633-0813-e6ce-443c-5fbc6a1afcdb.png)
development用が追加されています。他の記事を見るとこれをコピーして本番用を用意してたりしたのですが、根拠となるソースが見つからなかったので[Appleのドキュメント](https://developer.apple.com/documentation/bundleresources/entitlements/aps-environment)を漁ったところ

> Xcode sets the value of the entitlement based on your app's current 
provisioning profile. For example, if you're using a development provisioning
profile, Xcode sets the value to development.

プロジェクトに設定されているProvisioning Profileによって自動的に変更されるようです。
試しにProvisoning ProfileをDistributionに変えたんですが、書き換わることはありませんでした。
どうやらArchiveするときに自動的に値をセットしてくれる仕様のようです。
https://stackoverflow.com/questions/42292363/aps-environment-is-always-development
試しにアーカイブした中身を確認したところ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/bee80f52-f264-9f9c-48bf-756b309b0ce4.png" width=240 />
変更されていました。
また、AppStoreConnectにアップロードしたバイナリも確認しましたが、productionになっていました。
![スクリーンショット 2020-09-06 12.15.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/2fd4ab18-f46d-eb41-cad1-adb913922420.png)

# Push用のCertificateを作成
すでにアプリのIdentifiersが用意されている前提の説明となります。はじめから作る場合は[こちら](https://note.com/t0_inoue/n/nb817e740654b)が参考になるかと思います。

* キーチェーンアクセスより証明書を発行（本番・開発用の計２つ）
* Identifiersをクリック
* Push Notifications -> Configure 
![スクリーンショット 2020-09-05 19.21.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/9e04a5b5-9d77-cd87-f849-1403f50529d8.png)
* Development SSL Certificate -> Create Certificate
![スクリーンショット 2020-09-05 19.22.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/707a36cc-3f4a-00df-6fc2-4f90079a4e8c.png)
* 先程作った証明書を使ってCertificateを作成
* 本番も同様に作成
* ここまで完了するとCertificateに以下２つが作られます
![スクリーンショット 2020-09-06 12.42.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/15e88acc-188b-c320-d7c4-7935410ecacf.png)

* これら2つをダウンロード
* ~.cerをダブルクリックし、キーチェーンに登録
* それぞれ右クリックし、「〜を書き出す」

# FirebaseのCloud MessagingにAPNs証明書を登録
Firebaseコンソールの歯車を押して「プロジェクトを設定」
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/c9852564-d53e-2337-6a6b-ab11d52e7b9e.png" width=360 />
Settings -> Cloud MessagingからAPNs証明書の項目までスクロールし、先程作った証明書をアップロードします
![スクリーンショット 2020-09-06 13.15.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/f7edd516-7014-45f9-163a-7d4ae75841ae.png)

# AppDelegate.swiftに通知を受け取るための処理を追加
AppDelegate.swiftに以下を追加

```Swift
import Firebase
import FirebaseMessaging

class AppDelegate: UIResponder, UIApplicationDelegate {
...
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        FirebaseApp.configure()
        // Optional
        Messaging.messaging().delegate = self
        
        UNUserNotificationCenter.current().delegate = self
        // 通知の許可をユーザーに要求する
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound],completionHandler: { granted, error in
            guard error == nil else { return }
            if granted {
                // registerForRemoteNotificationsは必ずメインスレッドで実行しなければならない
                DispatchQueue.main.async {
                    // Appleプッシュ通知サービスを介してリモート通知を受信するための登録を行う
                    application.registerForRemoteNotifications()
                }
            }
        })
        
        return true
    }
...

}

// Optional
extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
        // テスト送信用のトークン取得
        print("Firebase registration token: \(fcmToken)")
    }
}

extension AppDelegate : UNUserNotificationCenterDelegate {
    // アプリがフォアグラウンドで通知を受け取ったとき
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo
        // Print full message.
        print(userInfo)
        
        // Change this to your preferred presentation option
        completionHandler([[.alert, .sound]])
    }
    
    // ユーザーが通知バナーをタップしたとき
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        // Print full message.
        print(userInfo)
        
        completionHandler()
    }
}
```

実装する上で何点か注意点があります。
まず一つ目として、iOS10未満を対象とするかで通知を受信したときのデリゲートメソッドの実装が異なります。
今回iOS10未満を対象としないため上記実装となります。もし、iOS10未満も対象にする場合は[公式ドキュメント](https://firebase.google.com/docs/cloud-messaging/ios/client?authuser=0#register_for_remote_notifications)を参考に場合分けが必要です。
二つ目の注意点として、registerForRemoteNotificationsを実行する際はメインスレッドで行う必要があるということです。メインスレッドでの実行を明示しない場合は以下警告がでます。
![スクリーンショット 2020-09-06 16.38.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/ab56e783-d878-111c-2499-7340d98fe595.png)
三つ目はMessagingDelegateの実装です。こちらは実装しなくても通知は受け取れます。
ただ、Firebaseコンソール上での通知送信は開発・本番の区別なく送信されるため、テスト送信をしたい場合にはFCMトークンを指定してテスト送信をする必要がありました。
そのため、本番に送る前に手元で通知内容を確認したい場合にはFCMトークンを取得すると便利です。

# FirebaseコンソールからPushを送る
FirebaseコンソールのCloud Messaging -> 通知の作成から新たな通知を発行できます。
![スクリーンショット 2020-09-06 16.58.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/650e4c38-2e94-1c15-c28d-1b06971ff132.png)
ここの「テストメッセージを送信」を選ぶと特定の端末に通知を送ることができます。
![スクリーンショット 2020-09-06 16.57.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/58a04295-9364-18e8-244d-65c67da2572f.png)
先程のAppdelegate内のデリゲートでプリントしたFCMトークンをここに追加して「テスト」を押せば通知を受け取れます。
テスト送信ができればあとは通知設定をしていき、最後に「公開」を押せば本送信されます。
![スクリーンショット 2020-09-06 17.01.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/18cdb2f2-b456-a284-8680-c3a70c9152f1.png)


#参考
* https://firebase.google.com/docs/cloud-messaging/ios/client?authuser=0#method_swizzling_in
* https://dev.classmethod.jp/articles/wwdc-2016-user-notifications-7/
* https://note.com/t0_inoue/n/nb817e740654b