はじめに
こんにちは、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 UIKit import Firebase import UserNotifications @main 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 } } // 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 } // トークンをサーバーに送信する処理をここに追加 } }- トークンの更新通知を受け取り
トークンが更新されると、
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
深山侑花