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.
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