iOS SDK (Swift) / Caching, offline & errors

Caching, offline & errors

A flow is fetched cache-first and revalidated with an ETag. A user with no signal sees the last known-good version, not a spinner.

How a fetch goes

  1. A cached copy for this flow key is rendered immediately, if there is one.
  2. The SDK revalidates in the background with the stored ETag. A 304 means the cache was already current and nothing changes on screen.
  3. A newer version replaces the cache for next time. A flow does not swap out underneath someone who is halfway through it.

forceRefresh: true skips step 1 — useful behind a pull-to-refresh in your own debug menu, and rarely what you want in production.

swift
UpliftFunnelFlowView(flowKey: "welcome") { result in
    switch result.source {
    case .network:      break            // fetched fresh
    case .cache:        break            // served offline, from the last good version
    case .revalidated:  break            // 304 — cache confirmed current
    }
}

Offline

SituationTypeDescription
Cached, no network.cacheThe flow renders from the cache. Analytics queue up on the device and drain when the connection returns.
Never fetched, no networkthrowsThere is nothing to render. Catch it and fall back to your own onboarding — see below.
Cached, flow deleted server-side.cacheKeeps rendering the cached copy. A flow key that stops resolving does not brick an installed app.

Errors

UpliftFunnelFlowView handles these itself and shows its error state with a Retry button; you only see them when you call UpliftFunnel.start directly.

swift
do {
    let start = try await UpliftFunnel.start("welcome")
    render(start.session)
} catch UpliftFunnelError.notConfigured(let method) {
    // configure() was never called, or not before this.
    assertionFailure("configure before \(method)")
} catch {
    // Network, 404 (no such flow key), 401 (bad key), decode.
    showOwnOnboarding()
}
ErrorDescription
UpliftFunnelError.notConfigured(method:)A method was called before configure. The message names the call, because this is almost always an ordering mistake at startup.
UpliftFunnelError.invalidEventName(_:)track() was given a name not matching ^[a-z][a-z0-9_:]*$.
URLError / decoding errorsNetwork failure, an unknown flow key (404), a rejected key (401), or a malformed response.

Pointing a debug build somewhere else

configure talks to the production API. A debug build can be pointed elsewhere through debugServerUrl, and a release build cannot— setting it there traps, so a staging URL left behind fails on your machine rather than sending real users' events somewhere nobody is reading.

swift
// Debug builds only. Set it BEFORE configure — configure reads it.
#if DEBUG
UpliftFunnel.debugServerUrl = "http://localhost:3000"
#endif
await UpliftFunnel.configure(apiKey: "fnl_pk_…")

Reaching an http:// host also needs an App Transport Security exception. NSAllowsLocalNetworking covers loopback without opening anything else.

Starting a session yourself

UpliftFunnel.start(_:forceRefresh:userVariables:) returns a FlowSessionStart — the session, where the JSON came from, the published version, and the experiment assignment. Render it with UpliftFunnelSessionView when you want the fetch to happen before the view exists. Rendering flows →