iOSエージェントの適用
WhaTapモバイルエージェントをiOSアプリに適用すると、アプリの起動性能、画面のロード、ネットワーク呼び出し、クラッシュ、リソース使用量、WebViewページの性能を収集できます。
こ のドキュメントはアプリ開発者を対象に、ライブラリの追加からデータ収集の確認までの全体の手順を案内します。WebView内のWebページまで収集するには、Web担当者の作業も必要です。詳細はWebViewページ収集の設定で扱います。
サポート環境
ビルド環境
| Item | Value |
|---|---|
| iOS | 15.0以上 |
| Xcode | 16.0以上 |
| 言語 | Swift 5.9以上、またはObjective-C |
デプロイターゲット
アプリのデプロイターゲット(IPHONEOS_DEPLOYMENT_TARGET)をiOS 15.0以上に設定してください。配布パッケージのplatforms宣言も.iOS(.v15)であるため、Swift Package Managerで追加すると15.0未満のアプリはパッケージ解決の段階でブロックされます。
XCFrameworkを直接追加した場合は解決段階の検査がなく、リンク時に警告としてのみ表示されます。
building for iOS x.y, but linking with dylib … which was built for newer version 15.0
ビルドは通りますが、15.0未満の端末での動作は保証しません。
Xcode 15以下はサポートしていません。
モジュールインターフェイスがSwiftUICoreを参照していますが、このモジュールはiOS 18 SDK(Xcode 16)で分離・新設されたものであり、それ以前のSDKには存在しません。
WebView連携に必要なバージョン
アプリ内のWebViewページまで併せて収集するには、iOSエージェントとブラウザエージェントの両方が必要バージョンを満たす必要があります。いずれか一方でもバージョンが低いと、一部の項目が収集されません。このとき、アプリとWeb のどちらのログにもエラーは残りません。
| Component | Minimum version | Owner |
|---|---|---|
| iOSエージェント | 2.7.15 | アプリ開発者 |
| ブラウザエージェント | 3.2.0 | Web開発者 |
2.7.15未満では、WebViewのデータがダッシュボードに表示されません。適用が完了したら、ダッシュボードのagent_versionの値で実際に動作中のバージョンを確認してください。ファイルを置き換えるだけでは、新しいバージョンが反映されたかどうかは分かりません。
.zipで受け取ったフレームワークは、チェックサムが案内された値と一致するかを確認します。
swift package compute-checksum WhatapAgent.xcframework.zip
ブラウザエージェントファイルのバージョンは、先頭4行のバナーで確認します。
head -4 whatap-browser-agent.js
コンソールのiOS WebViewインストール画面は、SPMパッケージのバージョンを2.7.3、ブラウザエージェントのスクリプトアドレスをv1のパスで案内します。WebView連携には上記の必要バージョンが必要であるため、画面の値をそのまま使用しないでください。
エージェントのインストール
次の順序で進めます。
1. ライブラリの追加
3つの方式のうち1つを選択します。Swift Package Managerの方式を推奨します。
Swift Package Manager(推奨)
-
Xcodeでプロジェクトを開き、File > Add Package Dependenciesメニューを選択してください。
-
パッケージURLフィールドに次のアドレスを入力してください。
https://github.com/whatap/WhatapIOSAgent-Release -
Releasesで最新バージョンを確認してそのバージョンを選択し、Add Packageボタンをクリックしてください。
branchを指定したり、上限のないfrom:を使用したりしないでください。 -
WhatapAgentライブラリをアプリのターゲットに追加してください。
Package.swiftで管理するプロジェクトの場合は、次のように指定します。
dependencies: [
.package(url: "https://github.com/whatap/WhatapIOSAgent-Release.git", exact: "<最新バージョン>")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "WhatapAgent", package: "WhatapIOSAgent-Release")
]
)
]
Swift Package Managerの方式もCDNへのアクセスが必要です。
GitHubリポジトリにはバイナリがなく、binaryTarget(url:checksum:)がCDNを指しています。ビルドマシンでgithub.comとrepo.whatap-mobile-agent.ioの両方が開いている必要があります。ファイアウォールでCDNがブロックされていると、swift package resolveの段階で失敗します。この場合はローカルファイルインストールを使用してください。
手動XCFrameworkインストール
-
XCFrameworkをダウンロードします。
<最新バージョン>の箇所には、Releasesで確認したバージョンを入れてください。curl -L -o WhatapAgent.xcframework.zip \
https://repo.whatap-mobile-agent.io/uploads/<最新バージョン>/WhatapAgent.xcframework.zip
unzip WhatapAgent.xcframework.zip -
Xcodeプロジェクトに追加します。
- プロジェクトのターゲットを選択し、Generalタブを選択してください。
- Frameworks, Libraries, and Embedded Contentセクションで**+**ボタンをクリックしてください。
- Add Other > Add Filesを選択し、
WhatapAgent.xcframeworkを選択してください。 - Embed & Signの設定を確認してください。
ローカルファイルインストール(閉域網、社内網)
外部リポジトリにアクセスできない環境では、WhaTapが提供したzipファイルを受け取って展開したうえで、プロジェクトに追加します。その後は手動XCFrameworkインストールと同じ方法で、XcodeでEmbed & Signとして追加してください。
unzip WhatapAgent.xcframework.zip
mv WhatapAgent.xcframework /path/to/YourApp/
2. エージェントの初期化
SDKはアプリの起動時に初期化する必要があります。初期化が遅れると、pre-main time、初期メモリ使用量など、アプリ起動初期の性能データを収集できません。
| Framework | 初期化の位置 |
|---|---|
| SwiftUI | @main structのinit()。アプリ構造体が作成される時点 |
| UIKit | application:willFinishLaunchingWithOptions:。didFinishLaunchingより先に呼び出される |
build()の後にinitialize()を必ず呼び出してください。
build()は設定を構成するだけです。initialize()を呼び出さなくてもsetupWebViewBridge()はそのまま動作してwindow.whatapBridgeが注入されるため、ブラウザエージェントはブリッジが正常であると判断し、HTTPフォールバックも行いません。その結果、Webとネイティブのデータがいずれも失われ、アプリのログにのみ次のエラーが残ります。
[ERROR] [WhatapWebviewBridge] WhatapAgentが初期化されていません。
initialize()は2回目の呼び出しから無視されます。すでに運用中のアプリにWebView連携を追加する場合は、初期化コードを新しく作らず既存のコードを修正してください。初期化を2か所に分けて入れても、後ろの呼び出しは適用されません。
Swift
SwiftUIアプリは@main structのinit()で初期化します。
import SwiftUI
import WhatapAgent
@main
struct MyApp: App {
init() {
let agent = WhatapAgentBuilder()
.setProjectKey("<PROJECT_ACCESS_KEY>")
.setPCode(12345)
.setServerUrl("https://<収集サーバードメイン>/m") // 末尾の /m は必須
.build()
agent.initialize()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
UIKitアプリは、AppDelegateのapplication:willFinishLaunchingWithOptions:で初期化することを推奨します。
import UIKit
import WhatapAgent
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let agent = WhatapAgentBuilder()
.setProjectKey("<PROJECT_ACCESS_KEY>")
.setPCode(12345)
.setServerUrl("https://<収集サーバードメイン>/m")
.build()
agent.initialize()
return true
}
}
Objective-C
willFinishLaunchingWithOptionsとdidFinishLaunchingWithOptionsのどちらの位置で初期化しても動作します。次はdidFinishLaunchingWithOptionsで初期化する例です。
#import <WebKit/WebKit.h> // WhatapAgentヘッダーより先に
#import <WhatapAgent/WhatapAgent-Swift.h>
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
WhatapAgentBuilder *b = [[WhatapAgentBuilder alloc] init];
(void)[b setProjectKey:@"発行されたアクセスキー"];
(void)[b setPCode:12345];
(void)[b setServerUrl:@"https://収集サーバーアドレス/m"]; // /m を含め、末尾に / は付けない
(void)[b setSamplingRate:1.0]; // 0.0 ~ 1.0(Webの 0 ~ 100 とは異なります)
WhatapIOSAgent *agent = [b build];
[agent initialize]; // この呼び出しがないとデータは送信されません
return YES;
}
ヘッダーを取り込む順序。 #import <WhatapAgent/WhatapAgent-Swift.h>より先に#import <WebKit/WebKit.h>を入れてください。生成ヘッダーはWKWebViewを前方宣言するだけで、定義を取り込みません。モジュールを無効にしたプロジェクト(CLANG_ENABLE_MODULES = NO)では、このヘッダーをimportするすべてのファイルが先にWebKitをimportする必要があります。そうしないとcannot find protocol declaration for 'WKNavigationDelegate'エラーが発生し、ビルドが失敗します。WebViewを扱わないAppDelegateも同様です。
@import WhatapAgent;ではなく#importの形式を使用してください。 @importも動作しますが、モジュールを無効にしたプロジェクトではuse of '@import' when modules are disabledエラーが発生してビルドが失敗します。また、umbrellaヘッダーが公開対象でない宣言までコード補完に露出させます。
ビルダーのsetterはすべてwarn_unused_resultです。 上の例のようにチェーンを切って複数行で記述しながら(void)キャストを省略すると、各行で-Wunused-result警告が発生します。-Werrorを使用するプロジェクトでは、この警告が原因でビルドが失敗します。1行のチェーンで記述すればキャストは不要です。
3. インストールの確認
アプリを実行した後、Xcode Consoleで初期化ログを確認してください。デバッグビルドでログを詳しく確認するには、次の設定を追加します。
#if DEBUG
WhatapLogger.isDebug = true
#endif
データが収集されない場合は、プロジェクトキー、PCode、サーバーURL、initialize()の呼び出し、サンプリング比率とディスクバッファを確認してください。
収集項目
build()とinitialize()を呼び出すと、次の項目を自動的に収集します。
- アプリの起動性能(Cold start、Warm start、pre-main)
- クラッシュ、例外、シグナル
- CPU、メモリ、バッテリー、熱状態
次の3つの項目は、build()とinitialize()を呼び出すだけでは自動的に有効になりません。別途の作業が必要です。
| Item | 必要な作業 |
|---|---|
| 画面別のロード時間 | 画面追跡の4つの方法のうち1つを適用 |
| ネットワークのリクエスト・レスポンス情報 | 収集とHTTPオプションのsetCollectNetwork(true)を指定 |
| WebViewページのロードと性能情報 | WebViewページ収集の設定のブリッジ登録 |
画面別のロード時間は自動的に有効になりません。
UIViewControllerのライフサイクルに基づく画面追跡は、デフォルトで無効です。setCollectScreenLoadingの初期値はtrueですが、エージェントのdisableAutoScreenTrackingの初期値もtrueです。Builderがこの値を戻さないため、画面追跡は自動的に有効になりません。有効にする方法は画面追跡を参照してください。
自動画面追跡を有効にしなくても、WebViewのデータは正常に収集されます。initialize()を呼び出してから0.3秒が経過すると、アプリ名(CFBundleName)でデフォルトのScreenGroupが1つ開始されます。WebViewページのロードはこのグループに紐付けられます 。自動追跡を使用しない場合は、ネイティブ画面単位の区分ができないだけです。
Builderオプション
すべてのBuilderオプションは任意であり、指定しない場合は初期値が適用されます。
送信とバッファのオプション
| Method | Default | Description |
|---|---|---|
setFlushInterval(_:) | 10.0 | バッチ送信の周期(秒) |
setQueueSize(_:) | 1000 | メモリキューのサイズ |
setMaxDiskBytes(_:) | 512000 | オフラインディスクバッファの上限(bytes) |
setMaxDiskFiles(_:) | 5 | ディスクバッファファイルの個数 |
収集とHTTPオプション
| Method | Default | Description |
|---|---|---|
setCollectScreenLoading(_:) | true | 画面ロードの収集 。画面追跡の設定も併せて必要 |
setCollectNetwork(_:) | false | 自動ネットワーク収集 |
setKeepAlive(_:) | true | HTTP keep-alive |
setMaxConnectionsPerHost(_:) | 4 | ホストあたりの最大接続数 |
ネットワーク収集はopt-inです。
iOS SDK 2.7.3から、自動ネットワーク収集の初期値はfalseです。この機能を有効にすると、URLProtocolを通じてリクエストを計測します。したがって、証明書ピンニング、ユーザー定義のURLSessionDelegate、独自のCookieや認証ヘッダー、HTTP/2ネゴシエーションに依存するアプリでは、影響を先に確認してください。セキュリティに敏感なリクエストは、別途のカスタムURLSessionに分離することを推奨します。
カスタムendpoint
| Method | Default | Description |
|---|---|---|
setLogServerUrl(_:) | serverUrl + "/log" | logエンドポイントを直接指定する場合にのみ使用 |
setSpanServerUrl(_:) | serverUrl + "/trace" | traceエンドポイントを直接指定する場合にのみ使用 |
setLogServerUrl(_:)とsetSpanServerUrl(_:)を直接指定した場合でも、setServerUrlは必ず設定する必要があります。詳細は収集サーバーアドレスの確認を参照してください。
ScreenGroupと追跡オプション
| Method | Default | Description |
|---|---|---|
setGroupWaitingInterval(_:) | 3.0 | 画面グループのクローズ遅延(秒) |
setExcludeLifecycleEventsFromScreenGroup(_:) | false | 画面グループからライフサイクルイベントを除外 |
enableMethodTracing(_:) | false | Method Tracingの使用有無 |
setSamplingRate(_:) | 1.0 | 収集比率。0.0 ~ 1.0のスケール。セッション単位で抽選 |
setSingleSessionPerLaunch(_:) | false | trueの場合、アプリの実行時に発行したセッションをプロセスが終了するまで維持 |
setSamplingRate(_:)は0.0 ~ 1.0の範囲の値を使用します。一方、ブラウザエージェントのsampleRateは0 ~ 100の範囲の値を使用します。したがって、モバイルエージェントの値をブラウザエージェントにそのまま入力してはいけません。モバイルの1.0をWebにそのまま入れると、収集比率が1%になります。
範囲外の値は無視され、全数収集のまま残ります。
0.0 ~ 1.0を外れた値を入れると、上限や下限に調整せず、直前に設定した有効な値をそのまま維持します。一度も設定していない場合は初期値の1.0です。つまり、誤った値を入れても収集が無効になるのではなく全数収集のまま残り、症状は「サンプリングがかからない」という形で現れます。
このとき次の警告が残りますが、WhatapLogger.isDebugを有効にしないと表示されません。収集量が設定と食い違う場合は、デバッグログを有効にしてこの文言を確認してください。
[WARN] [SESSION] configure: invalid samplingRate=50.0 - keeping 1.0
セッションが分かれる基準
初期値では、セッションは15分間ユーザーの操作がない場合、または開始後4時間が経過した場合に新しく発行されます。アプリを再実行した場合も新しいセッションです。サンプリングの抽選はセッション単位であるため、セッションが新しく発行されるたびに再度抽選され、1つのセッション内のデータはすべて収集されるか、まったく収集されないかのいずれかになります。
setSingleSessionPerLaunch(true)は上記2つの有効期限条件を適用しないため、セッションはアプリの実行ごとに1つだけ作られ、サンプリングの抽選もアプリの実行時に1回だけ行われます。セッション数で課金されるSaaSプロジェクトでは、初期値のfalseを維持してください。 セッション数が課金と無関係な設置型の環境でのみ有効にするオプションです。
WhatapAgentBuilder()
.setProjectKey("<PROJECT_ACCESS_KEY>")
.setPCode(12345)
.setServerUrl("https://<収集サーバードメイン>/m")
.setFlushInterval(120)
.setMaxDiskBytes(2 * 1024 * 1024)
.setMaxDiskFiles(5)
.setQueueSize(1000)
.setKeepAlive(true)
.setMaxConnectionsPerHost(4)
.setGroupWaitingInterval(3.0)
.build()
画面追跡
ネイティブ画面別のロード時間を収集するには、次の4つのうち1つを使用してください。
方法1. 自動追跡フラグを下げる。 この方法を使用すると、すべてのUIViewControllerを自動的に追跡します。initialize()を呼び出す前に設定してください。
// 方法1)initialize() の前にフラグを下げる。すべての UIViewController を自動追跡
WhatapIOSAgent.disableAutoScreenTracking = false
agent.initialize()
// 方法2)特定の画面のみ登録する(フラグを下げなくても動作)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
WhatapIOSAgent.trackViewController(self)
}
WhatapIOSAgent.disableAutoScreenTracking = NO; // 方法1、initialize の呼び出し前
[WhatapIOSAgent trackViewController:self]; // 方法2
方法3. SwiftUI modifier。 SwiftUIの画面では専用のmodifierを使用してください。trackViewController(_:)は、受け取ったインスタンスのクラスにswizzlingを適用するAPIです。したがって、UIHostingControllerをその場で作成して渡すと、意図した画面が捕捉されない場合があります。
struct CheckoutView: View {
var body: some View {
ContentView()
.whatapScreenTracking("決済画面") // onAppear / onDisappear を自動連結
}
}
自分で制御する場合は、WhatapIOSAgent.trackScreen("決済画面")とWhatapIOSAgent.endScreenTracking("決済画面")をペアで呼び出します。
方法4. 画面Taskを直接制御。 swizzlingをまったく使用しない場合です。いずれもインスタンスメソッドです。
guard let taskId = WhatapIOSAgent.shared?.startScreenTaskFor(self) else { return }
// または startScreenTaskWithName("決済画面")
WhatapIOSAgent.shared?.endScreenTaskWithTaskId(taskId)
自動画面追跡でクラス名の代わりに別の名前を使用するには、ViewControllerでWhatapScreenNamingプロトコルを採用してwhatapScreenNameを返してください。このプロトコルはWebView画面の名前には適用されません。WebView画面の名前を指定する方法は画面名の指定を参照してください。
手動連携
手動Task API
決済や画像のロードのように、1つの画面内の非同期処理を別のspanとして記録できます。
WhatapIOSAgent.startTask("checkout", taskId: "task-001")
WhatapIOSAgent.endTask("task-001")
Method Tracing
Method Tracingはopt-in機能です。BuilderにenableMethodTracing(true)を追加したうえで、必要なメソッドの性能を記録してください。
methodStartとmethodEnd
func validateBiometric(
agent: WhatapIOSAgent,
validate: () throws -> Void
) rethrows {
agent.methodStart(className: "AuthService", methodName: "validateBiometric")
defer {
agent.methodEnd(className: "AuthService", methodName: "validateBiometric")
}
try validate()
}
StackSpan
func charge(
agent: WhatapIOSAgent,
operation: () async throws -> Void
) async rethrows {
let stackSpan = agent.start(className: "PaymentService", methodName: "charge")
do {
try await operation()
stackSpan.end()
} catch {
stackSpan.endWithError(error)
throw error
}
}
グローバルコンテキスト
ExtrasStoreの値は、すべてのログとspanに自動的に付与されます。
import WhatapAgent
ExtrasStore.shared.setExtra(key: "user_id", value: userId)
ExtrasStore.shared.removeExtra(key: "user_id")
ExtrasStore.shared.clearExtras()
[[ExtrasStore shared] setExtraValue:userId forKey:@"user_id"];
ExtrasStoreに保存したキーは、Android互換のために送信時に自動的に.cサフィックスが付きます。user_idはuser_id.cとして送信されます。
画面グループ
複数の画面にまたがるフローを1つのグループにま とめます。
ChainView.shared.startChain(chainName: "LoginFlow")
ChainView.shared.endChain()
ネットワークとクラッシュ
ネットワークセキュリティ設定
HTTPSの収集エンドポイントには、App Transport Securityの例外は必要ありません。レガシーHTTPエンドポイントをどうしても使用する必要がある場合は、必要な単一ドメインにのみ個別に例外を適用してください。サブドメインや任意のロードは許可しないでください。
クラッシュレポート
クラッシュは自動的に収集され、次にアプリを実行したときに送信されます。初期値は内蔵レポーターです。PLCrashReporterを使用するには別途のライブラリが必要です。
WhatapAgentBuilder()
.useNativeCrashReporter()
.build()
WhatapAgentBuilder()
.usePLCrashReporter()
.build()
WebViewページ収集の設定
アプリのWKWebViewに表示されるWebページは、ネイティブSDKではなくブラウザエージェントが収集します。ネイティブ画面とWebViewページを同じセッションで紐付けるには、アプリとWebの両方を設定する必要があります。
3つの段階をすべて完了して初めて連携が完了します。いずれか1つでも欠けると、ブリッジに到達できなかったデータは標準HTTP送信にフォールバックします(ブラウザエージェントのオプションwebViewHttpFallback、初期値true)。この場合、データ自体は収集されます。しかしダッシュボードでは通常のブラウザ(BROWSER)として分類され、ネイティブセッションとは紐付きません。フォールバックはデータの欠損を防ぐための安全網にすぎず、連携が完了したという意味ではありません。
WebView連携には、iOSエージェント2.7.15以上とブラウザエージェント3.2.0以上が必要です。まずWebView連携に必要なバージョンを確認してください 。エージェントの初期化のinitialize()の呼び出しも完了している必要があります。initialize()を呼び出さないと、ブリッジは注入されますがデータは全量が失われます。
組み合わせ別の収集項目
iOS 26.5の実機と実際の収集サーバーで検証した結果です。
| Item | 標準適用 | 連携適用、ブリッジ接続 | 連携適用、ブリッジ未到達 |
|---|---|---|---|
| ページロード | ○ | ○ | ○(フォールバック) |
| リソース、AJAX | ○ | ○ | ○(フォールバック) |
| 画面遷移 | ○ | ○ | ○(フォールバック) |
| JSエラー5種 | ○ | ○ | ○(フォールバック) |
| ユーザーイベント | ○ | ○ | ○(フォールバック) |
| セッションリプレイ | ○ | ○ | ○(フォールバック) |
| Core Web Vitals | ○ | ○ | ○(フォールバック) |
| カスタムログ | ○ | ✗ | ○(フォールバック) |
| ダッシュボードの分類 | BROWSER | WEBVIEW | BROWSER |
| 蓄積されるプロジェクト | Webページのpcode | アプリのモバイルプロジェクト | Webページのpcode |
| ネイティブとのセッション統合 | - | ○ | ✗ |
標準適用はブラウザエージェントをisWebViewなしで初期化した状態であり、連携適用はisWebView: trueで初期化した状態です。JSエラー5種とは、uncaughtエラー、unhandled promise rejection、console.error()、noticeError()、CSP violationです。
カスタムログは連携適用では収集されません。
ブラウザエージェントは、カスタムログ(enableCustomLogとlogger)をブリッジメソッドlogに送ります。しかしモバイルブリッジには該当するインターフェイスがないため、カスタムログは静かに破棄されます。iOSではデバッグモードでも別途の表示が残らないため、ログが破棄されている事実をコンソールで確認できません。カスタムログが必要なページは、isWebViewを指定せず標準適用のままにしてください。
データが蓄積されるプロジェクト
連携が動作している間、Webのデータはアプリのモバイルプロジェクトに蓄積されます。ページに設定したpcodeやprojectAccessKeyには入りません。ブリッジに渡されたデータは、アップロードの直前に送信本文のmetaのプロジェクト識別値を、アプリのsetPCode、setProjectKeyの値に合わせます。sessionIDとuserIDもネイティブの値に統一されます。ネイティブ画面とWebView画面を1つのセッションとして確認するには、2つのデータが同じプロジェクトにある必要があります。そのために、2つの構成要素が同じ規格を使用します。
| Case | 照会するプロジェクト |
|---|---|
標準適用(isWebView未設定) | Webページのpcode |
| 連携適用、ブリッジ接続済み | アプリのモバイルプロジェクトのpcode |
| 連携適用、ブリッジ未到達(フォールバック) | Webページのpcode |
したがって、ページのpcodeとprojectAccessKeyはフォールバック送信にのみ使用します。連携が正常に動作している間は使用しません。ただし、ブリッジが切れる場合に備えて、ブラウザ(RUM)プロジェクトの有効な値を入れる必要があります。任意の値やアプリのモバイルpcodeを入れても、初期化段階の形式チェックは通過します。しかし、フォールバックで送信したデータはどこにも蓄積されません。
iOSアプリとAndroidアプリが互いに異なるモバイルプロジェクトを使用している場合、同じWebページのWebViewデータもプラットフォーム別に分かれて蓄積されます。HTMLをアプリごとに分ける必要はありません。1 つのWeb画面の全体の指標を確認するには、2つのプロジェクトをそれぞれ照会してください。
1. 収集サーバーアドレスの確認
ここで確認する値は、モバイルエージェントのsetServerUrlです。WebViewのデータもこのアドレスに送信されるため、WebView連携では特に重要です。
| Rule | Example |
|---|---|
スキーム、ホスト(ポート)、/mの順で終わる | https://収集サーバーアドレス:9443/m |
| リバースプロキシのパスを含めることが可能 | https://内部ホスト:9443/22/m |
末尾に/を付けない | https://…/m/は…/m//pageLoadとなり失敗する |
iOSは/mを自動的に付けないため、serverUrlには常に/mを含めてください。setLogServerUrlとsetSpanServerUrlを直接指定した場合でも、setServerUrlは必ず設定する必要があります。WebViewのデータはこの2つの値を使用しません。代わりにserverUrlの後ろにエンドポイントを付けて送信します。したがってserverUrlを空のままにすると、WebViewのデータが静かに消え、最初のspan送信でアプリが終了する場合があります。
社内のリバースプロキシを前段に置き、プロキシのパスを含む値を使用しても構いません。エージェントはこの値の後ろにパス をそのまま連結します。したがって、プロキシが次のパスをすべて転送するように設定すれば十分です。
| Data | <serverUrl>の後ろに付くパス |
|---|---|
| ネイティブのトレース、ログ | /trace /log |
| WebViewのページロード | /pageLoad |
| リソース、画面遷移、エラー、イベント、メモリ | /resource /routeChange /onError /event /memory |
| Web Vitals | /webVitals /v2/webVitals |
| セッションリプレイ | /sessionreplay /v2/sessionreplay |
2. WebViewの登録
WebViewを表示する画面で登録してください。必ずメインスレッドで、load()を呼び出す前に登録する必要があります。WebViewインスタンス1つにつき1回だけ呼び出してください。
UIKitアプリ
import WebKit
import WhatapAgent
final class WebViewController: UIViewController {
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: view.bounds)
view.addSubview(webView)
// ブリッジの登録。context には WebView を所有する ViewController を渡します
WhatapIOSAgent.setupWebViewBridge(webView, context: self)
// 登録が反映された後にロードされるよう、次のランループで呼び出します
DispatchQueue.main.async { [weak self] in
guard let self, let url = URL(string: "https://your.web.app") else { return }
self.webView.load(URLRequest(url: url))
}
}
}
#import <WebKit/WebKit.h> // WhatapAgentヘッダーより先に
#import <WhatapAgent/WhatapAgent-Swift.h>
- (void)viewDidLoad {
[super viewDidLoad];
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.webView];
// クラスメソッド(+)です。メインスレッドで、load の前に、インスタンスごとに1回だけ
[WhatapIOSAgent setupWebViewBridge:self.webView context:self];
// 登録が反映された後にロードされるよう、次のランループで呼び出します
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = [NSURL URLWithString:@"https://your.web.app"];
[weakSelf.webView loadRequest:[NSURLRequest requestWithURL:url]];
});
}
StoryboardまたはXIBで@IBOutlet WKWebView *webViewを受け取る構造の場合は、WebViewを生成するコードのみを除外してください。setupWebViewBridge:context:はそのままviewDidLoadで呼び出します。
登録のルール
| Rule | 守らない場合 |
|---|---|
load()を次のランループに遅らせる(推奨) | setupWebViewBridgeはハンドラーの登録とスクリプトの注入をDispatchQueue.main.asyncの中で処理する。同じ 流れですぐにload()しても実際には注入が先に反映されるのが一般的だが、キャッシュされたページ、file://、遅い端末ではタイミングが変わることがある。3行のコストの安全策であるため、新規コードには入れておくことを推奨 |
WKWebViewインスタンスごとに1回だけ登録(viewWillAppearではない) | 2回目の呼び出しはスキップされるが、画面グループの紐付けだけがもう1度実行され、不要なWebView_*グループが作られる。WebViewの生成時点の1回に固定すること。WebViewを再生成した場合は、新しいインスタンスに再度登録 |
[WhatapIOSAgent setupWebViewBridge:context:]で呼び出す | WhatapWebViewBridge(大文字のV)とWhatapWebViewManagerは公開APIではない。使用するとリンク段階でUndefined symbols for architecture …: "_OBJC_CLASS_$_WhatapWebViewBridge"として失敗する。ブリッジのインスタンスを直接扱う必要がある場合にのみ、小文字vのWhatapWebviewBridgeを使用 |
enableWebViewAutoBridge(_:)を使わない | サポートが終了したオプションであり、呼び出しても何も動作しない。YESに設定してもブリッジは有効にならない |
startDataUploadTimer()を直接呼び出さない | ブリッジの作成時に自動的に開始される。もう1度呼び出すと、同じ処理をするタイマーが1つ増えるだけ |
| JavaScriptを無効にしない | ブリッジはJavaScriptで動作する。WKWebViewはデフォルトで有効だが、アプリで無効にした場合はブリッジが動作しない |
| 自社コンテンツ をロードするWebViewにのみ登録 | 登録したWebView内でロードされるコンテンツは、iframeを含めブリッジにアクセス可能。オリジンからの逸脱を防ぐ地点はWKNavigationDelegateのwebView(_:decidePolicyFor:decisionHandler:) |
ブリッジが受け取ったデータはキューに蓄積され、5秒ごとに収集サーバーへ送信されます。このタイマーはブリッジの作成時に自動的に開始されるため、アプリ側ですることはありません。WebView画面を離れた後もブリッジのインスタンスが生きていれば、タイマーは動作し続けます。
iOSではWeb側でページロードを追跡します。setupWebViewBridge()はnavigationDelegateを変更しないため、既存のdelegateはそのまま維持されます。アプリが作るページロードの区間はありません。画面に付く前にロードが終わるWebViewがある場合は、画面の表示時点でWhatapRUM.endPageLoad()を呼び出してください。
画面名の指定
setupWebViewBridge()は、WebViewを所有するViewControllerのtitleをダッシュボードの画面名として使用します。titleがない場合はクラス名を使用します。画面名を直接指定するには、ブリッジのインスタンスを作成して使用してください。収集結果はsetupWebViewBridge()を使用した場合と同じで、画面名だけが変わります。
private var bridge: WhatapWebviewBridge! // インスタンスとして保持(ローカル変数は不可)
override func viewDidLoad() {
super.viewDidLoad()
bridge = WhatapWebviewBridge(context: self, screenName: "決済画面")
bridge.configureWebView(webView)
}
// @interface またはクラス拡張に。ローカル変数にせず、インスタンスとして保持します
@interface MyWebViewController ()
@property (nonatomic, strong) WhatapWebviewBridge *bridge;
@end
// @implementation の中に
- (void)viewDidLoad {
[super viewDidLoad];
self.bridge = [[WhatapWebviewBridge alloc] initWithContext:self screenName:@"決済画面"];
[self.bridge configureWebView:self.webView];
}
画面名は、収集データのscreen_nameとactivity_name属性に画面名 (URL)の形式で保存されます。WebView画面が複数あるアプリでは、この名前を使ってダッシュボードで各画面を区別します。名前を決める必要がない場合は、より短いsetupWebViewBridge()を使用してください。
自動画面追跡のWhatapScreenNamingプロトコルは、WebView画面の名前には適用されません。WebViewはWhatapWebviewBridge(context:screenName:)のscreenNameを使用し、指定しない場合はViewControllerのtitle、それもない 場合はクラス名を使用します。
SwiftUIアプリ
setupWebViewBridge(_:context:)を使用するにはUIViewControllerが必要です。したがって、SwiftUIでWKWebViewをラップする方式によって登録方法が変わります。
| ラップする方式 | 登録方法 |
|---|---|
UIViewControllerRepresentable(推奨) | ViewControllerがあるため、setupWebViewBridge(webView, context: self)をそのまま使用 |
UIViewRepresentable | ViewControllerがないため、WhatapWebviewBridge(screenName:)とconfigureWebView(webView)を使用 |
UIViewControllerRepresentableではUIKitと同じコードを使用できるため、この方式を推奨します。
import SwiftUI
import WebKit
import WhatapAgent
struct WhatapWebView: UIViewControllerRepresentable {
let urlString: String
final class Host: UIViewController {
var webView: WKWebView!
var urlString = ""
override func viewDidLoad() {
super.viewDidLoad()
title = "決済画面" // ダッシュボードの画面名になります
webView = WKWebView(frame: view.bounds)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)
WhatapIOSAgent.setupWebViewBridge(webView, context: self)
if let url = URL(string: urlString) { webView.load(URLRequest(url: url)) }
}
}
func makeUIViewController(context: Context) -> Host {
let vc = Host()
vc.urlString = urlString
return vc
}
func updateUIViewController(_ vc: Host, context: Context) {}
}
UIViewRepresentableしか使用できない場合は、screenNameで初期化してください。この場合、ブリッジが内部で代替のViewControllerを作成してcontextの位置を埋めます。
struct WhatapWebView: UIViewRepresentable {
let urlString: String
final class Coordinator {
var bridge: WhatapWebviewBridge?
}
func makeCoordinator() -> Coordinator { Coordinator() }
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView(frame: .zero)
let bridge = WhatapWebviewBridge(screenName: "決済画面")
bridge.configureWebView(webView)
context.coordinator.bridge = bridge // 下の説明を参照
if let url = URL(string: urlString) { webView.load(URLRequest(url: url)) }
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {}
}
ブリッジはCoordinatorに保持することを推奨します。ローカル変数にしてもWebKitがメッセージハンドラーとして保持するため動作します。しかし、ブリッジの寿命をコードで明示的に管理するほうが安全です。
SwiftUIでも、WebViewを生成するときに1回だけ登録します。makeUIViewとmakeUIViewControllerはビューが再生成されるときにのみ呼び出されますが、updateUIViewは毎回呼び出されます。したがって、登録コードをupdate…に入れると重複して登録されます。
SwiftUIアプリにはAppDelegateがないため、SDKの初期化もAppのinit()で行います。エージェントの初期化を参照してください。
3. ブラウザエージェントスクリプトの追加
WebViewが開くすべてのHTMLページのheadタグ最上部に、次のスクリプトを入れてください。Web担当者が進める作業です。
<script>
(function(w, h, _a, t, a) {
w = w[a] = w[a] || {};
w.config = {
projectAccessKey: "{PROJECT_ACCESS_KEY}",
pcode: {PCODE},
sampleRate: 100,
isWebView: true,
proxyBaseUrl: "https://rum-ap-northeast-2.whatap-browser-agent.io/"
};
a = h.createElement(_a);
a.async = 1;
a.src = t;
t = h.getElementsByTagName(_a)[0];
t.parentNode.insertBefore(a, t);
})(window, document, 'script', 'https://repo.whatap-browser-agent.io/rum/prod/v2/whatap-browser-agent.js', 'WhatapBrowserAgent');
</script>
<script>
window.WhatapBrowserAgent = {
config: {
projectAccessKey: "{PROJECT_ACCESS_KEY}",
pcode: {PCODE},
sampleRate: 100,
isWebView: true,
proxyBaseUrl: "https://rum-ap-northeast-2.whatap-browser-agent.io/"
}
};
</script>
<script src="https://repo.whatap-browser-agent.io/rum/prod/v2/whatap-browser-agent.js"></script>
設定キーとスクリプトアドレスはそのまま書き写してください。
isWebViewは大文字と小文字を区別します。isWebviewのように記述すると、不明なキーとして警告なしに無視され、ネイティブセッションとつながりません。- WebView用のスクリプトアドレスは
/rum/prod/v2/whatap-browser-agent.jsです。v1のアドレスにはBridge連携がありません。 - Asyncスニペットの5番目の引数
'WhatapBrowserAgent'は変更しないでください。この値は設定を格納するグローバルオブジェクトの名前です。5番目の引数をバンドルのパスに変えると、エージェントが設定を見つけられません。このときコンソールにメッセージも残らず、初期化も行われません。エージェントファイルを自社でホスティングする場合は、バンドルのパスを指定する4番目の引数を変更してください。
| Config | Description |
|---|---|
projectAccessKey | ブラウザ(RUM)プロジェクトのアクセスキー。フォールバック送信に使用 |
pcode | ブラウザ(RUM)プロジェクトのプロジェクトコード。フォールバック送信に使用 |
sampleRate | 収集比率(%)。0 ~ 100のスケールであり、モバイルエージェントのsetSamplingRate(0.0 ~ 1.0)とは異なる。適用確認中は100を使用 |
isWebView | データ送信をモバイルエージェントに委任するスイッチ。連携適用ではtrueを指定 |
proxyBaseUrl | フォールバック送信時にデータを送るアドレス。連携適用でも必須。設定しないとデータがまったく送信されない |
ブラウザ(RUM)プロジェクトの管理 > エージェントインストール画面で、projectAccessKey、pcode、proxyBaseUrlの値を確認できます。各項目には現在のプロジェクトの値が入力されています。
セキュリティポリシーの確認
ハイブリッドアプリでは、index.htmlにCSP(Content Security Policy)を宣言している場合が多くあります。
<meta http-equiv="Content-Security-Policy"
content="connect-src 'self' https://収集サーバーアドレス;
script-src 'self'">
| Directive | 許可するオリジン |
|---|---|
connect-src | 収集サーバーのオリジン。標準適用では常時必要であり、連携適用でもwebViewHttpFallback(初期値true)が有効であればブリッジ未到達時にこの経路で送信するため必要。webViewHttpFallback: falseでフォールバックを無効にした場合にのみ省略可能 |
script-src | エージェントファイルのオリジン。エージェントファイルをページと同じオリジンに置く場合は'self'で十分であり、別のオリジンに置いた場合はそのオリジンを追加 |
CSPではポートが異なるとホストソースを互いに異なるオリジンとして扱います。したがって:9443のようにポートまで記述してください。初期化スクリプトをインラインで記述する場合は、nonceまたはハッシュが必要です。
フォールバック送信はPOSTとContent-Type: text/plainを使用するため、OPTIONS preflightはありません。WAFがJSONではないという理由でリクエストをブロックするルールがないかを確認してください。また、pageLoad、routeChange、resource、onError、event、v2/webVitals、sessionreplay、v2/sessionreplay、rumlogのパスが許可されているかも確認してください。
モバイルエージェントがネイティブから送信するデータは、WebViewのCSPの対象ではありません。したがってconnect-srcには、モバイルエージェントの収集ホストではなく、WebページのproxyBaseUrlのホストを許可する必要があります。
WebViewのデータが収集されない場合
| 症状 | 原因 | 対処 |
|---|---|---|
| WebViewのデータがどこにも見当たらない | Webページのpcodeを照会している | アプリのモバイルプロジェクトを照会 |
| Webとネイティブのデータがいずれも失われる | initialize()が未呼び出し | アプリのログにWhatapAgentが初期化されていませんが残る。エージェントの初期化を確認 |
| データは届くが通常のブラウザとして分類される | WebViewの登録がページロードより遅い | 登録の後にロードする順序を確認。ブラウザエージェントは初期化の時点で1回だけWebView環境を判別するため、ロード後にブリッジが注入されても送信経路は変わらない |
| データがまったく収集されない | proxyBaseUrlが未設定 | 連携適用でも設定する |
| WebViewのデータが静かに消える | setServerUrlが空 | logとspanのURLを直接指定した場合でもsetServerUrlを設定すること |
| すべてのリクエストが404 | setServerUrl末尾の/によりパスが//で生成される | 末尾の/を削除 |
| データがほとんど届かない(約1%) | モバイルの1.0をWebのsampleRateにそのまま入力 | Webは0 ~ 100のスケール。sampleRate: 100を指定 |
リンク段階でUndefined symbols | 大文字VのWhatapWebViewBridge、WhatapWebViewManagerを使用 | [WhatapIOSAgent setupWebViewBridge:context:]または小文字vのWhatapWebviewBridgeを使用 |
cannot find protocol declaration for 'WKNavigationDelegate' | WhatapAgent-Swift.hよりWebKit/WebKit.hが後ろにある | ヘッダーの順序を変更。該当するヘッダーをimportするすべてのファイルに適用 |
| カスタムログのみ収集されない | 連携適用ではブリッジがlogを処理しない | 該当ページは標準適用のまま維持 |
不要なWebView_*グループが作られる | viewWillAppearでの重複登録 | 登録をWebViewの生成時点の1回に固定 |
アプリのログにWebView already configured - skipping duplicate | 該当WebViewにブリッジが注入されていない状態 | 登録対象のWebViewインスタンスを確認 |
まずXcode Consoleで初期化ログとブリッジ接続ログを確認してください。その後、Safari WebインスペクタのコンソールでWhatapRUM.VERSIONとWhatapRUM.getAgentContext()?.platformの値を確認してください。
正常に連携されると、Mobileダッシュボードでネイティブ画面とWebViewページが同じセッションに時系列で表示されます。WebViewダッシュボードでは、ページロード、Web Vitals、リソースタイミングを確認できます。
トラブルシューティング
SDKの初期化失敗
- プロジェクトキーとPCodeに正しい値が設定されているかを確認してください。
- デバイスがインターネットに接続されているかを確認してください。
- 提供された収集サーバーアドレスが正確かを確認してください。
/mで終わる必要があります。 build()の後にinitialize()を呼び出したかを確認してください。
データが収集されない
setSamplingRate(1.0)で全数収集されるように 設定されているかを確認してください。Info.plistのApp Transport Securityの設定が正しいかを確認してください。- 画面別のロード時間が表示されない場合は、画面追跡を確認してください。自動追跡は初期値が無効です。
- ネットワークデータが表示されない場合は、
setCollectNetwork(true)を指定したかを確認してください。初期値はfalseです。
クラッシュレポートが送信されない
- クラッシュは次回のアプリ実行時に送信されます。アプリを再実行してみてください。
- シミュレーターでは一部のクラッシュがキャプチャされない場合があります。実機でのテストを推奨します。
ビルドエラー
building for iOS x.y, but linking with dylib …の警告は、デプロイターゲットを確認してください。- Objective-Cプロジェクトのヘッダー関連のエラーは、Objective-Cの節のヘッダー順序の案内を確認してください。
サポートの依頼
問題が発生した場合は、次のチャネルにお問い合わせください。
- メール: support@whatap.io
- 技術ドキュメント: https://docs.whatap.io
技術サポートを依頼する際に次の情報を併せてお伝えいただくと、より早く解決できます。
- プロジェクトキー
- iOSバージョンとXcodeバージョン
- エージェントバージョン
- エラーメッセージまたはXcode Consoleのログ