优化 iOS SDK 中的流程与付费墙获取
在 iOS 上可靠地获取流程或付费墙需要做到三点:渲染速度快、返回面向目标受众的实验变体,以及在网络较慢时能优雅地降级。以下规则涵盖了实现这些目标所需的时机、缓存与备用方案。
以下规则假设 Adapty.activate() 和 Adapty.identify() 已完成调用。详见 iOS SDK 调用顺序。
规则与注意事项
| 该做什么 | 不该做什么 | 原因 |
|---|---|---|
获取即将展示的版位,或通过 preloadFlows(SDK 4.1+)预热缓存。 | 在启动时自行发起并发的 getFlow 请求。 | 手动实现的预取批量请求会阻塞主线程并导致黑屏。preloadFlows 专为此场景设计,整个批次共享同一个超时预算。 |
在归因有机会完成解析后再调用 getFlow——例如,在 activate 之后等待 1–2 秒,或等 didLoadLatestProfile 触发之后。 | 在 App.init() 时调用 getFlow。 | 此时归因数据尚未到达,流程会按默认目标受众进行解析,悄无声息地绕过市场细分和 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(必填):需要预加载的版位。空白和重复的 ID 会被忽略。loadTimeout(可选):整批请求的超时时间(单位:秒),而非单个版位的超时时间。默认为 5 秒,低于 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 下载图片和视频。若要让首屏媒体资源立即显示,可以从应用包中直接提供。这样可以复用已随应用分发的媒体资源,例如现有原生用户引导中的视觉素材。
- 在 Flow & Paywall Builder 中,为图片或视频设置自定义媒体 ID。您在那里上传的文件将作为备用资源。
- 将该文件添加到应用包中。
- 调用
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。