On this page

Experiment iOS SDK

Official documentation for Amplitude Experiment's Client-side iOS SDK implementation.

Unified SDK

Install the Unified SDK for Swift to access the Experiment SDK along with other Amplitude products (Analytics, Session Replay). The Unified SDK provides a single entry point for all Amplitude features and simplifies the integration process by handling the initialization and configuration of all components.

Install

Import statement

CocoaPods and Swift Package Manager/Carthage have different import statements.

  • CocoaPods: import AmplitudeExperiment
  • SPM/Carthage: import Experiment
ruby
pod 'AmplitudeExperiment', '~> <VERSION>'

Quick start

The right way to initialize the Experiment SDK depends on whether you use an Amplitude SDK for analytics or a third party (for example, Segment).

  1. Initialize the experiment client
  2. Fetch variants
  3. Access a flag's variant
swift
// (1) Initialize the experiment client with Amplitude Analytics
let experiment = Experiment.initializeWithAmplitudeAnalytics(
    apiKey: "DEPLOYMENT_KEY",
    config: ExperimentConfigBuilder().build()
)

// (2) Fetch variants
experiment.fetch(user: nil) { error in

    // (3) Lookup a flag's variant
    let variant = experiment.variant("FLAG_KEY")
    if variant.value == "on" {
        // Flag is on
    } else {
        // Flag is off
    }
}

Initialize

Initialize the SDK client in your application on startup. The deployment key argument you pass to the apiKey parameter must live within the same project that you are sending analytics events to.

swift
func initializeWithAmplitudeAnalytics(
    apiKey: String,
    config: ExperimentConfig
) -> ExperimentClient
  • apiKey
    • Requirement: required
    • Description: The deployment key that authorizes fetch requests and determines which flags to evaluate for the user.
  • config
    • Requirement: optional
    • Description: The client configuration used to customize SDK client behavior.

The initializer returns a singleton instance, so subsequent initializations for the same instance name always return the initial instance. To create multiple instances, use the instanceName configuration.

swift
let experiment = Experiment.initializeWithAmplitudeAnalytics(
    apiKey: "DEPLOYMENT_KEY",
    config: ExperimentConfigBuilder()
    .instanceName("myCustomInstance")  // case-sensitive name matching your Analytics instance
    .build()
)

Configuration

Configure the SDK client during initialization.

Eu data center

If you're using Amplitude's EU data center, configure the serverZone option on initialization to .EU.

Integrations

If you use either Amplitude or Segment Analytics SDKs to track events into Amplitude, you'll want to set up an integration on initialization. Integrations automatically implement provider interfaces to enable a more streamlined developer experience by making it easier to manage user identity and track exposures events.

Fetch

Fetches variants for a user and stores the results in the client for fast access. The function remote evaluates the user for flags associated with the deployment used to initialize the SDK client.

swift
func fetch(user: ExperimentUser?, options: FetchOptions?, completion: ((ExperimentClient, Error?) -> Void)?)
ParameterRequirementDescription
useroptionalExplicit user information to pass with the request to evaluate. The SDK merges this user information with user information provided from integrations through the user provider, preferring properties passed explicitly to fetch() over provided properties.
optionsoptionalExplicit flag keys to fetch.
completionoptionalCallback when the variant fetch (success or failure). If the fetch request fails, the error is returned in the second parameter of this callback.

Amplitude Experiment recommends calling fetch() during application start up so that the user gets the most up-to-date variants for the application session. Furthermore, you'll need to wait for the fetch request to return a result before rendering the user experience to avoid the interface "flickering".

swift
let user = ExperimentUserBuilder()
    .userId("user@company.com")
    .userProperty("premium", value: true)
    .build()
experiment.fetch(user: user) { experiment, error in
    // Do something...
}

If you're using an integration or a custom user provider then you can fetch without inputting the user.

swift
experiment.fetch(user: nil, completion: nil)

Fetch when user identity changes

If you want the most up-to-date variants for the user, it's recommended that you call fetch() whenever the user state changes in a meaningful way. For example, if the user logs in and receives a user ID, or has a user property set which may affect flag or experiment targeting rules.

In the case of user properties, Amplitude recommends passing new user properties explicitly to fetch() instead of relying on user enrichment before remote evaluation. Remote user-property sync through a separate system has no timing guarantees for fetch(), which can create a race condition.

If fetch() times out (default 10 seconds) or fails for any reason, the SDK client returns and retries in the background with back-off. You may configure the timeout or disable retries in the configuration options during SDK client initialization.

Start

Fetch vs start

Use start if you're using client-side local evaluation. If you're only using remote evaluation, call fetch instead of start.

Start the SDK by getting flag configurations from the server and fetching remote evaluation variants for the user. The SDK is ready when the completion callback runs.

js
func start(_ user: ExperimentUser? = nil, completion: ((Error?) -> Void)? = nil)
ParameterRequirementDescription
useroptionalExplicit user information to pass with the request to fetch variants. The SDK merges this user information with user information provided from integrations through the user provider, preferring properties passed explicitly to fetch() over provided properties. Also sets the user in the SDK for reuse.
completionoptionalThe completion block, called when the SDK has finished starting. If fetch is called on start, the completion block is called after the fetch response is received.

Call start() when your application is initializing, after user information is available to evaluate or fetch variants. The provided completion block is called after loading local evaluation flag configurations and fetching remote evaluation variants.

Configure the behavior of start() by setting fetchOnStart in the SDK configuration on initialization to improve performance based on the needs of your application.

  • If your application never relies on remote evaluation, set fetchOnStart to false to avoid increased startup latency caused by remote evaluation.
  • If your application relies on remote evaluation, but not right at startup, you may set fetchOnStart to false and call fetch() separately.
swift
experiment.start() { error in
    // SDK Started
}

Variant

Access a variant for a flag or experiment from the SDK client's local store.

Automatic exposure tracking

When you use an integration or set a custom exposure tracking provider, variant() automatically tracks an exposure event through the tracking provider. To disable this functionality, configure automaticExposureTracking to be false, and track exposures manually using exposure().

swift
func variant(_ key: String, fallback: Variant? = nil) -> Variant
ParameterRequirementDescription
keyrequiredThe flag key to identify the flag or experiment to access the variant for.
fallbackoptionalThe value to return if no variant was found for the given flagKey.

When determining which variant a user has been bucketed into, you'll want to compare the variant value to a well-known string.

swift
let variant = experiment.variant("<FLAG_KEY>")
if variant.value == "on" {
    // Flag is on
} else {
    // Flag is off
}

Access the variant's payload

A variant may also be configured with a dynamic payload of arbitrary data. Access the payload field from the variant object after checking the variant's value.

The payload in iOS is of type Any?, so cast the payload to the expected type to retrieve the value. For example, if the payload is {"key":"value"}:

swift
let variant = client.variant("<FLAG_KEY>")
if variant.value == "on" {
    if let payload = variant.payload as? [String:String] {
        let value = payload["key"]
    }
}

A null variant value means that the user hasn't been bucketed into a variant. You may use the built in fallback parameter to provide a variant to return if the store doesn't contain a variant for the given flag key.

swift
let variant = experiment.variant("<FLAG_KEY>", fallback: Variant("control"))
if variant.value == "control" {
    // Control
} else if variant.value == "treatment" {
    // Treatment
}

All

Access all variants stored by the SDK client.

swift
func all() -> [String:Variant]

Clear

Clear all variants in the cache and storage.

swift
func clear()

You can call clear after user logout to clear the variants in cache and storage.

swift
experiment.clear()

Exposure

Manually track an exposure event for the current variant of the given flag key through configured integration or custom exposure tracking provider. Generally used in conjunction with setting the automaticExposureTracking configuration optional to false.

swift
func exposure(key: String)
ParameterRequirementDescription
keyrequiredThe flag key to identify the flag or experiment variant to track an exposure event for.
swift
let variant = experiment.variant("<FLAG_KEY>")

// Do other things...

experiment.exposure("<FLAG_KEY>")
if variant.value == "control" {
    // Control
} else if variant.value == "treatment" {
    // Treatment
}

Providers

Integrations

If you use Amplitude or Segment analytics SDKs along side the Experiment Client SDK, Amplitude recommends you use an integration instead of implementing custom providers.

Provider implementations enable a more streamlined developer experience by making it easier to manage user identity and track exposures events.

User provider

The SDK client uses the user provider to access the most up-to-date user information only when needed (for example, when fetch() is called). The user provider is optional, but helps if you have a user information store already set up in your application. With a user provider, you don't need to manage two separate user info stores in parallel. Separate stores can create divergent user state if the application user store is updated and experiment isn't (or vice versa).

swift
protocol ExperimentUserProvider {
    func getUser() -> ExperimentUser
}

To use your custom user provider, set the userProvider configuration option with an instance of your custom implementation on SDK initialization.

swift
let config = ExperimentConfigBuilder()
    .userProvider(CustomUserProvider())
    .build()
let experiment = Experiment.initialize(apiKey: "<DEPLOYMENT_KEY>", config: config)

Exposure tracking provider

Amplitude highly recommends implementing an exposure tracking provider. Exposure tracking increases the accuracy and reliability of experiment results and improves visibility into which flags and experiments a user is exposed to.

swift
protocol ExposureTrackingProvider {
    func track(exposure: Exposure)
}

The implementation of track() should track an event of type $exposure (a.k.a name) with two event properties, flag_key and variant, corresponding to the two fields on the Exposure object argument. Finally, the event tracked must eventually end up in Amplitude Analytics for the same project that the [deployment] used to initialize the SDK client lives within, and for the same user that variants were fetched for.

To use your custom user provider, set the exposureTrackingProvider configuration option with an instance of your custom implementation on SDK initialization.

swift
ExperimentConfig config = ExperimentConfigBuilder()
    .exposureTrackingProvider(CustomExposureTrackingProvider(analytics))
    .build()
let experiment = Experiment.initialize(apiKey: "<DEPLOYMENT_KEY>", config: config)

Bootstrapping

You may want to bootstrap the experiment client with an initial set of flags or variants when variants come from an external source (for example, not from calling fetch() on the SDK client). Use cases include local evaluation or integration testing on specific variants.

Bootstrapping variants

To bootstrap the client with a predefined set of variants, set the flags and variants in the initialVariants configuration object, then set the source to Source.InitialVariants so that the SDK client prefers the bootstrapped variants over any previously fetched & stored variants for the same flags.

swift
let config = ExperimentConfigBuilder()
    .initialVariants(["<FLAG_KEY>": Variant("<VARIANT>")])
    .source(Source.InitialVariants)
    .build()
let experiment = Experiment.initialize(apiKey: "<DEPLOYMENT_KEY>", config: config)

Bootstrapping flag configurations

You may choose to bootstrap the SDK with an initial set of local evaluation flag configurations using the initialFlags configuration. The SDK evaluates these flag configurations when variant is called, unless an updated flag config or variant is loaded with start or fetch.

To download initial flags, use the evaluation flags API

swift
let config = ExperimentConfigBuilder()
    .initialFlags("<FLAGS_JSON>")
    .build()
let experiment = Experiment.initialize(apiKey: "<DEPLOYMENT_KEY>", config: config)

Custom logging

Control log verbosity with the logLevel configuration, or implement the CoreLogger protocol to integrate your own logging solution.

Log levels

  • .off - No logging
  • .error - Errors only
  • .warn - Errors and warnings (default)
  • .log - Errors, warnings, and logs
  • .debug - All messages including debug
swift
// Only log errors
let config = ExperimentConfigBuilder()
    .logLevel(.error)
    .build()
let experiment = Experiment.initialize(apiKey: "<DEPLOYMENT_KEY>", config: config)

Custom logger

Implement the CoreLogger protocol to use your own logging solution:

swift
// Implement the CoreLogger protocol
class CustomLogger: CoreLogger {
    func error(message: String) {
        // Send error logs to your logging service
        myLoggingService.error(message)
    }

    func warn(message: String) {
        myLoggingService.warn(message)
    }

    func log(message: String) {
        myLoggingService.log(message)
    }

    func debug(message: String) {
        myLoggingService.debug(message)
    }
}

// Initialize with custom logger
let config = ExperimentConfigBuilder()
    .loggerProvider(CustomLogger())
    .logLevel(.warn)
    .build()
let experiment = Experiment.initialize(apiKey: "<DEPLOYMENT_KEY>", config: config)

Was this helpful?