Code Examples
Complete async/await examples (with completion-handler equivalents) for every SDK method
Complete, copy-pasteable examples for every public method of the Convert iOS SDK. All examples use Swift's async/await; completion-handler equivalents are shown where the SDK provides them. All examples assume a single ConvertSwiftSDK instance created at app startup (see Initialization).
Create the SDK
import ConvertSwiftSDK
// Key path: fetches config from the CDN.
let sdk = ConvertSwiftSDK(configuration: ConvertConfiguration(sdkKey: "YOUR_SDK_KEY"))
// Key path with options.
let sdk = ConvertSwiftSDK(configuration: ConvertConfiguration(
sdkKey: "YOUR_SDK_KEY",
environment: "prod",
logLevel: .info
))
// Direct-data path: pre-fetched config bytes bypass the CDN.
let sdk = ConvertSwiftSDK(configData: preloadedData)Wait for readiness
async/await
try await sdk.ready()
// Decisions are available from here on.Completion handler (delivered on MainActor)
sdk.ready { result in
// Called on the MainActor.
switch result {
case .success:
// SDK is ready.
break
case .failure(let error):
print(error.errorDescription ?? "unknown")
}
}ready() resolves on a successful or degraded load; it throws ConvertError only on an unrecoverable configuration error (empty SDK key, or empty/invalid direct-data). A transient network failure does NOT throw — the SDK resolves degraded.
Create a visitor context
// Anonymous visitor — auto-generated UUID, persisted in Keychain.
let context = sdk.createContext()
// Explicit visitor ID.
let context = sdk.createContext(visitorId: "visitor-42")
// Explicit visitor ID with initial attributes.
let context = sdk.createContext(
visitorId: "visitor-42",
attributes: ["plan": "premium", "accountAgeDays": 120]
)createContext is synchronous and never blocks; it can be called before ready() resolves. Attribute values must be scalars (String, Int, Double, Bool) — nested dictionaries, arrays, and NSNull are dropped at call time and logged at debug.
Run a single experience
if let variation = await context.runExperience("homepage-redesign") {
switch variation.key {
case "control": renderControl()
case "treatment": renderTreatment()
default: renderControl()
}
} else {
renderControl() // SDK not ready, visitor not bucketed, or experience absent
}Run all experiences for the visitor
let variations = await context.runExperiences()
for variation in variations {
applyVariation(variation)
}runExperiences() returns only the experiences the visitor is bucketed into; experiences that fail audience or location rules are excluded. Returns [] when the SDK is not yet ready.
Completion handler
context.runExperiences { variations in
// Called on the MainActor.
for variation in variations {
applyVariation(variation)
}
}Suppress the tracking event for a run call
// Evaluate but do not report to the network (e.g. consent-denied flow).
let variation = await context.runExperience("homepage-redesign", enableTracking: false)
let variations = await context.runExperiences(enableTracking: false)Bucketing, sticky persistence, audience rules, and the internal bucketing event still fire — only the outbound network event is skipped. See Tracking Control.
Evaluate a feature flag
let feature = await context.runFeature("checkout-v2")
if feature.status == .enabled {
enableCheckoutV2()
}runFeature is non-optional by contract — it returns Feature.disabled(key:) (not nil) when the SDK is not ready or the feature is absent. It never throws. Unlike the experience API, runFeature has no enableTracking parameter (Android parity).
Read feature variables
let feature = await context.runFeature("checkout-v2")
// Typed accessor — returns nil on key miss or type mismatch.
let color = feature.variable("ctaColor", as: String.self) ?? "#0066ff"
let limit = feature.variable("maxItems", as: Int.self) ?? 20
let price = feature.variable("price", as: Double.self) ?? 9.99
let experimental = feature.variable("experimental", as: Bool.self) ?? falseFor JSON variables, read as Data and decode at the call site:
if let raw = feature.variable("config", as: Data.self) {
let decoded = try? JSONDecoder().decode(MyConfig.self, from: raw)
}Run all features
let features = await context.runFeatures()
for feature in features where feature.status == .enabled {
print("enabled:", feature.key)
}Returns one Feature per entry in the config, in config order. Returns [] when the SDK is not yet ready. No enableTracking parameter (Android parity).
Track a conversion
// Bare goal — no transactional data.
await context.trackConversion("signup-completed")
// With goal data.
let data: GoalData = [
.amount: .double(49.99),
.productsCount: .double(3),
.transactionId: .string("tx-42"),
]
await context.trackConversion("purchase-completed", goalData: data)GoalData is [GoalDataKey: GoalDataValue] — use a dictionary literal. Never use a constructor form. trackConversion is async but never throws (AOD-6).
Force a repeat transaction (bypass dedup)
By default the same goal is recorded only once per visitor. For renewals or repeat purchases, bypass dedup with forceMultipleTransactions:
await context.trackConversion(
"purchase-completed",
goalData: [.amount: .double(49.99)],
forceMultipleTransactions: true
)On the repeat call the conversion event is skipped (already recorded), the transaction payload is sent, and the conversion event fires. See Tracking Conversions.
Set default segments (targeting input + event payload)
// Six recognised string keys; unknown keys are WARN-logged and ignored.
await context.setDefaultSegments([
"country": "US",
"visitorType": "returning",
"browser": "Chrome",
])Segment fields flow into outbound tracking events and feed audience rules. Each call merges over the visitor's existing segments (overlay semantics — existing recognised keys are overwritten, unset keys are untouched). Fires SystemEvent.segments once.
Set custom segment identifiers
await context.setCustomSegments(["vip", "beta-tester"])Appends identifiers to the visitor's customSegments list. Backend owns dedup (matching JS SDK behavior). Fires SystemEvent.segments once.
Subscribe to and unsubscribe from events
let token = await sdk.on(.conversion) { payload in
// Called each time a conversion is tracked.
print("conversion fired:", payload)
}
// Cancel the subscription.
await sdk.off(token)Pass any SystemEvent case to on(_:callback:). The callback is @Sendable and may be called from any concurrency context — dispatch to MainActor if you need to update the UI:
let token = await sdk.on(.ready) { _ in
Task { @MainActor in updateUI() }
}Toggle tracking at runtime
// Suppress — e.g. on consent withdrawal.
await sdk.setTrackingEnabled(false)
// Re-enable — e.g. on consent re-grant.
await sdk.setTrackingEnabled(true)
// Read the current state.
let isOn = await sdk.isTrackingEnabled()Completion-handler overloads (delivered on MainActor):
sdk.setTrackingEnabled(false) { /* gate is closed */ }
sdk.isTrackingEnabled { isOn in
print("tracking:", isOn)
}When tracking is disabled, no events reach the network, but bucketing and sticky assignment are unaffected. See Tracking Control for the full three-layer consent model.
Background session wiring (optional)
Forward the AppDelegate background-session callback to the SDK for prompt OS release. This is optional — the SDK recovers persisted events on the next launch regardless.
// In your UIApplicationDelegate:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
ConvertSwiftSDK.shared?.handleEventsForBackgroundURLSession(
identifier: identifier,
completionHandler: completionHandler
)
}The SDK rejects any identifier it does not own, so passing unrelated session identifiers is safe.
Error handling
do {
try await sdk.ready()
} catch ConvertError.invalidSdkKey(let detail) {
print("Bad SDK key:", detail)
} catch ConvertError.invalidConfiguration(let detail) {
print("Bad config:", detail)
} catch {
print("Unexpected:", error)
}Only ready() throws. All decisioning and tracking methods degrade silently (return nil, [], or a disabled Feature; log at warn).
Next steps
- Return Types & Models — what each call returns
- Configuration — all
ConvertConfigurationparameters - Offline Behavior — what happens to tracked events offline