Оптимизация получения флоу и пейволов в iOS SDK
Надёжное получение флоу или пейвола в iOS решает три задачи: быстрый рендеринг, возврат варианта с таргетингом по аудитории и корректный фолбэк при медленной сети. Правила ниже охватывают тайминг, кэширование и резервные паттерны для достижения этих целей.
Предполагается, что 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 см. в Получение пейволов и продуктов, а советы по выбору плейсмента — в Плейсменты.
Предварительная загрузка плейсментов
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 Config | getFlow | Да |
| 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. Чтобы медиа первого экрана отображалось мгновенно, можно раздавать его из бандла приложения. Это удобно для повторного использования медиа, которое уже входит в поставку, — например, визуалов существующего нативного онбординга.
- В Flow & Paywall Builder задайте custom media ID для изображения или видео. Загруженный файл остаётся резервным.
- Добавьте файл в бандл приложения.
- При вызове
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независимо, чтобы медленный профиль не задерживал интерфейс.