課題
プッシュ通知において、ユーザーに明示的に通知バナーなどを見せることなく、アプリに特定の処理をさせるための Silent Push (Silent Notification) を実現したい。今回はFCMを利用するが、公式のリファレンス通りではどう頑張ってもうまく行かず時間を溶かしてしまったのでメモ
現在地
一般的なバナーありのNotificationは正常に動作しているが、逆立ちしても Silent Push が実現できない
- 環境
- Swift 5
- iOS 15(13以降であれば同じ)
結論
① ペイロードが間違っている
Silent Push を実現するためには、ペイロードに以下が必要。
content-available: true
- ヘッダの設定
apns-push-type: "background"
apns-priority: "5"
FIrebase Admin SDK (Node.js) で実装するなら、こんな感じになる
const message = {
apns: {
headers: {
"apns-push-type": "background",
"apns-priority": "5"
},
payload: {
aps: {
contentAvailable: true
}
}
},
data: {
hoge: "fuga"
},
token: "トークン"
};
await admin.messaging().send(message)
// エラー処理などは省略
② Method Swizzling を無効にする
Firebase iOS SDK は、何かと気が利く。デフォルトでは、APNsのトークンを発行通知を受け取るメソッドに介入して、Firebaseへのトークンの登録処理を肩代わりしてくれたりする(Method Swizzling = 実装入れ替え)のだが、これが厄介者だった!
Silent Push を受け取った際に呼ばれる func application(_ application: UIApplication, didReceiveRemoteNotification...
が、このMethod Swizzlingの影響を受けて全く呼ばれなくなってしまうのだ。
Silent Push を実現するには、これを無効にする必要がある。詳細はこちらにある通りなのだが、要は以下のように変更する。
Info.plist
に定義を追加
- Key:
FirebaseAppDelegateProxyEnabled
- Value:
NO
APNsトークンのFCMへの登録を自力で行う
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
// 残りの処理...
}
これで、 Silent Push によって以下のメソッドが呼ばれるようになった。
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
Messaging.messaging().appDidReceiveMessage(userInfo)
print(userInfo["hoge"]) // -> fuga
// 処理 (30秒以内)
completionHandler(.newData) // 処理結果をiOSに報告するのを忘れずに
}
めでたしめでたし。
この解決策の懸念
Method Swizzling は、 Analytics 関連でも様々な気配りをしてくれているようなので、Analyticsによる効果検証を行いたい場合はもう少し深堀りする必要がありそう。