はじめに
こんにちは、iOSアプリ開発を担当している深山です。
私が担当しているアプリでは、Firebase Analyticsで取得しているログの見直しに伴い、新規のFirebaseプロジェクトに移行する取り組みを行いました。
しかし、動作確認時に、PUSH通知に利用していたFirebase Cloud Messaging(以下、FCM)の登録トークンが取得できなくなっていることがわかりました。
今回はその問題をどのように解決したかを紹介します。
FCMとは
Firebase Cloud Messagingは、Googleが提供するメッセージ配信基盤で、Android、iOS、WebアプリケーションなどにPUSH通知を送信するためのサービスです。FCMを利用することで、開発者はユーザーにタイムリーな情報を提供し、リテンション率やエンゲージメントを向上させることができます。
FCM登録トークンについて
FCM登録トークンの概要
FCMを用いたPUSH通知を利用するには、登録トークンが必要になります。
登録トークンは、アプリケーションのインスタンスを一意に識別するための文字列です。これにより、FCMは特定のデバイスやアプリケーションにメッセージを送信することができます。以下に、登録トークンの主な特徴と役割について説明します。
- 一意性
- 登録トークンはデバイスごとに一意であり、アプリケーションのインスタンスごとに異なるため、特定のユーザーに対して個別にメッセージを送信できます。
- 有効期限
- 登録トークンには有効期限があります。期限が切れると、アプリは新しいトークンを取得する必要があります。これにより、セキュリティが強化され、無効なトークンへのメッセージ送信を防ぎます。
- 生成と更新
- トークンは初回アプリ起動時に生成され、その後必要に応じて更新されます。更新のタイミングには、トークンの有効期限切れやユーザーのアプリ再インストールなどがあります。
- 利用方法
- アプリは取得したトークンを、PUSH通知送信用の任意のサーバーに送信し、サーバーがこのトークンを使用して特定のデバイスに対してメッセージを送信します。
登録トークンの取得方法
ここではiOSアプリでの登録トークンの取得方法を説明します。
- Firebaseのセットアップ
Firebaseコンソール上から取得したGoogleService-Info.plistをプロジェクトに追加し、任意の方法でFirebaseSDKをインストールします。具体的なセットアップ方法は割愛します。
-
AppDelegeteの設定
AppDelegate.swiftに必要な設定を行います。
import UIKitimport Firebaseimport UserNotifications@mainclass AppDelegate: UIResponder, UIApplicationDelegate {var window: UIWindow?func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {// Firebase初期化FirebaseApp.configure()// プッシュ通知の許可をリクエストUNUserNotificationCenter.current().delegate = selflet authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]UNUserNotificationCenter.current().requestAuthorization(options: authOptions,completionHandler: {_, _ in })application.registerForRemoteNotifications()// 登録トークン取得のためのデリゲートを設定Messaging.messaging().delegate = selfreturn true}}// MARK: - UNUserNotificationCenterDelegateextension AppDelegate : UNUserNotificationCenterDelegate {// フォアグラウンドで通知を受信したときに呼ばれるfunc userNotificationCenter(_ center: UNUserNotificationCenter,willPresent notification: UNNotification,withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {completionHandler([.alert, .badge, .sound])}}// MARK: - MessagingDelegateextension AppDelegate : MessagingDelegate {// 新しい登録トークンを取得したときに呼ばれるfunc messaging(_ messaging: Messaging,didReceiveRegistrationToken fcmToken: String?) {guard let fcmToken else {return}// トークンをサーバーに送信する処理をここに追加}} - トークンの更新通知を受け取り
トークンが更新されると、 messaging(_:didReceiveRegistrationToken:)メソッドが呼ばれます。このメソッド内で新しいトークンを取得し、必要に応じてサーバーに送信します。
これで、iOSアプリでFCM登録トークンを取得し、更新をハンドリングする処理が実装できます。
プロジェクト移行で登録トークンを取得できなくなっていた原因
さて、FCMがなにかと、登録トークンの取得方法について理解したところで、本題に戻ります。
今回、登録トークンを取得できなかった原因は、更新タイミングにありました。
登録トークンの生成および更新タイミングは以下になります。
- アプリの初回起動時
- アプリを新しいデバイスで復元した場合
- ユーザーがアプリをアンインストール/再インストールした場合
- ユーザーがアプリのデータを消去する場合
上記の場合に、FCM SDKは新規または既存のトークンを取得し、 messaging(_:didReceiveRegistrationToken:)メソッドを介して、登録トークンを提供します。
今回のFirebaseプロジェクト移行作業では、あくまでも、既にリリースされているアプリ内のplistファイルを、アプリのバージョンアップデート時に差し替えるのみです。
したがって、上記の更新タイミングにはあてはまりません。
また、登録トークンはAppleが提供する識別子であるApple Push Notification Service(以下、APNs)トークンと関連付けられています。plistファイルを差し替えたとしても、APNsトークンには移行前のFirebaseプロジェクトで取得した登録トークンが紐づいてキャッシュされている状態です。
そのため、新規Firebaseプロジェクトの登録トークンが生成されず、アプリ上で取得できない状態となっていました。
登録トークンを再取得する方法
前述のとおり、
messaging(_:didReceiveRegistrationToken:)は、初回起動や再インストールなどの特定条件でのみ呼び出されます。また、古いトークンが残っているので、バージョンアップデートしても呼び出されません。
そのため、別の方法で登録トークンを取得する必要があります。
以下のように token(completion:)を使用して、直接トークンを取得できます。
Messaging.messaging().token { token, error in if let error { print("Error fetching FCM registration token: \(error)") } else if let token { print("FCM registration token: \(token)") self.fcmRegTokenMessage.text = "Remote FCM registration token: \(token)" } } |
注意点
- method swizzlingを無効にしている場合
- Swift UIアプリの場合
- いずれかのデリゲートに別のクラスを使用している場合
上記のいずれかの場合、以下のように明示的にAPNsトークンを設定しているかと思います。
func application( application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { Messaging.messaging().apnsToken = deviceToken } |
先ほど紹介したように、登録トークンはAPNsトークンと関連付けられている必要があります。
明示的にAPNsトークンを取得している場合は、APNsトークンの設定後に登録トークン取得するよう実装しましょう。これにより、登録トークンの取得およびPUSH通知の配信が可能となります。
func application( application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { Messaging.messaging().apnsToken = deviceToken // 登録トークンを取得する Messaging.messaging().token { token, error in if let error { print("Error fetching FCM registration token: \(error)") } else if let token { print("FCM registration token: \(token)") } } } |
これを実装したAppDelegateのコードが次のようになります。
import UIKit import Firebase import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // Firebase初期化 FirebaseApp.configure() // プッシュ通知の許可をリクエスト UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: {_, _ in } ) application.registerForRemoteNotifications() // 登録トークン取得のためのデリゲートを設定 Messaging.messaging().delegate = self return true } func application( application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { Messaging.messaging().apnsToken = deviceToken // 登録トークンを取得する Messaging.messaging().token { token, error in if let error { print("Error fetching FCM registration token: \(error)") } else if let token { print("FCM registration token: \(token)") } } } } // MARK: - UNUserNotificationCenterDelegate extension AppDelegate: UNUserNotificationCenterDelegate { // フォアグラウンドで通知を受信したときに呼ばれる func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { completionHandler([.alert, .badge, .sound]) } } // MARK: - MessagingDelegate extension AppDelegate: MessagingDelegate { // 登録トークンを取得したときに呼ばれる func messaging( _ messaging: Messaging, didReceiveRegistrationToken fcmToken: String? ) { guard let fcmToken else { return } // トークンをサーバーに送信する処理をここに追加 } } |
まとめ
FCMを利用していて、かつFirebaseプロジェクトを移行する場合、登録トークンを明示的に直接取得するようにすることで、PUSH通知に関してデグレードなく実装することが可能です。
その際、APNsトークンの設定後に処理が実行されるように注意をしましょう。
参考文献
- Firebase Cloud Messaging
- インスタンス ID データを管理する | Firebase
- FirebaseInstallations
- Android で Firebase Cloud Messaging クライアント アプリを設定する
- Apple プラットフォームで Firebase Cloud Messaging クライアント アプリを設定する
- アプリサーバーからの送信リクエストを作成する | Firebase Cloud Messaging
- FirebaseMessaging Framework Reference
- About FCM messages | Firebase Cloud Messaging
この記事を書いた人
![深山侑花](https://techblog.recochoku.jp/wp-content/uploads/2021/12/F2761D01-6BB6-4BCF-AF0B-6D00A9847805-180x180.jpeg)
最近書いた記事
2024.06.19Firebaseプロジェクト移行時のFirebase Cloud Messaging登録トークン再取得方法
2023.03.30【Swift】iOS 16で画面回転禁止処理を行っている箇所で無限ループが発生しクラッシュする
2023.03.27SwiftLintでカスタムルールを追加してみた
2022.09.30【Swift】CoreDataのユニットテストの環境構築