# iOS開発者ガイド

ベータ機能
Mobile Pushは現在ベータ版です。機能、API、実装の詳細は正式リリース前に変更される可能性があります。

## 概要

このガイドでは、Treasure Data iOS SDKを使用してiOSアプリにEngage Studio Mobile Push通知を統合するための実装要件を提供します。開発チームは次の機能を実装する必要があります:

1. **APNs/FCM連携**: Apple Push Notification ServiceとFirebase Cloud Messaging経由でプッシュ通知を受信
2. **トークン管理**: デバイストークンをTreasure Dataに登録および更新
3. **リッチ通知**: Notification Service Extensionを使用して画像付き通知を表示
4. **イベントトラッキング**: Treasure Data SDKを使用してユーザーインタラクション(配信、開封、削除、リンク)をトラッキング
5. **自動データアップロード**: Treasure Data SDKがイベントのTreasure Dataへのアップロードを自動的に処理


サンプル実装が利用可能
本番環境対応の完全なサンプルアプリケーションがGitHubで公開されています:

**[Treasure Data Mobile Push - iOSサンプル](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)**

サンプルに含まれる内容:

- FCMとTreasure Data SDK連携を含む完全なAppDelegate実装
- リッチメディア用のNotification Service Extension
- Treasure Data SDKを使用したイベントトラッキング
- ディープリンクとWebリンク処理


リポジトリをクローンして実装の参考にしてください。

## 必要要件

| コンポーネント | 要件 |
|  --- | --- |
| **最小iOSバージョン** | iOS 15.0以降 |
| **言語** | Swift (推奨) またはObjective-C |
| **必要なフレームワーク** | - Firebase Messaging SDK 12.7.0+
- Treasure Data iOS SDK 1.2.1+
- UserNotificationsフレームワーク
- SafariServices (Webリンク処理用)

 |
| **Xcodeバージョン** | Xcode 14.0以降 |
| **トラッキングイベント** | `delivery`, `open`, `dismiss`, `deeplink_open`, `link_open`, `token_register` |


## アーキテクチャ概要

iOSアプリ実装は以下のコンポーネントで構成されます:

### 1. AppDelegate設定

**目的:** Firebaseを初期化し通知デリゲートを設定する

**主な責務:**

- Firebase SDKを初期化
- 通知センターのデリゲートを設定
- ユーザーから通知パーミッションをリクエスト
- リモート通知に登録
- フォアグラウンドとバックグラウンドで通知イベントを処理


**参照:** [サンプルリポジトリ](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)の`AppDelegate.swift`を参照してください

### 2. Notification Service Extension

**目的:** 通知に画像をダウンロードして添付する

**主な責務:**

- 表示前に通知を受信
- ペイロードから`image_url`を抽出
- リモートURLから画像をダウンロード
- UNNotificationAttachmentを作成
- 通知コンテンツに添付ファイルを追加


**参照:** [サンプルリポジトリ](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)の`NotificationService.swift`を参照してください

### 3. FCM Token Service

**目的:** Treasure Dataとのデバイストークン登録を管理する

**主な責務:**

- FirebaseからFCMトークンを取得
- アプリ起動時にTreasure Dataにトークンを登録
- トークン更新時に再登録
- ユーザーログイン時にトークンとユーザーIDを関連付け
- `token_register`イベントをトラッキング


**参照:** [サンプルリポジトリ](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)の`FCMTokenService.swift`を参照してください

### 4. Event Queue

**目的:** Treasure Dataにアップロードする前にイベントをローカルに保存する

**主な責務:**

- UserDefaultsにイベントを永続化
- 最大サイズ制限(1000イベント)でFIFOキューを実装
- アップロード用のバッチドレインをサポート
- キューオーバーフローを適切に処理
- DispatchQueueを使用したスレッドセーフな操作


**参照:** [サンプルリポジトリ](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)の`EventQueue.swift`を参照してください

### 5. Push Event Uploader

**目的:** Treasure Data Ingest APIにイベントをアップロードする

**主な責務:**

- イベントをバッチアップロード(リクエストあたり最大500件)
- 信頼性の高い実行にバックグラウンドキューを使用
- 失敗したアップロードの再試行ロジックを実装
- Treasure Dataポストバック APIにイベントを送信
- アップロード失敗を適切に処理


**参照:** [サンプルリポジトリ](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)の`PushEventUploader.swift`を参照してください

### 6. Deep Link Handler

**目的:** 通知からのディープリンクを処理する

**主な責務:**

- SceneDelegateまたはAppDelegate経由でディープリンクURLを受信
- URLスキームとパスを解析
- ディープリンクに基づいて適切な画面に遷移
- SafariでWeb URLを開く


**参照:** [サンプルリポジトリ](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)の`SceneDelegate.swift`を参照してください

## プロジェクトセットアップ

### ステップ1: iOSプロジェクトにFirebaseを追加

1. Firebase Consoleから`GoogleService-Info.plist`をダウンロード
2. ファイルをXcodeプロジェクトに追加:
  - `GoogleService-Info.plist`をプロジェクトナビゲーターにドラッグ
  - **Copy items if needed**にチェックが入っていることを確認
  - メインアプリターゲットに追加
3. Swift Package Managerを使用してFirebase SDKを追加:
  - Xcodeで **File** > **Add Packages...** に移動
  - 入力: `https://github.com/firebase/firebase-ios-sdk`
  - バージョン12.0.0以降を選択 (最新: 12.7.0)
  - 次のproductsを追加:
    - `FirebaseMessaging`
    - `FirebaseAnalytics` (オプション)
4. またはCocoaPodsを使用 (`Podfile`に追加):



```ruby
platform :ios, '13.0'
use_frameworks!

target 'YourApp' do
  pod 'Firebase/Messaging', '~> 12.7'
  pod 'Firebase/Analytics', '~> 12.7'
end
```

その後実行:


```bash
pod install
```

### ステップ2: Xcodeでケイパビリティを設定

1. Project Navigatorでプロジェクトを選択
2. アプリターゲットを選択
3. **Signing & Capabilities**タブに移動
4. **+ Capability**をクリックして追加:
  - **Push Notifications**
  - **Background Modes** → **Remote notifications**にチェック


### ステップ3: Notification Service Extensionを作成 (リッチメディア用)

通知で画像をサポートするには、Notification Service Extensionが必要です:

1. Xcodeで **File** > **New** > **Target** に移動
2. **Notification Service Extension**を選択
3. Extension名を入力 (例: `NotificationService`)
4. **Finish**をクリック


### ステップ4: ディープリンク用にInfo.plistを設定

`Info.plist`にURLスキームサポートを追加:


```xml
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string>
    </dict>
</array>
```

## データペイロードスキーマ

アプリは通知`userInfo`で以下のJSON構造を受信します。カスタムデータフィールドに加えて、Engage Studioは配信トラッキングと削除トラッキングを有効化する`content-available`フィールドと`category`フィールドを含む標準のAPNs `aps`ディクショナリを含めます（後述の[配信トラッキングと削除トラッキングのためのAPNsフィールド](#apns-fields-for-delivery-and-dismiss-tracking)を参照）:


```json
{
  "aps": {
    "alert": {
      "title": "Special Offer!",
      "body": "Get 20% off your next purchase"
    },
    "content-available": 1,
    "category": "default"
  },
  "td_campaign_id": "cmp_20251214_promo",
  "title": "Special Offer!",
  "body": "Get 20% off your next purchase",
  "image_url": "https://cdn.example.com/banner.png",
  "link": "https://example.com/promo"
}
```

| フィールド | 型 | 必須 | 説明 |
|  --- | --- | --- | --- |
| `aps.content-available` | Integer | Yes | Engage Studioにより`1`に設定されます。通知を受信したときにiOSにアプリをバックグラウンドで短時間起動するよう指示し、アプリがフォアグラウンドにない場合でも`delivery`イベントを記録できるようにします。 |
| `aps.category` | String | Yes | Engage Studioにより`default`に設定されます。アプリが登録する通知カテゴリを識別し、ユーザーが通知を削除したときにコールバックを受信できるようにすることで、`dismiss`トラッキングを有効化します。 |
| `td_campaign_id` | String | Yes | Engage Studioからの一意のキャンペーン識別子 |
| `title` | String | Yes | 通知タイトル |
| `body` | String | Yes | 通知本文テキスト |
| `image_url` | String | No | リッチ通知画像のURL |
| `link` | String | No | Safariで開くWeb URL |


### 配信トラッキングと削除トラッキングのためのAPNsフィールド

Engage Studioは、アプリが配信イベントと削除イベントをトラッキングできるように、すべてのiOSプッシュ通知の`aps`ディクショナリに2つのフィールドを自動的に追加します:

これらのフィールドの仕組み
- **`content-available: 1`** — 通知をバックグラウンド更新通知としてマークします。iOSは受信時にアプリをバックグラウンドで短時間起動するため、アプリがフォアグラウンドにない場合でも**delivery**イベントを記録できます。Appleの[Pushing background updates to your app](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app)を参照してください。
- **`category: default`** — 通知を通知カテゴリに関連付けます。アプリがカスタム削除アクションを許可する一致するカテゴリを登録すると、ユーザーが通知をスワイプして削除したときにiOSがデリゲートを呼び出し、**dismiss**トラッキングを有効化します。Appleの[Generating a remote notification](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification)を参照してください。


これらのペイロードフィールドはEngage Studioにより自動的に送信されるため、キャンペーンの設定は不要です。ただし、配信イベントと削除イベントを実際にキャプチャするには、後述の[必要なアプリ側のセットアップ](#enabling-delivery-and-dismiss-tracking-required-app-side-setup)を完了する必要があります。

## イベントトラッキング

アプリは以下のイベントをトラッキングしTreasure Dataに送信する必要があります:

| イベントタイプ | トラッキングタイミング | 必須フィールド |
|  --- | --- | --- |
| `delivery` | 通知が受信され表示されたとき | `campaign_id`, `message_id`, `platform`, `time` |
| `open` | ユーザーが通知をタップしたとき | `campaign_id`, `message_id`, `platform`, `time`, `user_id` |
| `dismiss` | ユーザーが通知を削除したとき | `campaign_id`, `message_id`, `platform`, `time`, `user_id` |
| `link_open` | ユーザーがWebリンクをタップしたとき | `campaign_id`, `message_id`, `platform`, `time`, `user_id`, `value` (URL) |
| `deeplink_open` | ユーザーがディープリンクをタップしたとき | `campaign_id`, `message_id`, `platform`, `time`, `user_id`, `value` (URI) |
| `token_register` | FCMトークンが取得または更新されたとき | `fcm_token`, `platform`, `time`, `user_id` (ログイン時) |


すべてのイベントはTreasure Data SDKにより、設定されたTreasure Dataエンドポイントに自動的に送信されます。SDKはバッチ処理、キューイング、再試行ロジックを自動的に処理します。

配信イベントと削除イベントにはアプリ側のセットアップが必要
（アプリがバックグラウンドにある場合の）`delivery`イベントと`dismiss`イベントは、Engage Studioがペイロードに追加する`content-available`フィールドと`category`フィールドに依存します。これらのイベントをキャプチャするには、後述の[配信トラッキングと削除トラッキングの有効化](#enabling-delivery-and-dismiss-tracking-required-app-side-setup)のセットアップを完了する必要があります。

完全なスキーマ詳細については[Push Events Table](/ja/products/marketing-cloud/engage-studio/channels/mobile-push/push-events-table)ドキュメントを参照してください。

## 配信トラッキングと削除トラッキングの有効化（必要なアプリ側のセットアップ）

Engage Studioは、iOSプッシュペイロードに`content-available: 1`と`category: default`を自動的に追加します。これらを`delivery`イベントと`dismiss`イベントに変換するには、アプリで以下を実装する必要があります。

### 1. Background Modesを有効化する

[ステップ5: XcodeでのCapabilitiesの設定](#step-5-configure-capabilities-in-xcode)で説明されているように、アプリターゲットで**Background Modes → Remote notifications**を有効化します。これにより、`content-available: 1`を持つ通知が到着したときにiOSがアプリをバックグラウンドで起動できるようになり、`delivery`イベントを記録できます。

### 2. `default`通知カテゴリを登録する

通知が削除されたときにiOSがアプリに通知するように、識別子`default`と`.customDismissAction`オプションを持つ`UNNotificationCategory`を登録します。通知センターを設定するとき（たとえば`application(_:didFinishLaunchingWithOptions:)`内）にカテゴリを設定します:


```swift
let defaultCategory = UNNotificationCategory(
    identifier: "default",
    actions: [],
    intentIdentifiers: [],
    options: [.customDismissAction]
)
UNUserNotificationCenter.current().setNotificationCategories([defaultCategory])
```

`identifier`はペイロードで送信される`category`の値（`default`）と一致する必要があります。

### 3. 削除イベントをトラッキングする

ユーザーが通知をスワイプして削除すると、iOSはアクション識別子として`UNNotificationDismissActionIdentifier`を指定して`userNotificationCenter(_:didReceive:withCompletionHandler:)`を呼び出します。そこで`dismiss`イベントをトラッキングします:


```swift
func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
) {
    let userInfo = response.notification.request.content.userInfo

    switch response.actionIdentifier {
    case UNNotificationDismissActionIdentifier:
        // User dismissed the notification
        TrackingService.shared.track(event: "dismiss", userInfo: userInfo)
    case UNNotificationDefaultActionIdentifier:
        // User tapped the notification
        TrackingService.shared.track(event: "open", userInfo: userInfo)
    default:
        break
    }

    completionHandler()
}
```

### 4. バックグラウンド配信をトラッキングする

アプリがバックグラウンドにある間に`content-available: 1`を持つ通知が到着すると、iOSは`application(_:didReceiveRemoteNotification:fetchCompletionHandler:)`を呼び出します。そこで`delivery`イベントを記録します。フォアグラウンドの通知は`willPresent`デリゲートですでにトラッキングされているため、`delivery`イベントの重複記録を避けるには、呼び出しを`applicationState != .active`でガードしてください:


```swift
func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
    // ここではバックグラウンド配信のみをトラッキングします。
    // フォアグラウンド配信は重複イベントを避けるため willPresent デリゲートでトラッキングします。
    if application.applicationState != .active {
        TrackingService.shared.track(event: "delivery", userInfo: userInfo)
    }
    completionHandler(.newData)
}
```

リファレンス実装
カテゴリ登録と配信/削除トラッキングの完全な実装については、[サンプルリポジトリ](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)の`AppDelegate.swift`を参照してください。

## テスト

### シミュレーターでのテスト (iOS 13+)

iOSシミュレーターはiOS 13とXcode 11.4+からプッシュ通知をサポートしています:

1. テストペイロードファイル`test-notification.apns`を作成:



```json
{
  "Simulator Target Bundle": "com.example.myapp",
  "aps": {
    "alert": {
      "title": "Test Notification",
      "body": "This is a test message"
    },
    "sound": "default",
    "badge": 1
  },
  "campaign_id": "test_campaign",
  "message_id": "test_001",
  "deeplink": "myapp://test"
}
```

1. シミュレーターに送信:



```bash
xcrun simctl push booted com.example.myapp test-notification.apns
```

### 実機でのテスト

1. アプリログからFCMトークンを取得
2. Firebase Console > Cloud Messagingを開く
3. **Send your first message**をクリック
4. タイトルと本文を入力
5. **Send test message**をクリック
6. FCMトークンを入力
7. デバイスに通知が表示されることを確認


### イベントトラッキングの確認

Treasure Dataでクエリを実行してイベントがログに記録されていることを確認:


```sql
SELECT
  time,
  type,
  campaign_id,
  message_id,
  platform
FROM mobile.push_events
WHERE platform = 'ios'
ORDER BY time DESC
LIMIT 100
```

## ユーザーID関連付け

プッシュ通知イベントを特定のユーザーとリンクするには、FCMトークン登録時に`user_id`を渡します。

**実装ノート:**

- **ログインユーザー**: トークン登録イベントに`user_id`(例: 顧客ID、メールハッシュ値)を含める
- **未ログインユーザー**: `user_id`に`nil`を渡す — Treasure Dataはユーザーログイン時に後でトークンを関連付けます
- **名前解決**: Treasure Dataは時間経過に伴う`fcm_token`値の照合によってイベントをリンクします


## トラブルシューティング

### 通知が受信されない

1. **APNs証明書を確認**: APNs認証キーがFirebase Consoleにアップロードされているか確認
2. **Bundle IDを確認**: XcodeのBundle IDがFirebase登録と一致するか確認(大文字小文字を区別)
3. **通知パーミッションを確認**: 設定 > 通知でアプリが通知パーミッションを持っているか確認
4. **デバイス登録を確認**: `didRegisterForRemoteNotificationsWithDeviceToken`が呼ばれているか確認
5. **Firebase Consoleでテスト**: Firebase Consoleから直接テスト通知を送信


### リッチ画像が表示されない

1. **Notification Service Extensionを確認**: Extensionが適切に設定されアプリに追加されているか確認
2. **画像URLを確認**: URLがHTTPSであり画像にアクセス可能か確認
3. **Extensionログを確認**: Console.appを使用してExtensionログを確認
4. **ファイルサイズを確認**: 大きな画像はタイムアウトする可能性あり(制限約10MB)


### イベントがTreasure Dataに表示されない

1. **書き込みキーを確認**: Info.plistの`TDWriteKey`が正しくアクティブであるか確認
2. **エンドポイントを確認**: `TDEndpoint`がリージョン(US、東京、EU)と一致するか確認
3. **テーブル名を確認**: `TDDatabase`と`TDTable`がTreasure Dataに存在するか確認
4. **SDK初期化を確認**: AppDelegateでTreasure Data SDKが初期化されているか確認
5. **ログを有効化**: `TreasureData.enableLogging()`を追加してSDKの動作を確認
6. **手動アップロード**: `TreasureData.sharedInstance().uploadEvents()`を手動で呼び出してみる


### ディープリンクが機能しない

1. **URLスキームを確認**: `Info.plist`に正しい`CFBundleURLTypes`設定があるか確認
2. **ディープリンクをテスト**: Safariでテスト: アドレスバーに`myapp://test`を入力
3. **Scene Delegateを確認**: `scene(_:openURLContexts:)`が実装されているか確認
4. **URI形式を確認**: ディープリンクが登録されたスキームで始まっているか確認


## セキュリティのベストプラクティス

1. **APIキーを保護**:
  - 実際の`TDWriteKey`をソース管理にコミットしない
  - GitにコミットするInfo.plistにはプレースホルダー値を使用
  - 必要な設定を別途ドキュメント化
  - 書き込み専用キーを使用(マスターキーではない)
  - 本番アプリではKeychainの使用を検討
2. **ディープリンクを検証**:
  - ナビゲーション前にディープリンクの宛先を常に検証
  - 機密性の高いアクションにURL許可リストを実装
  - ディープリンクパラメータをサニタイズ
3. **イベントデータを保護**:
  - イベントペイロードにPII(個人を特定できる情報)を含めない
  - 可能な限りハッシュ化または匿名化されたユーザーIDを使用
4. **ネットワークセキュリティ**:
  - APIエンドポイントには常にHTTPSを使用
  - App Transport Security (ATS)を実装
  - 本番アプリでは証明書ピン留めを検討


## サンプルリポジトリ

完全な本番環境対応の実装:

**[https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios](https://github.com/treasure-data/engage-push-notification-sample/tree/main/ios)**

リポジトリに含まれる内容:

- インラインドキュメント付きの完全なソースコード
- XcodeGenプロジェクト設定
- Swift Package Manager依存関係管理
- Treasure Data SDK連携の例
- テスト手順
- セキュリティのベストプラクティス


## 実装ガイド

このセクションでは、サンプルアプリケーションのビルドとテストの詳細な手順を提供します。

### プロジェクト構造

サンプルリポジトリは以下のディレクトリ構造に従います:


```
ios/
├── MyApp/
│   ├── AppDelegate.swift           # Firebase初期化、通知デリゲート
│   ├── SceneDelegate.swift         # ディープリンクルーティング
│   ├── ViewController.swift        # メインUI
│   ├── Models/
│   │   ├── FCMTokenService.swift   # トークン登録
│   │   ├── EventQueue.swift        # ローカルイベント保存
│   │   └── PushEventUploader.swift # TDへのバッチアップロード
│   ├── Assets.xcassets
│   ├── Info.plist
│   └── GoogleService-Info.plist    # Firebase設定
├── NotificationService/             # リッチメディア用Extension
│   └── NotificationService.swift
├── MyApp.xcodeproj/
└── Podfile                          # 依存関係
```

### ビルドと実行手順

#### CocoaPodsを使用する場合

1. 依存関係をインストール:



```bash
cd ios
pod install
```

1. Xcodeでワークスペースを開く:



```bash
open MyApp.xcworkspace
```

1. Firebaseを設定:
  - `GoogleService-Info.plist`をあなたのFirebase設定ファイルに置き換える
  - Bundle IDをFirebaseアプリと一致するように更新
2. Treasure Dataを設定:
  - `PushEventUploader.swift`を開く
  - `YOUR_TD_WRITE_KEY`を実際の書き込み専用APIキーに置き換える
  - 必要に応じて`TD_DATABASE`と`TD_TABLE`を更新
3. ビルドして実行:
  - ターゲットデバイス/シミュレーターを選択
  - Cmd+Rを押してビルドと実行


#### Swift Package Managerを使用する場合

1. Xcodeで`MyApp.xcodeproj`を開く
2. Firebaseパッケージを追加:
  - File > Add Packages...
  - 入力: `https://github.com/firebase/firebase-ios-sdk`
  - バージョン12.7.0以降を選択
  - `FirebaseMessaging`と`FirebaseAnalytics`を追加
3. 上記CocoaPodsの手順3-5に従う


### テストワークフロー

#### 事前準備

テストを開始する前に、以下を確認してください:

- [ ] iOSアプリが登録されたFirebaseプロジェクト
- [ ] Firebase ConsoleにアップロードされたAPNs認証キー
- [ ] ダウンロードしてプロジェクトに追加された`GoogleService-Info.plist`
- [ ] 作成されたTreasure Dataデータベースとテーブル
- [ ] 取得した書き込み専用APIキー
- [ ] 物理デバイスまたはiOS 13+シミュレーター


#### シミュレーターでのテスト (iOS 13+)

1. テストペイロードファイル`test-notification.apns`を作成:



```json
{
  "Simulator Target Bundle": "com.example.myapp",
  "aps": {
    "alert": {
      "title": "テスト通知",
      "body": "これはテストメッセージです"
    },
    "sound": "default",
    "badge": 1
  },
  "campaign_id": "test_campaign",
  "message_id": "test_001",
  "deeplink": "myapp://test",
  "image_url": "https://example.com/image.jpg"
}
```

1. シミュレーターでアプリを実行:



```bash
# シミュレーターを起動
xcrun simctl boot "iPhone 15"

# ビルドして実行
xcodebuild -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 15' \
  build
```

1. テスト通知を送信:



```bash
xcrun simctl push booted com.example.myapp test-notification.apns
```

1. 通知が表示され画像が読み込まれることを確認


#### 実機でのテスト

1. USBケーブルでデバイスを接続
2. デバイスでDeveloper Modeを有効化:
  - 設定 > プライバシーとセキュリティ > Developer Mode
3. Xcodeからアプリをビルドしてインストール
4. XcodeコンソールでFCMトークンを確認:



```
FCM token: [your-token-here]
```

1. Firebase Consoleからテスト通知を送信:
  - Firebase Console > Cloud Messagingを開く
  - "Send your first message"をクリック
  - タイトルと本文を入力
  - "Send test message"をクリック
  - FCMトークンを貼り付け
  - "Test"をクリック
2. デバイスに通知が表示されることを確認


#### イベントトラッキングの確認

Treasure Dataにクエリを実行してイベントがログに記録されていることを確認:


```sql
SELECT
  time,
  type,
  campaign_id,
  message_id,
  platform,
  user_id
FROM mobile.push_events
WHERE platform = 'ios'
  AND time > td_time_add(now(), '-1h', 'JST')
ORDER BY time DESC
LIMIT 20
```

期待されるイベントシーケンス:

1. `token_register` - アプリ起動時
2. `delivery` - 通知受信時
3. `open` - ユーザーが通知をタップ時
4. `link_open` または `deeplink_open` - 通知にリンクが含まれる場合


### 実装チェックリスト

自分のアプリにプッシュ通知を実装する際は、このチェックリストを使用してください:

#### フェーズ1: プロジェクトセットアップ

- [ ] 最小デプロイメントターゲットiOS 13.0のiOSプロジェクトを作成
- [ ] XcodeでPush Notificationsケイパビリティを追加
- [ ] Background Modesケイパビリティを追加（Remote notifications）
- [ ] Info.plistでディープリンク用のURLスキームを設定
- [ ] プロジェクトにFirebaseを追加（CocoaPodsまたはSPM）
- [ ] `GoogleService-Info.plist`をダウンロードして追加


#### フェーズ2: コアコンポーネント

- [ ] `AppDelegate`でFirebase初期化を実装
- [ ] `UNUserNotificationCenter`デリゲートを設定
- [ ] Firebase Messagingデリゲートを設定
- [ ] 通知パーミッションをリクエスト
- [ ] リモート通知に登録
- [ ] トークン管理用の`FCMTokenService`を実装
- [ ] UserDefaults永続化を使用した`EventQueue`を実装
- [ ] URLSessionを使用した`PushEventUploader`を実装


#### フェーズ3: Notification Service Extension

- [ ] Notification Service Extensionターゲットを作成
- [ ] `NotificationService`クラスを実装
- [ ] 画像ダウンロードロジックを追加
- [ ] 画像用の`UNNotificationAttachment`を作成
- [ ] Extensionタイムアウトを適切に処理


#### フェーズ4: イベントトラッキング

- [ ] `willPresent`デリゲートで`delivery`イベントを追跡
- [ ] `.customDismissAction`を持つ`default`通知カテゴリを登録
- [ ] バックグラウンド配信のためにBackground Modes（Remote notifications）が有効になっていることを確認
- [ ] `didReceiveRemoteNotification`でバックグラウンドの`delivery`イベントを追跡
- [ ] `didReceive response`デリゲートで`open`イベントを追跡
- [ ] `didReceive response`で`UNNotificationDismissActionIdentifier`の`dismiss`イベントを追跡
- [ ] Webリンクの`link_open`イベントを追跡
- [ ] アプリリンクの`deeplink_open`イベントを追跡
- [ ] アプリ起動時とトークン更新時に`token_register`イベントを追跡


#### フェーズ5: ディープリンク処理

- [ ] SceneDelegateで`scene(_:openURLContexts:)`を実装
- [ ] ディープリンクURLのスキームとパスを解析
- [ ] ディープリンクに基づいて適切な画面に遷移
- [ ] SafariまたはSFSafariViewControllerでWebリンクを処理
- [ ] ディープリンクの宛先を検証


#### フェーズ6: 設定とセキュリティ

- [ ] プレースホルダー`TD_WRITE_KEY`を実際のキーに置き換え
- [ ] APIキーを`.gitignore`に追加
- [ ] ネットワーク障害のエラーハンドリングを実装
- [ ] 失敗したアップロードの再試行ロジックを実装
- [ ] デバッグ用のログを追加（本番環境では削除）


#### フェーズ7: テスト

- [ ] シミュレーターで通知配信をテスト
- [ ] 実機で通知配信をテスト
- [ ] 通知内のリッチメディア（画像）をテスト
- [ ] ディープリンクナビゲーションをテスト
- [ ] Webリンク開封をテスト
- [ ] Treasure Dataにイベントが表示されることを確認
- [ ] バックグラウンドアプリ状態でテスト
- [ ] 強制終了アプリ状態でテスト


### ビルドトラブルシューティング

#### "No such module 'Firebase'"エラー

- CocoaPodsを使用する場合、`.xcworkspace`（`.xcodeproj`ではなく）を開いていることを確認
- 依存関係が不足している場合は`pod install`を実行
- ビルドフォルダをクリーン: Product > Clean Build Folder (Cmd+Shift+K)


#### Notification Service Extensionが画像を読み込まない

- 画像URLがHTTPSであることを確認
- 画像にアクセス可能であることを確認（ブラウザでURLをテスト）
- Console.appでExtensionログを確認
- スキーム設定でExtensionターゲットのメモリ制限を増やしていることを確認


#### イベントがTreasure Dataに表示されない

- `TD_WRITE_KEY`が正しいことを確認
- HTTP 200レスポンスのネットワークログを確認
- データベースとテーブル名が設定と一致することを確認
- EventQueueがイベントを永続化していることを確認（UserDefaultsを検査）


#### ディープリンクが開かない

- Info.plistのURLスキームがディープリンクと一致することを確認
- Safariからディープリンクをテスト: アドレスバーに`myapp://test`を入力
- SceneDelegateで`scene(_:openURLContexts:)`が実装されていることを確認
- iOS 12以下の場合は、代わりにAppDelegateに実装


## 次のステップ

1. **サンプルリポジトリをクローン**して実装を確認
2. [Mobile Push Setup](/ja/products/marketing-cloud/engage-studio/channels/mobile-push/mobile-push-setup)ガイドに従って**Firebaseを設定**
3. iOSアプリに**コンポーネントを実装**
4. **通知配信とイベントトラッキングをテスト**
5. [Push Events Table](/ja/products/marketing-cloud/engage-studio/channels/mobile-push/push-events-table)で**イベントデータを確認**
6. [Engage Studio](/ja/products/marketing-cloud/engage-studio/channels/mobile-push)で**最初のキャンペーンを作成**


## 関連ドキュメント

- [Mobile Push Setup](/ja/products/marketing-cloud/engage-studio/channels/mobile-push/mobile-push-setup) - FirebaseとAPNs設定
- [Android Developer Guide](/ja/products/marketing-cloud/engage-studio/channels/mobile-push/developer-guide-android) - Android実装ガイド
- [Push Events Table](/ja/products/marketing-cloud/engage-studio/channels/mobile-push/push-events-table) - イベントスキーマと分析クエリ
- [Campaign Creation](/ja/products/marketing-cloud/engage-studio/channels/mobile-push) - Mobile Pushキャンペーンの作成