Supported on: iOS SDK 1.0.61+
The experiments module assigns users to variants using a deterministic hash. No server call is needed — assignment is instant, works offline, and the same user always gets the same variant.
Two ways to run an experiment
- Servable surface experiments — A/B test a whole paywall, onboarding flow, in-app message, or survey with no branching code. You design the variants in the Console and the SDK serves the right one automatically. Best for testing the content or design of a managed surface.
- Code-level experiments — read the assigned variant yourself with
getVariant or getExperimentConfig and branch in your own code. Best for feature flags, custom UI, or logic that isn’t a managed surface.
Servable Surface Experiments
Supported on: iOS SDK 1.0.65+
Run an A/B test on a live paywall, onboarding flow, in-app message, or survey without changing your app code. In the Console, create an experiment on the surface: the control variant points at the live entity you already present, and the treatment variant carries an alternate configuration.
When your app presents that surface the way it normally does, the SDK does the rest:
// Present the live paywall exactly as before -- no experiment code here.
AppDNA.presentPaywall(
id: "premium_paywall",
from: viewController,
delegate: self
)
The SDK deterministically buckets the user and:
- Treatment cohort → renders the treatment’s configuration in place of the live entity.
- Control cohort (and users on older SDK versions) → renders the live entity unchanged.
Exposure is tracked automatically the first time the surface is presented, so conversions attribute to the experiment with no extra calls. The treatment configuration lives only inside the experiment payload, so a user who isn’t bucketed into the treatment — or who is on an SDK version that predates this feature — can never receive it.
This works for every managed surface: paywalls, onboarding flows, in-app messages, and surveys. You only reach for the code-level API below when you’re experimenting on something that isn’t a managed surface.
Get Variant
let variant = AppDNA.experiments.getVariant("paywall-test")
switch variant {
case "control":
showStandardPaywall()
case "variant_a":
showNewPaywall()
default:
showStandardPaywall() // Always handle the default case
}
Returns the variant string ("control", "variant_a", etc.) or nil if the experiment is not found, not running, or the user is not in the target audience.
Always handle the nil / default case. Experiments can be archived or stopped at any time from the Console.
Check a Specific Variant
let isInVariantB = AppDNA.isInVariant(experimentId: "paywall-test", variantId: "b")
Returns true only if the user is currently bucketed into the named variant. Useful as a guard before rendering variant-specific UI.
Get a Per-Variant Config Value
When an experiment variant carries a config payload, read individual keys with:
let headline = AppDNA.getExperimentConfig(experimentId: "paywall-test", key: "headline") as? String ?? "Get Premium"
Returns Any? (string, number, boolean, array, or dictionary depending on what the Console stored).
Module Access
let experiments = AppDNA.experiments
Module Methods
| Method | Signature | Description |
|---|
getVariant | getVariant(_ experimentId: String) -> String? | Get the assigned variant |
getExposures | getExposures() -> [(experimentId: String, variant: String)] | Get all experiment exposures for the current session |
The top-level AppDNA object also exposes AppDNA.getExperimentVariant(experimentId:), AppDNA.isInVariant(experimentId:variantId:), and AppDNA.getExperimentConfig(experimentId:key:) as convenience methods that delegate to this module.
Exposure Tracking
The SDK tracks an experiment_exposure event once per session the first time getVariant() is called for a given experiment. Calling it multiple times in the same session does not create duplicate events.
To inspect which experiments the user has been exposed to in the current session:
// Get all experiment exposures for the current session
let exposures = AppDNA.experiments.getExposures()
// Returns [(experimentId: String, variant: String)]
Experiment Lifecycle
| Status | getVariant() returns |
|---|
| Draft | nil — not visible to SDKs |
| Running | Assigned variant string |
| Completed | Winning variant for all users |
| Archived | nil — removed from config |
Full Example
import AppDNASDK
class PaywallExperiment {
private weak var presenter: UIViewController?
init(presenter: UIViewController) {
self.presenter = presenter
}
func showPaywall() {
guard let vc = presenter else { return }
let variant = AppDNA.experiments.getVariant("paywall_redesign")
let paywallId: String
switch variant {
case "new_design":
paywallId = "paywall_v2"
default:
paywallId = "paywall_v1"
}
AppDNA.paywall.present(
paywallId,
from: vc,
context: PaywallContext(
placement: "settings",
experiment: "paywall_redesign",
variant: variant ?? "control"
)
)
}
}
Experiments are created and managed in the Console under Experiments. The SDK uses MurmurHash3 for deterministic assignment — the same user always gets the same variant across sessions, platforms, and even offline.