-
Notifications
You must be signed in to change notification settings - Fork 0
persistent datastore
The iOS SDK persists all visitor state automatically using platform-native storage. Unlike the JavaScript and PHP SDKs, there is no DataStore interface to implement — the persistence layer is built in and requires no configuration.
| Data | Storage | Implementation |
|---|---|---|
| Visitor ID | Keychain (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) + UserDefaults mirror |
KeychainSecureStore + UserDefaultsKeyValueStore
|
| Sticky bucketing decisions, goal deduplication, segments | App-private file (ApplicationSupportFileStore via CoordinatedFileStore) |
Decision store under Application Support/com.convertexperiments.sdk/
|
| Config cache | App-private file (atomic write via NSFileCoordinator) |
CoordinatedFileStore at Application Support/com.convertexperiments.sdk/config-<key>.json
|
| Pending tracking event queue | App-private file (atomic write via NSFileCoordinator) |
CoordinatedFileEventQueueStore at Application Support/com.convertexperiments.sdk/event-queue.json
|
The visitor ID is the foundation of deterministic bucketing. The SDK resolves it at context creation using the following precedence:
-
Explicit caller-supplied ID — if you pass
visitorIdtocreateContext(visitorId:), it is used verbatim with no store access at all. -
Keychain — a
kSecClassGenericPassworditem under the servicecom.convert.sdk, readable after first unlock post-reboot. TheThisDeviceOnlyaccess constraint keeps the value off iCloud Keychain so it never syncs to other devices. -
UserDefaultsmirror — a fallback for environments where the Keychain is unavailable (e.g. CI runners without the entitlement). When the mirror contains a value but the Keychain does not, the mirror value is returned AND backfilled into the Keychain so both stores converge. -
Fresh UUID — when neither store holds a value,
UUID().uuidStringis generated, written to both stores, and returned.
This means the same visitor is consistently identified across app launches, app updates, and even app reinstalls (Keychain entries survive reinstall on iOS when the ThisDeviceOnly constraint is used, because the device's Secure Enclave key remains stable).
Note: The SDK does not use
IDFAorIDFVfor visitor identification. The Keychain UUID is fully under your control and requires no privacy-tracking permission.
// Auto-resolved persistent UUID (no IDFA/IDFV):
let ctx = sdk.createContext()
print(ctx.visitorId) // e.g. "550E8400-E29B-41D4-A716-446655440000"
// Explicit ID — returned verbatim, Keychain not consulted:
let ctx = sdk.createContext(visitorId: "authenticated-user-id")Once a visitor is bucketed into a variation, that assignment is written to an on-disk file under Application Support. On subsequent runs, the stored decision is read back before any bucketing calculation, so the visitor always sees the same variation even if experience traffic allocation changes.
This happens automatically — no DataStore or cache configuration is needed.
The SDK caches the fetched project configuration to disk (config-<sdkKey>.json in Application Support). On startup, the cached config is loaded first, making the SDK ready immediately even before a live fetch completes. The cache is replaced atomically (via NSFileCoordinator + .atomic write option) so a partial write is never observable.
Pending tracking events are persisted to event-queue.json in Application Support before the app is backgrounded or terminated. On the next launch, any previously queued events are recovered and delivered. File operations use NSFileCoordinator for forward-compatible cross-process coordination (for future App Group support).
If the event queue file is found corrupted on load, the SDK discards it with a [WARN] log line and starts with an empty queue — it never crashes on bad bytes.
The Android and iOS SDKs handle persistence automatically. Unlike the JavaScript and PHP SDKs, there is no DataStore protocol or interface for you to implement. Persistence is a built-in, zero-configuration capability.
If you need to observe visitor state for your own analytics or debugging, subscribe to the system events the SDK fires:
let token = await sdk.on(.bucketing) { payload in
// fired when a visitor is bucketed
}
let token = await sdk.on(.conversion) { payload in
// fired when a conversion is tracked
}See the Event System guide for the full list of system events.
The SDK maintains visitor ID continuity automatically via the Keychain. For authenticated users, you can supply your own stable identifier to ensure cross-device and cross-session consistency:
// Logged-in user: pass your own stable ID
let ctx = sdk.createContext(visitorId: currentUser.id)
// Anonymous user: let the SDK manage the persistent UUID
let ctx = sdk.createContext()For anonymous-to-authenticated migration (a user who browsed anonymously, then logged in), you manage the transition by creating a new context with the authenticated ID. The two contexts are tracked separately; combining their histories is a product decision outside the SDK's scope.
All SDK files are stored in the app's private Application Support directory — never in the shared Documents directory, the temp directory, or iCloud Drive. The com.convertexperiments.sdk namespace prevents collisions with other app data.
| File | Path |
|---|---|
| Config cache | {AppSupport}/com.convertexperiments.sdk/config-{sanitizedKey}.json |
| Event queue | {AppSupport}/com.convertexperiments.sdk/event-queue.json |
| Decision store |
{AppSupport}/com.convertexperiments.sdk/ (directory) |
Copyrights © 2026 All Rights Reserved by Convert Insights, Inc.
Getting Started
iOS SDK
- Quickstart
- Installation
- Initialization
- Configuration
- Return Types & Models
- Code Examples
- Offline Behavior
- Tracking Control
- App Privacy & Data Collection
- Objective-C Interop
Core Concepts
- Experiences & Variations
- Feature Flags
- Bucketing Algorithm
- Rule Evaluation
- Segments
- Data Management
- Event System
- API Communication
How-To Guides
- Running Experiences
- Running Features
- Tracking Conversions
- Visitor Context
- Persistent Storage
- Troubleshooting
Contributing