LoginSignup
3
5

More than 3 years have passed since last update.

【Swift】アプリを再インストールした際にFCMのリモート通知が届かなくなった

Posted at

環境

  • Xcode Version 12.3 (12C33)
  • iOS 13 以上
  • Firebase/Messaging (7.1.0)

初期実装

AppDelegate では以下のような初期化処理を行っていた

AppDelegate.swift

import FirebaseCore
import FirebaseMessaging

import UIKit


// MARK: - AppDelegate

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        self.setupFirebase(application: application)

        let window = UIWindow()
        self.window = window

        return true
    }

    private func setupFirebase(application: UIApplication) {

        FirebaseApp.configure()

        Messaging.messaging().delegate = self

        application.registerForRemoteNotifications()
    }

FCMトークンが必要な場面で以下のように呼び出しを行っていた

import FirebaseMessaging

// 中略

let fcmToken = Messaging.messaging().fcmToken

問題発覚

  • アプリをアンインストール後再インストールするとリモート通知が届かなくなった
  • サーバーのログを確認すると "error":"No information found about this instance id." とエラーが出ていた

原因

  • 再インストール後 Messaging.messaging().fcmToken を利用するとアンインストール前の FCM トークンが返却されていた

対応

  • Messaging.messaging().token(completion:) を一度呼び出すことで completion 呼び出し後には有効なトークンが取得できるようになる
  • setupFirebase(application:) を以下の通り修正
AppDelegate.swift
    private func setupFirebase(application: UIApplication) {

        FirebaseApp.configure()

        Messaging.messaging().delegate = self

        // iOSは起動時に意図的にFCMトークンを取得する処理を追加することで「アプリが再インストール」された場合も対応できる
        Messaging.messaging().token { (_, error: Error?) in

            guard error == nil else { return }

            application.registerForRemoteNotifications()
        }
    }

検証

検証1: アプリ再インストール時の挙動

MessagingDelegatefunc messaging(_ messaging:didReceiveRegistrationToken fcmToken:) を以下の通り実装

AppDelegate.swift
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {

        print("*** fcmToken: \(fcmToken ?? "")")
        print("*** fcmToken: \(Messaging.messaging().fcmToken ?? "")")
    }
  • didReceiveRegistrationTokenfcmTokenMessaging.messaging().fcmTokenが一致することは確認している
  1. 通知が届く状態のアプリをデバッグ実行しdidReceiveRegistrationToken デリゲートメソッドに届いたFCMトークンを確認
  2. FCMトークンが有効か curl コマンドで確認
  3. デバッグ実行を中止しアプリをアンインストール
  4. アプリをデバッグ実行(再インストール)しdidReceiveRegistrationToken デリゲートメソッドに届いたFCMトークンを確認
  5. FCMトークンが有効か確認
  6. 1分程度放置しdidReceiveRegistrationTokenデリゲートメソッドが再び発火するか確認
  7. PUSH通知が届くか確認
  8. アプリを再起動(デバッグ実行)
  9. didReceiveRegistrationToken デリゲートメソッドに届いたFCMトークンを確認
  10. FCMトークンが有効か確認
  11. 1分程度放置しdidReceiveRegistrationTokenデリゲートメソッドが再び発火するか確認
  12. PUSH通知が届くか確認

結果

  1. トークンAを得る
  2. トークンAは curl 有効であることがわかる
  3. 実行可
  4. トークンAを得る
  5. トークンAは curl 無効であることがわかる( "error":"No information found about this instance id."
  6. 発火しない(トークンAは更新されない)
  7. 届かない
  8. 実行可
  9. トークンBを得る
  10. トークンBは curl コマンドより有効であることがわかる
  11. 発火しない(トークンBは更新されない)
  12. 届く

検証2: トークン取得メソッドを変更しエラーハンドリングを試みる

MessagingDelegatefunc messaging(_ messaging:didReceiveRegistrationToken fcmToken:) を以下の通り実装

AppDelegate.swift
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {

        print("*** fcmToken: \(fcmToken ?? "")")
        print("*** fcmToken: \(Messaging.messaging().fcmToken ?? "")")

        Messaging.messaging().token { (token: String?, error: Error?) in

            print("*** Messaging.messaging().token")
            print("*** fcmToken: \(token ?? "")")
            print("*** fcmTokenError: \(error?.localizedDescription ?? "nothing")")
        }
    }

以下の手順を試みる

  1. 通知が届く状態のアプリをデバッグ実行しdidReceiveRegistrationToken ログを確認
  2. FCMトークンが有効か curl コマンドで確認する
  3. デバッグ実行を中止しアプリをアンインストール
  4. アプリを再インストール(デバッグ実行)しdidReceiveRegistrationToken 内部のログを確認

結果

  1. 以下の通りトークンAを得る
*** fcmToken: [トークンA]
*** fcmToken: [トークンA]
*** Messaging.messaging().token
*** fcmToken: [トークンA]
*** fcmTokenError: nothing
  1. トークンA はcurl コマンドより有効であることがわかる
  2. 実行可能
  3. 以下のログを得る
*** fcmToken: [トークンA]
*** fcmToken: [トークンA]
*** fcmToken: [トークンB]
*** fcmToken: [トークンB]
*** Messaging.messaging().token
*** fcmToken: [トークンB]
*** fcmTokenError: nothing
*** Messaging.messaging().token
*** fcmToken: [トークンB]
*** fcmTokenError: nothing

curl コマンドによる追加検証で以下が判明している

  • 上記ログ2行目時点ではトークンAは有効
  • 上記ログ3行目時点ではトークンAは無効
  • トークンBは有効

検証結果まとめ

  1. アプリを再インストールした際にdidReceiveRegistrationTokenでアンインストール前に利用していたトークンが返却される
  2. Messaging.messaging().fcmToken では didReveiveRegistrationToken で得られるトークンと同じものを取得する
  3. didReceiveRegistrationToken発火後何らかの原因でアンインストール前に利用していたトークンが無効化される
  4. 無効化された後アプリを再起動することで正常に通信可能なトークンをdidReceiveRegistrationTokenMessaging.messaging().fcmTokenで利用可能となる
  5. Messaging.messaging().token(completion:)メソッドを用いることで再インストール後にも有効なFCMトークンを得ることができる
  6. その際Messaging.messaging().token(completion:)メソッドにはエラーが入ってこない
  7. Messaging.messaging().token(completion:)メソッドを呼び出すとdidReveiveRegistrationTokenが発火し、内部で有効なFCMトークンを得ることができる
  8. Messaging.messaging().token(completion:)メソッドを呼び出した後にはMessaging.messaging().fcmTokenプロパティで有効なFCMトークンを得ることができる
3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5