Return Types & Models
Variation/Feature models, FeatureStatus, GoalData keys, and the never-throws / nil-on-miss contract
This page documents the public Swift types the SDK returns and the enums you pass in. All model types live in ConvertSwiftSDKCore; the event constants live in ConvertSwiftSDKCore/Event. The SDK never throws from its public decision methods — absent or not-ready data surfaces as nil, an empty array, or a disabled Feature.
Variation
VariationReturned by ConvertContext.runExperience(...) (optional) and runExperiences(...) (as an array). A Swift struct conforming to Codable, Sendable, and Identifiable:
public struct Variation: Codable, Sendable, Identifiable {
public let id: String
public let key: String
public let experienceId: String
public let experienceKey: String
}key— the merchant-defined variation key from the Convert dashboard; this is what you branch on.id— the variation identifier (typedString, notInt, for wire forward-compatibility).experienceId/experienceKey— the owning experience's identifier and key.
Unlike the Android model, all fields are non-optional — the iOS Variation is only returned when a complete record can be constructed; a missing or not-bucketed visitor returns nil from runExperience.
if let variation = await context.runExperience("homepage-redesign") {
switch variation.key {
case "control": renderControl()
case "treatment": renderTreatment()
default: renderControl()
}
} else {
renderControl() // not ready / not bucketed / experience absent
}Feature and FeatureStatus
Feature and FeatureStatusReturned by ConvertContext.runFeature(...) (non-optional) and runFeatures(...) (as an array). A Swift struct conforming to Codable, Sendable, and Equatable:
public struct Feature: Codable, Sendable, Equatable {
public let id: String
public let key: String
public let status: FeatureStatus
public let variables: [String: FeatureVariable]
}
public enum FeatureStatus: String, Codable, Sendable, Equatable {
case enabled
case disabled
}status == .enabledmeans the feature is on for this visitor andvariablesholds the bucketed values.- When
status == .disabled,variablesis an empty dictionary and variable accessors returnnil. runFeatureis non-optional by contract: a missing snapshot or miss returnsFeature.disabled(key:)rather thannil— never throws.- A feature also resolves
.disabledwhen the call'sexperienceKeysallow-list excluded every experience that carries it..disabledtherefore means "off for this visitor on this call", not "absent from the config". runFeaturesreturns one entry per feature the config declares, in config order — never in the orderexperienceKeyswas passed in, and never with an excluded feature omitted. Narrowing changes statuses, never the shape of the array.
let feature = await context.runFeature("checkout-v2")
if feature.status == .enabled {
enableCheckoutV2()
}Feature.disabled(key:)
Feature.disabled(key:)The canonical "feature off" factory:
public static func disabled(key: String) -> FeatureReturns a Feature with status == .disabled and an empty variables dictionary. Used internally by the degraded path; you can use it in tests or as a safe default.
Feature variable accessors
When a feature is .enabled, variables holds FeatureVariable values — a Swift enum with five cases mirroring the five JS variable types:
public enum FeatureVariable: Codable, Sendable, Equatable {
case boolean(Bool)
case integer(Int)
case float(Double)
case string(String)
case json(Data) // raw JSON bytes; decode at the call site
}Use the non-throwing typed accessor variable(_:as:) to read a variable by name:
public func variable<T>(_ key: String, as type: T.Type) -> T?let feature = await context.runFeature("checkout-v2")
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 flag = feature.variable("experimental", as: Bool.self) ?? falseFor .json variables, pass Data.self; then decode the bytes at the call site using JSONDecoder or another decoder:
if let raw = feature.variable("config", as: Data.self) {
let decoded = try JSONDecoder().decode(MyConfig.self, from: raw)
}variable(_:as:) returns nil when the key is unknown or when the stored case's associated value is not of the requested type T. It never force-unwraps.
GoalData, GoalDataKey, and GoalDataValue
GoalData, GoalDataKey, and GoalDataValueGoalData is a type alias for a dictionary:
public typealias GoalData = [GoalDataKey: GoalDataValue]Passed to ConvertContext.trackConversion(...) to attach transactional data to a conversion event.
GoalDataKey is an eight-case String-backed enum:
| Case | Wire name (rawValue) | Typical value |
|---|---|---|
.amount | amount | number |
.productsCount | productsCount | integer |
.transactionId | transactionId | string |
.customDimension1 | customDimension1 | string / number |
.customDimension2 | customDimension2 | string / number |
.customDimension3 | customDimension3 | string / number |
.customDimension4 | customDimension4 | string / number |
.customDimension5 | customDimension5 | string / number |
GoalDataValue is a three-case enum encoding the possible metric value types:
public enum GoalDataValue: Codable, Sendable {
case double(Double)
case string(String)
case strings([String])
}Build a GoalData dictionary literal — no constructor needed:
let data: GoalData = [
.amount: .double(49.99),
.productsCount: .double(3),
.transactionId: .string("tx-42"),
]
await context.trackConversion("purchase-completed", goalData: data)ConvertError
ConvertErrorThe single error type thrown by the SDK (LocalizedError, Sendable):
public enum ConvertError: LocalizedError, Sendable {
case invalidConfiguration(String)
case invalidSdkKey(String)
}Only ready() and its completion overload throw ConvertError. All decisioning methods (runExperience, runFeature, trackConversion, etc.) never throw — degraded inputs surface as nil, an empty array, or a disabled Feature.
| Case | When thrown |
|---|---|
.invalidConfiguration(String) | Invalid config struct fields, or empty/invalid direct-data payload |
.invalidSdkKey(String) | Structurally invalid or empty SDK key |
do {
try await sdk.ready()
} catch let error as ConvertError {
print(error.errorDescription ?? "unknown")
}LogLevel
LogLevelpublic enum LogLevel: String, CaseIterable, Comparable, Sendable {
case trace
case debug
case info
case warn // default production level
case error
case silent // fully mutes output
}Comparable compares by severity: trace < debug < info < warn < error < silent. A logger configured at a given level emits messages at that level and above. The default is .warn. Set via ConvertConfiguration(sdkKey:logLevel:). See Configuration.
SystemEvent
SystemEventWell-known event names you can subscribe to with sdk.on(_:callback:). Each is a case on the SystemEvent enum with a JS-parity raw String value:
| Case | Raw value (wire string) |
|---|---|
.ready | ready |
.configUpdated | config.updated |
.apiQueueReleased | api.queue.released |
.bucketing | bucketing |
.conversion | conversion |
.segments | segments |
.locationActivated | location.activated |
.locationDeactivated | location.deactivated |
.audiences | audiences |
.dataStoreQueueReleased | datastore.queue.released |
The set is frozen — the 10 members and their raw values match the JS SDK exactly and will not change.
EventListenerToken
EventListenerTokenpublic struct EventListenerToken: Sendable, Hashable, EquatableAn opaque subscription handle returned by sdk.on(_:callback:). Pass it to sdk.off(_:) to cancel. Tokens are created only by the SDK; callers cannot mint their own.
let token = await sdk.on(.conversion) { payload in
// handle conversion payload
}
await sdk.off(token)Segments
Segmentspublic struct Segments: Codable, Sendable, Equatable {
public var country: String?
public var browser: String?
public var devices: String?
public var source: String?
public var campaign: String?
public var visitorType: String?
public var customSegments: [String]?
}Set via ConvertContext.setDefaultSegments(_:) (string-keyed fields) and setCustomSegments(_:) (the customSegments array). Both methods accept async; completion-handler overloads are not provided for segmentation methods. See Code Examples.
ConvertValue
ConvertValuepublic enum ConvertValue: Sendable, Equatable {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
}The closed set of scalar attribute types the SDK accepts in createContext(attributes:). Unsupported values (nested dictionaries, arrays, NSNull, custom objects) are dropped at createContext call time and logged at debug. Accessible indirectly through ConvertContext.attributes as [String: Any].
Next steps
- Code Examples — these types in complete flows
- Configuration —
ConvertConfigurationandLogLevelin depth
Updated 10 days ago