iOS SDKでのフロー・ペイウォールのフェッチを最適化する

iOS での信頼性の高いフロー・ペイウォールのフェッチには、3つのことが必要です:高速なレンダリング、オーディエンスターゲティングに基づくバリアントの返却、そしてネットワークが遅い場合のグレースフルなフォールバック。以下のルールは、これを実現するためのタイミング、キャッシュ、フォールバックのパターンを説明します。

Tip

このルールは、Adapty.activate() と Adapty.identify() がすでに解決済みであることを前提としています。詳細は iOS SDK の呼び出し順序 を参照してください。

ルールと注意点

これをやるこれをやらない理由
表示しようとしているプレースメントをフェッチするか、preloadFlows(SDK 4.1+)でキャッシュをウォームアップする。起動時に独自の並行 getFlow 呼び出しを複数実行する。自前のプリフェッチバーストはメインスレッドをブロックし、ブラックスクリーンを引き起こす。preloadFlows はこの用途向けに設計されており、バッチ全体で1つのタイムアウト予算を共有する。
アトリビューションが解決される機会があった後(例: activate の1〜2秒後、または didLoadLatestProfile が発火した後)に getFlow をフェッチする。App.init() で getFlow を呼び出す。アトリビューションがまだ反映されていない。フローはデフォルトのオーディエンスに対して解決され、セグメントや ASA パーソナライゼーションが無音でバイパスされる。
loadTimeout を設定し、すべてのプレースメントにフォールバックペイウォールを設定する。getFlow を無期限に待機する。タイムアウトがないと、接続状態が悪いユーザーはネットワークが解決するまで空白の画面を見続けるか、アプリを閉じてしまう。

loadTimeout がいずれかのフェッチで発火した場合(通常の getFlow を含む)、SDKはキャッシュされたバリアントが存在すればそれを返し、存在しない場合は残り時間内にデフォルトオーディエンス(All Users)のバリアントをフェッチします。ターゲティングはその時点で失われ、遅延されるわけではありません。つまり、セグメントやアトリビューションに基づくオーディエンスは結果に適用されません。

fetchPolicy と loadTimeout パラメータの詳細についてはペイウォールとプロダクトのフェッチを、適切なプレースメントの選択についてはプレースメントを参照してください。

プレースメントのプリロード

Info

preloadFlows と preloadFlowsForDefaultAudience は SDK バージョン 4.1 以降で利用できます。

preloadFlows はフローの JSON を事前にキャッシュします(プレースメントごとに 1 リクエスト)。その後は通常通り使用できます。フローには getFlow、ビュー設定には getFlowConfiguration を使います。

fetchPolicy は後続の getFlow がどのレイヤーを最初に読み込むかを決定するものであり、キャッシュにアクセスできるかどうかを制御するものではありません:

  • .returnCacheDataElseLoad はまずプリロードされたコピーを読み込み、キャッシュに何もない場合のみネットワークにアクセスします。.returnCacheDataIfNotExpiredElseLoad(maxAge:) は同様の動作をしますが、コピーが maxAge より新しい場合に限ります。
  • デフォルトの .reloadRevalidatingCacheData はまずネットワークにアクセスし、リクエストが失敗またはタイムアウトした場合にプリロードされたコピーにフォールバックします。

いずれの場合もプリロードは有効ですが、効果の現れ方が異なります。キャッシュ優先ポリシーではリクエスト自体がなくなるのに対し、デフォルトではリクエストは維持されつつ、フォールバック用のウォームコピーが得られます。

どのプレースメントがセッションで必要になるかわかっているが、まだ表示したくない場合に使用します。たとえば、activate と identify が解決した直後、ユーザーがまだタップしていないボタンの背後にあるフローに対して使用します。

パラメーター:

  • placementIds(必須):プリロードするプレースメント。空白や重複するIDは無視されます。
  • loadTimeout(任意):バッチ全体のタイムアウト(秒)。プレースメントごとではありません。デフォルトは5秒で、1秒未満の値は1秒に切り上げられます。

知っておくべき動作:

  • このメソッドはすべてのプレースメントへの試行が終わった後にのみエラーをスローし、エラーには各プレースメントの失敗内容がまとめて含まれます。1つのプレースメントが失敗しても他のプレースメントの処理は継続されます。
  • プレースメントがタイムアウトまたはネットワークエラーで失敗した場合、SDKはそのプレースメントのデフォルトオーディエンスのバリアントにフォールバックします。その他のエラーはそのまま報告されます。
  • オーディエンス対象のフェッチが完了する前にタイムアウトが発生した場合でも、SDKは残りの時間内でデフォルトオーディエンスのバリアント取得を試みます。
  • プリロードはキャッシュを温めるだけです。コンテンツを返すわけではないため、表示するには引き続き getFlow を呼び出す必要があります。

プリロードがカバーする範囲

フローは段階的に画面に表示されます。プリロードは最初の段階をカバーします。これは getFlow と同じ動作です。

レイヤー取得元プリロードによるキャッシュ
フロー JSON — 選択されたバリアント、そのプロダクト ID、リモートコンフィグgetFlowあり
UI レイアウト — 画面の構造、スタイル、テキストgetFlowConfigurationなし
画像(動画要素の代わりに表示される静止フレームを含む)getFlowConfiguration(バックグラウンドで実行)なし
動画ファイル画面レンダリング時にシステムプレイヤーが取得SDK ではキャッシュされない

getFlowConfiguration はレイアウトの取得を待機するため、プリロード後でも特定のレイアウトへの最初のリクエストにはラウンドトリップのコストがかかります。その後、SDK はそのレイアウトを独自のディスクキャッシュに保持します。このキャッシュはアプリの再起動後も存続し、ネットワーク呼び出しより先に読み込まれるため、コストは毎回ではなく最初のリクエスト時のみ発生します。SDK がレイアウトを取得すると、呼び出しとは独立して画像のキャッシュを開始します。この処理は画面表示をブロックせず、完了を通知するコールバック、デリゲートメソッド、エラーも一切ありません。

どのプレースメントが失敗したかを調べる

スローされるエラーはバッチ全体をカバーする単一の AdaptyError で、コードは networkFailed (2005) です。個々の失敗を確認するには、preloadErrors プロパティを参照してください。このプロパティはプレースメント ID をキーとするディクショナリです。

do {
    try await Adapty.preloadFlows(placementIds: ["onboarding", "main_paywall"])
} catch {
    for (placementId, placementError) in error.preloadErrors ?? [:] {
        // log or retry the individual placement
    }
}

preloadErrors は、プリロードの呼び出し以外で発生したエラーの場合は nil になります。そのため、nil は「エラーなし」ではなく「プリロードの失敗ではない」として扱ってください。

オーディエンスのセグメンテーションをスキップする

オーディエンスのセグメンテーションを待たずにキャッシュをウォームアップするには、デフォルトオーディエンスのバリアントを使用します:

try await Adapty.preloadFlowsForDefaultAudience(placementIds: ["main_paywall"])

アプリバンドルから最初の画面のメディアを表示する

フローは画像や動画を Adapty からダウンロードします。最初の画面のメディアを即座に表示するには、アプリバンドルから提供するようにします。これは、既存のネイティブオンボーディングのビジュアルなど、すでに同梱しているメディアを再利用する良い方法です。

  1. Flow & Paywall Builder で、画像または動画にカスタムメディア ID を設定します。そこでアップロードしたファイルはフォールバックとして残ります。
  2. アプリバンドルにそのファイルを追加します。
  3. getFlowConfiguration を呼び出す際に、assetsResolver を通じてそのIDに対応するバンドルされたファイルを渡します。
// "welcome_video" is the custom media ID set in the Flow & Paywall Builder
let bundledAssets: [String: AdaptyCustomAsset] = [
    "welcome_video": .video(
        .file(
            url: Bundle.main.url(forResource: "welcome", withExtension: "mp4")!,
            preview: .uiImage(value: UIImage(named: "welcome_poster")!),
            resolution: CGSize(width: 1080, height: 1920)
        )
    ),
]

let flowConfig = try await AdaptyUI.getFlowConfiguration(
    forFlow: flow,
    assetsResolver: bundledAssets
)

バンドルファイルはアプリのダウンロードサイズを増やすため、最初にユーザーが目にするメディアのみをバンドルしてください。

バンドルしないメディアもすぐに表示されます。ビュー設定には各画像(動画のスチルフレームを含む)の小さな低解像度コピーが含まれており、全ファイルが読み込まれるまでその画像が表示されます。

assetsResolver の完全なリファレンスについては、アセットのカスタマイズを参照してください。

通信状況が悪い環境向けのチューニング

通信状況が常に悪い市場(農村部、交通機関、ルーティングの問題が多い地域など)向けの設定:

  • 初回以外のすべてのフェッチで fetchPolicy: .returnCacheDataElseLoad を設定する。
  • Adapty ダッシュボードのすべてのプレースメントにフォールバックペイウォールを設定する。
  • loadTimeout を 3〜5 秒に設定し、タイムアウト発生時はフォールバックを使用する。
  • getProfile() の完了をフロー表示の条件にしない。getFlow は独立して呼び出すことで、プロファイルの遅延が UI をブロックしないようにする。