# Swift On Device Evaluation SDK

{% callout type="info" %}
Statsig recommends its normal (remote evaluation) SDKs for most client applications. Understand the use case and privacy risks by reading the [On-Device Eval SDK overview](https://docs.statsig.com/client/onDeviceOverview). On-device evaluation SDKs are for Enterprise & Pro Tier only.
{% /callout %}

These SDKs use a different paradigm than their precomputed counterparts: [JS](https://docs.statsig.com/client/javascript-sdk), [Android](https://docs.statsig.com/client/Android), [iOS](https://docs.statsig.com/client/iosClientSDK). On-device evaluation SDKs behave more like Server SDKs. Rather than requiring a user in advance, you can check gates, configs, and experiments for any set of user properties. The SDK downloads a complete representation of your project and evaluates checks in real time.

### Pros

* No network request is needed when changing user properties: check the gate/config/experiment locally
* You can bring your own CDN or synchronously initialize with a preloaded project definition
* Lower latency to download configs cached at the edge, rather than evaluated for a given user (which Statsig can't cache as much)

### Cons

* The client has the entire project definition: it exposes the names and configurations of all experiments and feature flags accessible by your client key. Refer to [client key with server permission best practices](https://docs.statsig.com/access-management/api-keys#client-keys-with-server-permissions).
* Payload size is larger than what the traditional SDKs require
* Evaluation performance is slightly slower: rather than looking up the value, the SDK must evaluate targeting conditions and an allocation decision
* Doesn't support ID list segments with > 1000 IDs
* Doesn't support IP or User Agent based checks (Browser Version/Name, OS Version/Name, IP, Country)

## Set up the SDK

{% steps %}
{% step title="Install the SDK" %}
To use the SDK in your project, you must add Statsig as a dependency.

{% codetabs %}
```swift Swift Package Manager
// In your Xcode, select File > Swift Packages > Add Package Dependency
// and enter the URL https://github.com/statsig-io/swift-on-device-evaluations-sdk.git.
//
// You can also include it directly in your project's Package.swift. 
// Find out the latest release version on our GitHub page:
// https://github.com/statsig-io/swift-on-device-evaluations-sdk/releases

dependencies: [
    // see the latest version on https://github.com/statsig-io/swift-on-device-evaluations-sdk/releases
    .package(url: "https://github.com/statsig-io/swift-on-device-evaluations-sdk.git", .upToNextMinor("X.Y.Z")),
],
//...
targets: [
    .target(
        name: "YOUR_TARGET",
        dependencies: ["StatsigOnDeviceEvaluations"]
    )
],
```

```ruby CocoaPods
# If you are using CocoaPods, our pod name is 'StatsigOnDeviceEvaluations', 
# and you can include the following line to your Podfile:

use_frameworks!
target 'TargetName' do
  //...
  pod 'StatsigOnDeviceEvaluations', '~> X.Y.Z'
end

# Find the latest versions by searching cocoapods.org or on Github:
# https://github.com/statsig-io/swift-on-device-evaluations-sdk/releases
```
{% /codetabs %}
{% /step %}

{% step title="Initialize the SDK" %}
Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application.

Along with the key, pass in a [User Object](#statsig-user) with the attributes you want to target later on in a gate or experiment.

{% callout type="warning" %}
For On-Device Evaluation, add the **"Allow Download Config Specs"** scope. Client keys, by default, can't download the project definition for on-device evaluation.

While client keys are safe to include, always keep Server and Console keys private.
{% /callout %}

{% accordion title="How to add the scope" %}
{% tabs %}
{% tab title="New SDK Keys" %}
When creating a new client key, select **"Allow Download Config Specs"**

![Add DCS Scope to New Key](https://docs.statsig.com/images/local-eval/new-keys.png)
{% /tab %}

{% tab title="Existing SDK Keys" %}
To add the scope to an existing key, under **Project Settings** → **API Keys** → **Client API Keys**, select **Actions** → **Edit Scopes**, and select **"Allow Download Config Specs"**, then **Save**.

![Add DCS Scope to Existing Key](https://docs.statsig.com/images/local-eval/existing-keys.png)
{% /tab %}
{% /tabs %}
{% /accordion %}

{% codetabs %}
```swift Async (Swift)
import StatsigOnDeviceEvaluations

// (optional) Configure the SDK if needed
let opts = StatsigOptions()
opts.environment.tier = "staging"

Statsig.shared.initialize("client-sdk-key", options: opts) { err in
    if let err = err {
        print("Error \(err)")
    }
}

// or, create your own instance

let myStatsigInstance = Statsig()
myStatsigInstance.initialize("client-sdk-key", options: opts) { err in
    if let err = err {
        print("Error \(err)")
    }
}
```

```objective-c Objective C
StatsigOptions *options = [StatsigOptions new];

StatsigEnvironment *env = [StatsigEnvironment new];
env.tier = @"staging";
options.environment = env;

[[Statsig sharedInstance] 
  initializeWithSDKKey:@"client-sdk-key"
  options:options
  completion:^(NSError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error %@", error);
    }
}];
```

```swift Synchronous (Swift)
import StatsigOnDeviceEvaluations

// (optional) Configure the SDK if needed
let opts = StatsigOptions()
opts.environment.tier = "staging"

let specs: NSString = "..." // JSON string of your configurations

let error = client.initializeSync("client-sdk-key", initialSpecs: specs)
if let err = error {
    print("Error \(err)")
}
```
{% /codetabs %}

You can configure the SDK to use cached values if they are newer than the local file.
This is useful when you ship your app with a local file but want it used only for the first session.
In the following example, the SDK uses `initialSpecs` only when there is no cache or the cache is older than `initialSpecs`.

```swift
let options = StatsigOptions()
options.useNewerCacheValuesOverProvidedValues = true

client.initializeSync(
  "client-sdk-key", 
  initialSpecs: specs,
  options: options
)
```

{% callout type="note" %}
You can get a copy of your current specs data by visiting: `https://api.statsigcdn.com/v1/download_config_specs/client-{YOUR_SDK_KEY}.json`
{% /callout %}
{% /step %}
{% /steps %}

## Working with the SDK

### Checking a feature flag/gate

After you initialize the SDK, check a [**Feature Gate**](https://docs.statsig.com/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default.

{% codetabs %}
```swift Swift
// Simple Pass/Fail check
let isPassing: Bool = Statsig.shared.checkGate("my_gate", user)

// or, the verbose FeatureGate check
let gate = Statsig.shared.getFeatureGate("my_gate", user)
print(gate.evaluationDetails.reason) // "Network" | "Cache" | "Unrecognized"
let isPassing: Bool = gate.value
```

```objective-c Objective C
BOOL isPassing = [[Statsig sharedInstance] checkGate:@"my_gate" forUser:user];
```
{% /codetabs %}

### Reading a dynamic config

Feature Gates are useful for simple on/off switches with optional advanced user targeting. If you need to send a different set of values (strings, numbers, and so on) to your clients based on specific user attributes (such as country), use **Dynamic Configs**. The API is similar to Feature Gates, but you receive an entire JSON object you can configure on the server and fetch typed parameters from.

```swift
let config = Statsig.shared.getDynamicConfig("my_dynamic_config", user)

let name: String? = config.value["product_name"] as? String
let price: Double? = config.value["price"] as? Double
```

### Getting a layer/experiment

Use **Layers/Experiments** to run A/B/n experiments. Statsig offers two APIs but recommends [layers](https://docs.statsig.com/experiments/layers-overview) because they enable quicker iterations with parameter reuse.

```swift
// Getting values via getLayer
let layer = Statsig.shared.getLayer("my_layer", user)
let name: String? = layer.getValue(param: "product_name", fallback: "Unknown") as? String

// or, using getExperiment
let experiment = Statsig.shared.getExperiment("my_experiment", user)
let name: String? = experiment.value["product_name"] as? String
let price: Double? = experiment.value["price"] as? Double
```

### Logging an event

After you set up a Feature Gate or an Experiment, you can track custom events to see how your features or experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and an object of metadata to log with the event:

```swift
let event = StatsigEvent(
    eventName: "add_to_cart",
    value: "SKU_1234",
    metadata: [
        "price": "9.99",
        "item_name": "CoolProduct"
    ]
)

Statsig.shared.logEvent(event, user)
```

### Code examples

Find working sample apps in the repository:

* [Swift & Objective C Examples](https://github.com/statsig-io/swift-on-device-evaluations-sdk/tree/main/Sample/App/Examples/OnDeviceEvaluations)

## Statsig user

Provide a `StatsigUser` object to check or get your configurations. Pass as much information as possible to use advanced gate and config conditions.

You usually need the `userID` field to provide a consistent experience for a given user. Refer to [logged-out experiments](https://docs.statsig.com/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users.

In addition to `userID`, `StatsigUser` has the following top-level fields: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass any key-value pairs in an object or dictionary to the `custom` field to create targeting based on them.

After the user logs in or their attributes change, call `updateUser`
with the updated `userID` and any other updated user attributes:

{% codetabs %}
```swift Swift
let user = StatsigUser(
    userID: "a-user",
    customIDs: ["EmployeeID": "an-employee"],
    email: "user@statsig.io",
    ip: "58.84.239.246",
    userAgent: "Mozilla/5.0 (iPad; CPU OS 13_4_1....",
    country: "NZ",
    locale: "en_NZ",
    appVersion: "3.2.1",
    custom: ["Level": "9001"],
    privateAttributes: ["SensitiveInfo": "shhh"]
)
```

```objective-c Objective C
StatsigUser *user = [StatsigUser userWithUserID:@"a-user"];
user.customIDs = @{ @"EmployeeID": @"an-employee" };
user.email = @"user@statsig.io";
user.ip = @"58.84.239.246";
user.userAgent = @"Mozilla/5.0 (iPad; CPU OS 13_4_1....";
user.country = @"NZ";
user.locale = @"en_NZ";
user.appVersion = @"3.2.1";
[user.custom setString:@"9001" forKey:@"Level"];
[user.privateAttributes setString:@"shhh" forKey:@"SensitiveInfo"];
```
{% /codetabs %}

### Private attributes

The `StatsigUser` object has a `privateAttributes` field: an object/dictionary for setting private user attributes. Statsig uses any attribute set in `privateAttributes` only for evaluation and targeting, and removes it from any logs before sending them to the server.

For example, you might have feature gates that should pass only for users with emails ending in "@statsig.com", but you don't want to log users' email addresses to Statsig. In this case, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user.

### Setting a global user

To avoid passing the user object to every evaluation call, you can set a global user. The SDK uses the global user for all evaluations unless otherwise specified.

```swift
Statsig.shared.setGlobalUser(myGlobalUser)

Statsig.shared.checkGate("my_gate") // <- Will use myGlobalUser
Statsig.shared.checkGate("my_gate", StatsigUser(userID: "user-123")) // <- Will NOT use myGlobalUser
```

{% callout type="note" %}
Unlike precomputed evaluation SDKs, the on-device evaluation SDK doesn't have an `updateUser` method since it evaluates gates/configs/experiments in real-time for any user object you pass in.
{% /callout %}

## Statsig options

Configure the SDK behavior by passing a `StatsigOptions` object during initialization.

{% parameter name="eventQueueMaxSize" type="Int" %}
The maximum number of events to batch before flushing logs to the server.
{% /parameter %}

{% parameter name="eventQueueInternalMs" type="Double" %}
How frequently to flush queued logs.
{% /parameter %}

{% parameter name="eventLoggingAPI" type="String" %}
The API where Statsig sends all events.
{% /parameter %}

{% parameter name="configSpecAPI" type="String" %}
The API the SDK uses to fetch the latest configurations.
{% /parameter %}

{% parameter name="environment" type="StatsigEnvironment" %}
An object you can use to set environment variables that apply to all of your users in the same session and Statsig uses for targeting purposes.
{% /parameter %}

## Lifecycle and advanced usage

## Shutting Statsig down

The SDK keeps event logs in a client cache and flushes them periodically to save battery usage and prevent dropped events. Statsig may not send some events when your app shuts down before a flush occurs.

To flush or save all logged events locally, call shutdown when your app is closing:

{% codetabs %}
```swift Swift
Statsig.shared.shutdown { err in
    if let err = err {
        print("An error occurred during Statsig shutdown: \(err)")
    } else {
        print("Statsig shutdown successfully")
    }
}
```

```objective-c Objective C
[[Statsig sharedInstance] shutdownWithCompletion:^(NSError * _Nullable error) {
    if (error != nil) {
        NSLog(@"An error occurred during Statsig shutdown: %@", error);
    } else {
        NSLog(@"Statsig shutdown successfully");
    }
}];
```
{% /codetabs %}

## Post-init syncing

### From network

By default, the SDK syncs only during initialization. To re-sync after initialization, call the `Statsig.update` method. This triggers a network call to fetch the latest changes from the server.

```swift
Statsig.shared.update { err in
    if let err = err {
        print("Statsig update error: \(err)")
    }
}
```

### From a local file

If you maintain your own copy of the "specs" json, pass it to `Statsig.updateSync()`. This skips the network call and uses the provided specs instead.

```swift
let result = Statsig.shared.updateSync(updatedSpecs: myJsonData)
```

### Scheduled polling

To have the SDK regularly poll for updates, start the polling task with `Statsig.scheduleBackgroundUpdates()`. This calls `Statsig.update` internally to fetch the latest changes from the network.

```swift
let pollingTask = Statsig.shared.scheduleBackgroundUpdates() // Defaults to 1 hour interval

// or, specify a custom interval
let pollingTask = Statsig.shared.scheduleBackgroundUpdates(intervalSeconds: 300)

// and, if you need to cancel it later
pollingTask?.cancel()
```

## Local overrides

Override the values the Statsig SDK returns for unit testing or for enabling features in local development.

To set up local overrides, pass an instance of `LocalOverrideAdapter` to the SDK through the `StatsigOptions` object.

{% callout type="note" %}
You can write your own override adapter by implementing the [`OverrideAdapter`](https://github.com/statsig-io/swift-on-device-evaluations-sdk/blob/main/Sources/StatsigOnDeviceEvaluations/OverrideAdapter.swift) protocol and passing it in instead.
{% /callout %}

{% codetabs %}
```swift Swift
let user = StatsigUser(userID: "a-user")

let overrides = LocalOverrideAdapter()

// Override a gate
overrides.setGate(user, FeatureGate.create("local_override_gate", true))

// Override a dynamic config (Similar for Layer and Experiment)
overrides.setDynamicConfig(user, DynamicConfig.create("local_override_dynamic_config", ["foo": "bar"]))

let opts = StatsigOptions()
opts.overrideAdapter = overrides

Statsig.shared.initialize(YOUR_SDK_KEY, options: opts) { _ in
    let gate = Statsig.shared.getFeatureGate("local_override_gate", user)
    print("Result: \(gate.value) (\(gate.evaluationDetails.reason))")
}
```

```objective-c Objective C
StatsigUser *user = [StatsigUser userWithUserID:@"a-user"];

LocalOverrideAdapter *overrides = [LocalOverrideAdapter new];

// Override a gate
[overrides
    setGateForUser:user
    gate:[FeatureGate createWithName:@"local_override_gate" andValue:true]];

// Override a dynamic config (Similar for Layer and Experiment)
[_overrides
    setDynamicConfigForUser:user
    config:[DynamicConfig
            createWithName:@"local_override_dynamic_config"
            andValue:@{@"foo": @"bar"}]];

StatsigOptions *options = [StatsigOptions new];
options.overrideAdapter = overrides;

[[Statsig sharedInstance] 
    initializeWithSDKKey:YOUR_SDK_KEY
    options:options
    completion:^(NSError * _Nullable error) {

    FeatureGate *gate = [[Statsig sharedInstance] getFeatureGate:@"local_override_gate" forUser:user options:nil];
    NSLog(@"Result: %d (%@)", gate.value, gate.evaluationDetails.reason);

}];
```
{% /codetabs %}

## FAQs


{% accordion-group %}
{% accordion title="How do I run experiments for logged out users?" %}
Refer to the guide on [device level experiments](https://docs.statsig.com/guides/first-device-level-experiment).
{% /accordion %}
{% /accordion-group %}

## Additional resources

* [On-Device Evaluation SDK Overview](https://docs.statsig.com/client/onDeviceOverview)
* [Client Keys with Server Permissions](https://docs.statsig.com/access-management/api-keys#client-keys-with-server-permissions)
* [Debugging SDK Evaluations](https://docs.statsig.com/sdks/debugging)
