Notification Service Extensionは、ユーザーに表示される前にプッシュ通知をインターセプトし、コンテンツを変更できるようにするiOS拡張機能です。この拡張機能は、暗号化されたデータを復号し、メディア添付ファイルをダウンロードし、テキストを変更したり、カスタムフィールドを追加したりできます。Apple Developer, 2025によると、Notification Service Extensionはバックグラウンドでタスクを完了するために最大30秒の時間があります。
重要なポイント
Notification Service Extensionは、プッシュ通知が表示される前に実行されるiOSのアプリ拡張機能です。サーバーが最小限のペイロードを送信し、拡張機能が画像のダウンロード、データの復号、テキストの置換などのコンテンツを追加します。ユーザーは既に処理された通知を表示します。
iOSがプッシュ通知を受信すると、システムはアプリに拡張機能が存在するかどうかを確認します。拡張機能が登録されている場合、iOSはそれをバックグラウンドプロセスで起動し、didReceive(_:withContentHandler:)メソッドを介して通知コンテンツを渡します。拡張機能はコンテンツを処理し、変更されたコンテンツでcontentHandlerを呼び出します。拡張機能が30秒以内に完了しない場合、システムは元の通知を表示します。
Service Extensionは、通知コンテンツがデバイス上での処理を必要とするシナリオで必要です。安全な通信:サーバーが暗号化されたペイロードを送信し、拡張機能がローカルで復号します。リッチメディア通知:サーバーが画像URLを送信し、拡張機能がダウンロードして添付します。動的ローカライゼーション:拡張機能がデバイスの言語でテキストを置換します。
| シナリオ | 拡張機能なし | 拡張機能あり |
|---|---|---|
| 画像 | サポートされていません | ダウンロードされて表示されます |
| 暗号化 | サーバーが鍵を保持 | デバイス上で復号 |
| テキスト | サーバーで固定 | 動的置換 |
| 検証 | チェックされません | 悪意のある通知をキャンセル |
Notification Service Extensionは制限された環境で動作します。最大実行時間は30秒です。拡張機能は(App Groupを除く)メインアプリのストレージにアクセスできません。拡張機能のサイズは50 MBに制限されています。時間を超過すると、システムは元のコンテンツでcontentHandlerを呼び出し、すべての変更が失われます。
Xcodeでは、Notification Service ExtensionはNotification Service Extensionタイプの新しいtargetを作成して追加します。XcodeはUNNotificationServiceExtensionを継承するテンプレートNotificationServiceクラスを生成し、didReceiveとserviceExtensionTimeWillExpireの2つのメソッドを持ちます。
Xcodeで、File → New → Target → Notification Service Extensionを選択します。名前(例:PushNotificationService)を入力し、Swift言語を選択します。targetがメインアプリに追加され、正しい署名があることを確認します。作成後、NotificationService.swiftクラスが基本的な実装とともに生成されます。
拡張機能のInfo.plistには、NSExtensionキーとサブキーのNSExtensionPointIdentifier(com.apple.usernotifications.service)およびNSExtensionPrincipalClass(コントローラ名)が含まれます。さらに、拡張機能のアクティベーションルールを持つNSExtensionAttributesを指定できます。Xcodeはこれらの設定を自動的に生成します。
基本的な実装はdidReceiveをオーバーライドし、通知コンテンツを変更してcontentHandlerを呼び出します。処理に時間がかかりすぎる場合、serviceExtensionTimeWillExpireが呼び出され、現在の状態で作業を完了する必要があります。
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
contentHandler = handler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "[処理済み] \(bestAttemptContent.title)"
contentHandler?(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
Service Extensionの主な用途の1つは、デバイス上で通知コンテンツを復号することです。サーバーが暗号化されたペイロードを送信し、拡張機能がKeychainまたはApp Groupに保存された鍵を使用して復号します。これにより、送信中にコンテンツが傍受されるのを防ぎます。
サーバーは対称鍵(AES-256)を使用して通知本文を暗号化します。暗号化鍵はクライアントとサーバー間で事前に合意されます。拡張機能はペイロードのdataフィールドで暗号化された文字列を受け取り、復号して通知フィールドに代入します。ユーザー鍵は認証後にKeychainから取得できます。
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
guard let content = request.content.mutableCopy()
as? UNMutableNotificationContent else {
handler(request.content)
return
}
guard let encryptedData = content.userInfo["encrypted_data"]
as? String else {
handler(content)
return
}
let decrypted = CryptoService.decrypt(encryptedData)
content.body = decrypted.body
content.title = decrypted.title
handler(content)
}
復号鍵は拡張機能のコードやNSUserDefaultsに保存してはいけません。アプリと拡張機能の両方が鍵を読み取れるように、App Groupを介したアクセスを持つiOS Keychainを使用してください。クライアントでの鍵生成には、AES-256-GCMアルゴリズムのSecurity.frameworkを使用します。
Notification Service Extensionの最も一般的な用途は、通知に表示する画像、GIF、動画をダウンロードすることです。サーバーがメディアファイルのURLを送信し、拡張機能がそれをダウンロードして一時ディレクトリに保存し、UNNotificationAttachmentを作成します。
拡張機能は通知ペイロードフィールドから画像URLを受け取ります。URLSessionを使用して、拡張機能はファイルを一時ディレクトリにダウンロードします。ダウンロードが完了すると、ローカルURLを持つUNNotificationAttachmentが作成されます。添付ファイルは変更されたコンテンツに渡されます。iOSは自動的に標準インターフェースまたはNotification Content Extensionで画像を表示します。
private func downloadAndAttachMedia(
content: UNMutableNotificationContent,
mediaUrl: String,
handler: @escaping (UNNotificationContent) -> Void
) {
guard let url = URL(string: mediaUrl) else {
handler(content)
return
}
let task = URLSession.shared.downloadTask(with: url) { localUrl, _, error in
guard let localUrl = localUrl, error == nil else {
handler(content)
return
}
let attachment = try? UNNotificationAttachment(
identifier: "media",
url: localUrl,
options: nil
)
if let attachment = attachment {
content.attachments = [attachment]
}
handler(content)
}
task.resume()
}
iOSは通知表示のために以下の形式をサポートしています:JPEG、PNG、GIF(静的)、50 MBまでのMPEG-4動画。オーディオファイルには、MP3、AAC、ALACがサポートされています。重要:すべてのメディアファイルは30秒の制限内でダウンロードする必要があります。大きなファイルの場合は、サーバー側のトリミングやプログレッシブローディングを推奨します。
30秒の制限の管理は、Notification Service Extensionを開発する際の重要なタスクです。拡張機能が時間内に処理を完了しない場合、システムはserviceExtensionTimeWillExpireを呼び出し、元のコンテンツを表示します。各処理タイプに対してフォールバックシナリオを提供する必要があります。
タスクを優先順位に従って分割します。最初に重要な変更(復号、基本的なローカライゼーション)を実行します。オプションの改善(画像のダウンロード、テキストの拡充)は後回しにします。ネットワークリクエストにはタイムアウト付きのURLSessionを使用して、制限時間全体を1つの操作に費やさないようにします。
画像のダウンロードが失敗した場合やペイロードの復号がエラーを返した場合、拡張機能は元のコンテンツでcontentHandlerを呼び出す必要があります。contentHandlerを呼び出さずに拡張機能を終了しないでください。通知が失われます。安全なフォールバックは常に拡張機能コードに組み込む必要があります。
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
let content = (request.content.mutableCopy()
as? UNMutableNotificationContent) ?? request.content
// クリティカルタスク:復号
var decryptedContent = tryDecryptPayload(content)
// オプションタスク:メディア
guard let mediaUrl = decryptedContent.userInfo["media_url"]
as? String else {
handler(decryptedContent)
return
}
downloadAndAttachMedia(
content: decryptedContent,
mediaUrl: mediaUrl,
handler: handler
)
}
Notification Service Extensionをテストするには、Xcodeを使用します:拡張機能のtargetを選択し、シミュレーターで実行し、ターミナルまたはFirebase Consoleを介してプッシュ通知を送信します。os_logを使用して処理の各段階をログ記録します。これにより、タイミングやダウンロードの問題を診断するのに役立ちます。
よくある質問
システムはserviceExtensionTimeWillExpireを呼び出し、その後元の通知を変更せずに表示します。ダウンロードされたすべてのファイルと変更は破棄されます。
はい、空のコンテンツ(空のフィールドのUNNotificationContent)でcontentHandlerを呼び出すと、通知は表示されません。これはスパム通知や不正なデータをフィルタリングするために使用されます。
トークンは通知ペイロードのuserInfoで渡すか、App Groupを介してKeychainから取得できます。拡張機能のUserDefaultsにトークンを保存することは推奨されません。
拡張機能はメインアプリと同じ開発者証明書で署名する必要があります。本番環境では、Push Notifications機能が有効な本番証明書が必要です。
デバイスをXcodeに接続し、実行スキームで拡張機能のtargetを選択し、Firebase Consoleを介してプッシュ通知を送信します。拡張機能のブレークポイントはメインアプリと同じように機能します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。