目次

目次

Firebaseプロジェクト移行時のFirebase Cloud Messaging登録トークン再取得方法

アバター画像
深山侑花
アバター画像
深山侑花
最終更新日2025/12/25 投稿日2024/06/19

はじめに

こんにちは、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アプリでの登録トークンの取得方法を説明します。

  1. Firebaseのセットアップ Firebaseコンソール上から取得したGoogleService-Info.plistをプロジェクトに追加し、任意の方法でFirebaseSDKをインストールします。具体的なセットアップ方法は割愛します。
  2. 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
           }
           // トークンをサーバーに送信する処理をここに追加
       }
    }
    
  3. トークンの更新通知を受け取り トークンが更新されると、 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トークンの設定後に処理が実行されるように注意をしましょう。

参考文献

アバター画像

深山侑花

目次