◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-05-01上級

Rork アプリに Notification Service Extension を後付けする — 画像付き通知・暗号化ペイロード復号・動的書き換えの実装メモ

Rork で作った iOS アプリに Notification Service Extension を後付けし、画像付き通知・E2E暗号化ペイロードの復号・APNs ペイロードの動的書き換えを本番品質で動かすまでの設計と、実際に踏んだ落とし穴を、動作する Swift コードとともにまとめました。

Rork585Notification Service ExtensionRich PushAPNs3Swift48iOS115プッシュ通知20暗号化3

✦ プレミアム記事

「同じプッシュ通知でも、画像付きとテキストだけでは開封率が2倍以上違う」— App Store の主要アプリを観察していると、画像通知を使いこなしているチームとそうでないチームの差が、リテンションと収益にそのまま響いていることが見えてきます。それなのに、Rork で作ったアプリに後から画像通知を組み込もうとして、「mutable-content: 1 を付けても画像が出ない」「Notification Service Extension(NSE)を作ったが30秒で落ちる」「テスト環境では出るのに本番だと出ない」と詰まっている方を、最近よく見かけます。

私自身、最初に NSE を実装したときは、Xcode のターゲット追加から App Group の設定、APNs ペイロードの仕様まで、断片的な情報を10個以上のドキュメントから拾い集める必要がありました。厄介だったのは、どのドキュメントも間違っていなかったことです。ただ、順番が書かれていませんでした。

そこで以下では、画像通知の最低限実装 → E2E 暗号化ペイロードの復号 → APNs ペイロードの動的書き換え → 出荷後に効く監視設計 という、私が結局たどり着いた順番のまま並べます。Rork で作ったアプリに後付けする前提で、動作する Swift コードを添えました。Rork Max でネイティブターゲットを生成済みのプロジェクトを想定していますが、Expo Bare ワークフローや React Native からの移行ケースでも同じ順番で通せます。

なぜ Notification Service Extension が必要なのか

iOS のプッシュ通知は、APNs(Apple Push Notification service)から送られたペイロードをそのまま画面に表示するのが基本動作です。しかしこの仕組みには、本番アプリで遭遇する3つの大きな制約があります。

ひとつ目は、画像・動画・音声などの添付ファイルは APNs のペイロードに直接乗せられないこと。ペイロード上限は 4096 バイト(HTTP/2 経由なら 4KB)しかなく、Retina 用の画像 1 枚すら入りません。代わりに、ペイロードに画像 URL を入れて、デバイス側でダウンロードしてから通知に貼り付ける必要があります。この「デバイス側で通知を加工する」処理を担うのが Notification Service Extension です。

ふたつ目は、APNs を経由するペイロードは Apple のサーバーに届く以上、平文では機密情報を載せられないこと。チャットアプリの本文や金融アプリの取引明細など、E2E 暗号化が前提の通知では、サーバー側で暗号化したペイロードを送り、デバイス側で復号してから表示する設計が必要になります。これも NSE で実装します。

みっつ目は、ユーザーごと・タイムゾーンごとの動的書き換えです。たとえば「あと3時間で締切」という通知を多言語で送る場合、サーバー側で全ユーザー分の文字列を生成するより、デバイス側のロケールに応じて NSE で書き換える方が、配信パフォーマンスもリージョン拡張性も高まります。

これら3つは、いずれも「APNs を信頼境界にしない」「デバイス側で最終加工する」という発想が共通しています。NSE はそのための公式に提供された唯一の手段であり、Rork で作ったアプリでも、ネイティブターゲットを開いて NSE を追加するだけで利用できます。

NSE が動く仕組み — 通知パイプライン全体像

実装に入る前に、NSE がどのタイミングで起動し、何ができて何ができないのかを整理しておきます。これを理解しておかないと、後で時間切れやメモリ超過で詰まったときに、なぜ落ちているのか分からなくなります。

通常のプッシュ通知では、APNs から届いたペイロードがそのままシステム UI に渡され、通知センターやロック画面に表示されます。一方、ペイロードに mutable-content: 1 が含まれている場合、システムは通知をユーザーに見せる前に、アプリの NSE を起動し、UNNotificationServiceExtension のサブクラスに通知を渡します。

NSE 側では、didReceive(_:withContentHandler:) というメソッドが呼ばれ、ここで通知の内容を書き換えたり、画像をダウンロードして添付したり、暗号化されたペイロードを復号したりできます。処理が終わったら contentHandler を呼び出して、システムに加工後の通知を返します。

ここで重要なのが、処理に与えられる時間は最大30秒、使えるメモリはおよそ24MBという制約です。30秒を超えた場合、システムは serviceExtensionTimeWillExpire() を呼び出して「あと少しで強制終了するから、できる範囲の結果を返せ」と通知します。これに応答せずに30秒を超えると、加工前のペイロードがそのまま表示されます。

メモリの数字は、私自身が長く取り違えていた箇所です。Widget や Share の拡張と混同して 50MB 前後だと思い込んだまま画像処理を組み、実機のクラッシュログで EXC_RESOURCE / RESOURCE_TYPE_MEMORY (limit=24 MB) を見て、ようやく桁がひとつ違うことに気づきました。通知拡張のメモリ予算は、本体アプリの感覚では測れません。 24MB は 4K 画像を UIImage(data:) で一度展開しただけで飛ぶ量ですので、画像はダウンサンプルしながら扱うのが鉄則になります。

また、NSE は本体アプリと同じプロセスでは動きません。別プロセス・別バンドル ID で動作するため、Keychain や UserDefaults を共有するには App Group の設定が必要です。本体側で保存した認証トークンを NSE から読みたい場合は、Keychain Access Group を共有する設計にしておきます。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦プッシュ通知に画像が出ない・開封率が伸びないと悩んでいた人が、本番で動く Rich Push 実装を今日手に入れられます
✦APNs ペイロードを暗号化して送信し、デバイス側で復号してから通知に表示する E2E 暗号化通知の設計と Swift コードを習得できます
✦30秒の処理時間制限と24MBのメモリ上限、さらに添付ファイル自体のサイズ上限という三つの壁の中で、画像ダウンロード・ローカライズ・パーソナライズを安定して捌く本番品質のパターンが手に入ります
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。
⬡ 開発ツール2026-09-05
Widget Extension でも BGTaskScheduler.submit は成功します。走らないのは register が親アプリだけのものだからです
Widget Extension から BGTaskScheduler に依頼を出すとビルドも submit も通るのに、翌朝ウィジェットは変わりません。register がホストアプリ専用である事実から、更新の主語を親アプリへ戻す設計まで、壁紙アプリの運用で詰めた手順を残します。
⬡ 開発ツール2026-07-27
最低サポート iOS をいつ上げるか — 利用率ではなく、コードに残った分岐の数で決める
最低サポート OS の引き上げを利用率で判断すると、毎年同じ結論になって先送りが続きます。OS 分岐に注記を付けて棚卸しし、iOS と Android の引き上げ先ごとに消せる分岐を数えるスクリプトと、引き上げ後に見る指標をまとめました。