Offline Behavior

Durable on-disk event queue, background URLSession delivery, network-aware flush

The Convert iOS SDK is built so tracking events are never lost, even on a flaky or absent network. This is a mobile-SDK capability with no equivalent in the request-scoped JS or PHP SDKs — it relies on on-device durable storage, a background URLSession, and an app-lifecycle observer.

What happens when the network is down

flowchart TD
    A[Decision / conversion call] --> B[Event enqueued in memory]
    B --> C{Flush succeeds?}
    C -->|Yes| D[Delivered to Convert]
    C -->|No / offline| E[Persisted to on-disk queue<br>Application Support / convert-sdk-events.json]
    E --> F[Background URLSession<br>delivery task]
    F -->|App suspended / terminated| G[OS completes upload in background]
    G --> D
    H[App returns to foreground] -->|cold-start recovery| B
    I[Next launch] -->|start recovers persisted events| B
  1. Config fetch fails → the last successfully cached config is loaded from the on-disk cache (Application Support, coordinated file). If no cache exists, runExperience returns nil and runFeature returns a disabled Feature until the next successful fetch.
  2. Tracking events generated while offline → persisted to a durable on-disk queue (CoordinatedFileEventQueueStore — an atomic NSFileCoordinator-backed write in Application Support). The queue survives app restarts and process termination.
  3. App is suspended or terminated before the queue drains → the pending batch is uploaded by a background URLSession task (BackgroundSessionManager). The OS can complete the upload after your process is gone.
  4. Next launch → the SDK recovers any events persisted by the previous process (EventQueue.start() drains the on-disk queue into memory and arms the flush timer), so events are not lost across a termination. No integrator wiring is required for this zero-config durability guarantee.

Storage locations

WhatWhere
Offline event queueApplication Support / convert-sdk-events.json (app-private, coordinated file write)
Last-good config cacheApplication Support / convert-sdk-config.json (coordinated file write)
Visitor ID (system of record)Keychain (app-scoped, survives reinstall depending on Keychain sharing settings)
Visitor ID (fast-read mirror)UserDefaults (cleared on uninstall)

All paths are inside the app's private sandbox (Application Support / Keychain). They are discarded on app uninstall (UserDefaults, Application Support) or remain depending on Keychain configuration.

Foreground / background handoff (UIKit lifecycle)

The SDK's LifecycleObserver registers for UIApplication foreground/background notifications:

  • On background transition — the in-memory queue is flushed. Any events that could not be uploaded are persisted to the on-disk queue, and a background URLSession upload task is enqueued to ship them after the app suspends. UIApplication.beginBackgroundTask buys extra time for the flush attempt before suspension.
  • On foreground return / next launchEventQueue.start() recovers events from the on-disk queue into memory and arms the release timer, so they deliver on the next batch cycle.

LifecycleObserver is UIKit-gated (#if canImport(UIKit)) and built only on the production path; test paths inject a MockEventSink and skip the observer.

Optional background-completion forwarding

For prompt background-wake completion, forward your app delegate's callback to the SDK:

// AppDelegate.swift
func application(
    _ application: UIApplication,
    handleEventsForBackgroundURLSession identifier: String,
    completionHandler: @escaping () -> Void
) {
    sdk.handleEventsForBackgroundURLSession(
        identifier: identifier,
        completionHandler: completionHandler
    )
}

This is optional — the SDK reconciles persisted uploads on the next init regardless. An integrator that never wires this loses no events; they simply rely on the cold-start recovery path rather than the background-session acknowledgment path. The SDK rejects any identifier other than its own canonical background-session id (com.convertexperiments.sdk.background-upload), so a callback for an unrelated URLSession is never stored on its manager.

The bounded data-loss boundary

The durable queue has one documented failure mode: if the on-disk queue file is present but its bytes fail to decode (partial write from an unexpected kill, or external corruption), the SDK discards that file, logs a [WARN] line through the Logger port, reinitializes an empty queue, and continues. The events inside that corrupt batch are lost; nothing else is. A missing queue file — the normal state on first launch or right after a successful flush cleared it — is not corruption: it is treated as an empty queue silently, with no warning.

What you do not need to do

  • No manual flush — the SDK batches and flushes on its own timer (eventsReleaseIntervalMs).
  • No manual config refresh — the ConfigRefreshScheduler (started after the first config lands) handles re-fetching while the app is in the foreground.
  • No background-task registration — the SDK registers its lifecycle observer synchronously in ConvertSwiftSDK.init; the background URLSession is also wired at init time.

What is not persistent

  • Events inside a corrupt queue batch (see bounded data-loss boundary above).
  • Events that were never enqueued because networkTracking was false or the runtime setTrackingEnabled(false) gate was closed — suppressed events are never buffered.
  • The visitor ID in UserDefaults is cleared on app uninstall (the Keychain copy may survive depending on the app's Keychain sharing settings).

No platform permission required

Unlike Android's ACCESS_NETWORK_STATE, iOS does not require an explicit entitlement for the connectivity observation the SDK performs. The background URLSession requires the Background Modes → Background fetch capability in your Info.plist if you want the OS to wake your app proactively; without it, uploads still complete when the OS schedules the background session opportunistically.

Inspecting the queue during development

The on-disk queue file is a JSON array of tracking events at:

<app sandbox>/Library/Application Support/convert-sdk-events.json

Access it via Xcode's Devices and Simulators → Download Container or directly in the iOS Simulator file system at ~/Library/Developer/CoreSimulator/Devices/<device-id>/data/Containers/Data/Application/<app-id>/Library/Application Support/. See Troubleshooting.

Related pages