prefetch(_:warmMedia:)warms one or more placements on demand and tells you, per placement, exactly what is now ready.prefetchOnLaunchdeclares placements to warm automatically in the background right afterconfigure(...).isPlacementReady(_:)checks whether a flow is available, and leaves it warm as a side effect.
prefetch
prefetch resolves each placement key and caches the result (the flow JSON, in memory and on disk) plus any custom fonts the flow uses, so a later present can serve from cache instead of waiting on the network. It runs the keys concurrently, de-duplicates the input, and never throws: instead of swallowing failures, it returns a [String: PrefetchOutcome] so you can see, per placement, what is warm and what failed, without a second round-trip.
prefetch is not @MainActor; call it from any async context (for example a launch Task).
@discardableResult, so a bare await prefetch([...]) (ignoring the return value) keeps working unchanged.
Warming media
By default a bareprefetch([...]) warms the flow JSON and fonts only, not images. To also warm images, opt in with warmMedia: true:
warmMedia is true, how many images are warmed is bounded by prefetchMediaStrategy (first screen by default; see the table under Prefetch at launch). Media warming is best-effort: a failed image download never fails the prefetch, and PrefetchOutcome.mediaWarmed tells you whether it ran. See Media preloading for how images are otherwise loaded at present time.
PrefetchOutcome
| Field | Meaning |
|---|---|
placementKey | The placement this outcome is for. |
state | .warmed(fromCache:), .noFlow, or .failed(_). See below. |
mediaWarmed | true when images were warmed into the image cache as part of this prefetch. Always false unless you requested media warming and the flow is presentable. |
isWarmed | Convenience: true when state is .warmed. |
.warmed(fromCache:), fromCache is true when the flow came from a local cached copy (a fresh hit or a last-known-good stale entry) without a fresh network resolve, and false when it was freshly resolved from the network or served from a bundled default.
Prefetch at launch
Declare placements to warm automatically right afterconfigure(...). Warming runs in the background at utility priority and never blocks startup.
prefetch([...]), launch prefetch does warm images by default, governed by prefetchMediaStrategy:
| Strategy | Warms |
|---|---|
.none | flow JSON + fonts only |
.firstScreen (default) | + first-screen and persistent-zone (navigation bar, footer, overlay) images |
.allScreens | + every screen’s images |
POST /apps/{appId}/placements/resolve-batch), then falls back to per-placement resolves if that endpoint is unavailable. Either way the result is the same warmed cache.
isPlacementReady
isPlacementReady returns true if a flow is available for the placement. It checks the cache first (a cache hit returns true without a network call); if there is no cache, it performs a live resolve. It is not @MainActor and never throws (a failed resolve returns false).
Crucially, the resolve it performs is not wasted: it routes through the same cache-populating path as prefetch, so a presentable flow is left warm and the following presentPlacement hits the cache with no second round-trip.
Identity-correct caching
A resolved flow is personalized by the user identity and targeting attributes it was resolved under. The cache reflects this: a warmed flow is only reused for the same identity and attributes it was prefetched with. If you change the user (for example after login) or update targeting attributes viaupdateContext, the next present resolves fresh rather than serving the previously warmed flow. This keeps targeting and experiment assignment correct: a flow warmed while anonymous is never shown to a since-identified user. The stale-cache offline fallback (Tier 2) still works across an identity change. See Caching.
In-flight coalescing
Concurrent resolves for the same placement and identity share a single network call. If youprefetch a placement and then presentPlacement it before the prefetch finishes (or fire two prefetches for the same key), only one resolve goes to the network; the others await the same result. A caller hitting its own resolveTimeout does not cancel the shared resolve, so it still completes and populates the cache for the next caller.
When to prefetch
- At app launch, with
prefetchOnLaunch, for the first placement the user will hit (oftenonboarding). - Before a known entry point, for example call
prefetch(["paywall"])when the user lands on a screen that has an upgrade button. - Not everything. Each key is a resolve (a network call when not cached). Prefetch only what you are reasonably likely to show.
Notes and warnings
- Best-effort, not a guarantee. A
.warmedoutcome or atruefromisPlacementReadyis a strong hint, not a promise: conditions can change between the check and the present (context updates, experiment bucketing, a paused placement), so the present can still resolve differently. Always handle the “no flow” path at present time (catch the error or use thefallbackoverload). - Readiness can be
falseoffline. If a placement is not cached and the device is offline, the live resolve fails andisPlacementReadyreturnsfalse, even if a bundled default would let the present succeed. - Media warming is bounded and best-effort. It respects the image cache’s concurrency cap, skips images already cached, and never fails the prefetch. Lottie and video assets are not warmed.
Common mistakes
- Prefetching everything. Each key triggers a resolve. Prefetch only the placements you expect to present soon.
- Expecting a bare
prefetchto warm images. A bareprefetch([...])warms JSON and fonts only. PasswarmMedia: true, or useprefetchOnLaunch(which warms media perprefetchMediaStrategy), to remove first-screen image pop-in. - Assuming readiness means a flow will show.
isPlacementReadyreflects the moment you call it. Still handle a missing flow when you present (use the fallback overload or catch the error). - Calling
isPlacementReadyrepeatedly to poll. Check it once at the point you need the decision. (The first call leaves the placement warm, so the following present is cheap.) - Expecting launch prefetch to work in development. With
.development(or a0-TTL backend), warmed entries expire immediately. Test launch prefetch against.staging/.production.