Оптимизация получения флоу и пейволов в iOS SDK

Надёжное получение флоу или пейвола в iOS решает три задачи: быстрый рендеринг, возврат варианта с таргетингом по аудитории и корректный фолбэк при медленной сети. Правила ниже охватывают тайминг, кэширование и резервные паттерны для достижения этих целей.

Tip

Предполагается, что Adapty.activate() и Adapty.identify() уже выполнены. См. Порядок вызовов в iOS SDK.

Правила и подводные камни

Делайте такНе делайте такПочему
Получайте плейсмент непосредственно перед показом или прогрейте кэш с помощью preloadFlows (SDK 4.1+).Не запускайте собственные параллельные вызовы getFlow при запуске.Самодельный параллельный prefetch блокирует главный поток и приводит к чёрному экрану. preloadFlows создан именно для этого и делит один таймаут на весь пакет запросов.
Вызывайте getFlow после того, как атрибуция успела разрешиться — например, через 1–2 секунды после activate или после срабатывания didLoadLatestProfile.Не вызывайте getFlow в App.init().Атрибуция ещё не пришла. Флоу разрешается по аудитории по умолчанию и молча обходит сегменты и персонализацию ASA.
Устанавливайте loadTimeout и настраивайте резервный пейвол для каждого плейсмента.Не ждите getFlow бесконечно.Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или просто закрывают приложение.

Когда срабатывает loadTimeout при любом запросе — включая обычный getFlow — SDK возвращает кэшированный вариант, если он существует, иначе в оставшееся время запрашивает вариант аудитории по умолчанию (All Users). Таргетинг теряется для этого запроса, а не откладывается: сегменты и аудитории на основе атрибуции не применяются к результату.

Описание параметров fetchPolicy и loadTimeout см. в Получение пейволов и продуктов, а советы по выбору плейсмента — в Плейсменты.

Предварительная загрузка плейсментов

Info

preloadFlows и preloadFlowsForDefaultAudience доступны начиная с версии SDK 4.1.

preloadFlows заблаговременно кэширует JSON флоу — по одному запросу на плейсмент. Дальше вы работаете с ним как обычно: getFlow — для получения флоу, getFlowConfiguration — для его конфигурации отображения.

fetchPolicy определяет, из какого слоя последующий getFlow будет читать данные в первую очередь, но не то, может ли он вообще обратиться к кэшу:

  • .returnCacheDataElseLoad сначала читает предзагруженную копию и обращается к сети только при отсутствии кэша. .returnCacheDataIfNotExpiredElseLoad(maxAge:) делает то же самое, пока копии не исполнилось maxAge.
  • По умолчанию используется .reloadRevalidatingCacheData — сначала идёт запрос к сети, и лишь при его неудаче или таймауте используется предзагруженная копия.

Предзагрузка полезна в обоих случаях, но по-разному: политика «кэш в приоритете» полностью исключает запрос к сети, а политика по умолчанию сохраняет запрос, но даёт тёплую копию на случай сбоя.

Используйте это, когда вы знаете, какие плейсменты понадобятся в сессии, но не хотите показывать их сразу — например, сразу после того, как отработали activate и identify, для флоу за кнопкой, которую пользователь ещё не нажал.

Параметры:

  • placementIds (обязательный): плейсменты для предзагрузки. Пустые и дублирующиеся идентификаторы игнорируются.
  • loadTimeout (необязательный): таймаут в секундах для всего пакета, а не для отдельного плейсмента. По умолчанию 5 секунд; значения меньше 1 секунды повышаются до 1 секунды.

Важные особенности поведения:

  • Метод выбрасывает исключение только после попытки обработать все плейсменты, а ошибка объединяет сбои по каждому плейсменту. Сбой одного плейсмента не останавливает остальные.
  • Если для плейсмента истёк таймаут или произошла сетевая ошибка, SDK откатывается к вариации аудитории по умолчанию для этого плейсмента. Остальные сбои сообщаются как есть.
  • Если таймаут срабатывает до завершения получения данных для целевой аудитории, SDK всё равно пытается получить вариацию аудитории по умолчанию в рамках оставшегося бюджета.
  • Предзагрузка только прогревает кэш. Она не возвращает контент — для отображения по-прежнему нужно вызвать getFlow.

Что охватывает предзагрузка

Флоу появляется на экране послойно. Предзагрузка охватывает первый слой — ровно то же, что и getFlow:

СлойКто загружаетПрогревается предзагрузкой
Flow JSON — выбранный вариант, ID продуктов и Remote ConfiggetFlowДа
UI-лейаут — структура, стили и текст экранаgetFlowConfigurationНет
Изображения, включая статичный кадр-заглушку для видеоэлементаgetFlowConfiguration, в фонеНет
ВидеофайлыСистемный плеер при рендеринге экранаSDK не кэширует

getFlowConfiguration ожидает загрузки макета, поэтому первый запрос для данного макета требует сетевого обращения даже после предзагрузки. Затем SDK сохраняет этот макет в собственном дисковом кеше, который переживает перезапуски приложения и читается до любого сетевого вызова — таким образом, затраты приходятся на первый запрос, а не на каждый последующий. Как только SDK получает макет, он начинает кешировать изображения независимо от вызова: экран не блокируется, и никакой обратный вызов, метод делегата или ошибка не сообщают о завершении этого процесса.

Узнайте, какой плейсмент не удалось загрузить

Выброшенная ошибка — это единый AdaptyError, охватывающий весь пакет, с кодом networkFailed (2005). Чтобы увидеть отдельные сбои, прочитайте свойство preloadErrors — словарь, ключами которого являются идентификаторы плейсментов:

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

preloadErrors имеет значение nil для любых ошибок, не связанных с вызовом preload, поэтому значение nil следует интерпретировать как «не сбой preload», а не «ошибок нет».

Пропуск сегментации аудитории

Чтобы прогреть кэш без ожидания сегментации аудитории, используйте вариант default-audience:

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

Показ медиа первого экрана из бандла приложения

Флоу загружает изображения и видео с серверов Adapty. Чтобы медиа первого экрана отображалось мгновенно, можно раздавать его из бандла приложения. Это удобно для повторного использования медиа, которое уже входит в поставку, — например, визуалов существующего нативного онбординга.

  1. В Flow & Paywall Builder задайте custom media ID для изображения или видео. Загруженный файл остаётся резервным.
  2. Добавьте файл в бандл приложения.
  3. При вызове getFlowConfiguration передайте файл из бандла для этого ID через assetsResolver:
// "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 независимо, чтобы медленный профиль не задерживал интерфейс.