Skip to content

Swift SDK Reference

TL;DR: Add the featureflip-swift Swift Package, call client.boolVariation("flag-key", default: false), and you’re evaluating flags from any iOS or macOS app.

The Featureflip Swift package provides a client-side SDK for evaluating feature flags in Apple platform apps. It fetches evaluated flag values from the Featureflip Evaluation API, supports real-time updates via SSE streaming, disk caching for offline starts, and automatic lifecycle management (pausing on background, resuming on foreground).

View source on GitHub

  • Swift 5.9+
PlatformMinimum Version
iOS15.0
macOS13.0
tvOS15.0
watchOS8.0

Add the package via Swift Package Manager.

In Xcode: File → Add Package Dependencies, then enter the repository URL.

Or in Package.swift:

dependencies: [
.package(url: "https://github.com/canopy-labs/featureflip-swift", from: "2.0.0")
]

The FeatureflipConfig struct accepts the following options:

OptionTypeDefaultDescription
clientKeyStringrequiredClient SDK key from your project settings
baseUrlString"https://eval.featureflip.io"Evaluation API base URL
context[String: String][:]Initial evaluation context (user attributes)
streamingBooltrueEnable SSE for real-time flag updates
pollIntervalTimeInterval30Polling interval in seconds (used when streaming is disabled)
flushIntervalTimeInterval30Interval in seconds between event flush batches
flushBatchSizeInt100Maximum number of events per flush batch
initTimeoutTimeInterval10Maximum time in seconds to wait for initial flag fetch
let config = FeatureflipConfig(
clientKey: "your-client-sdk-key",
context: ["user_id": "u-123"],
streaming: true,
pollInterval: 60,
initTimeout: 15
)

Create a client and call initialize():

import Featureflip
let config = FeatureflipConfig(clientKey: "your-client-sdk-key")
let client = FeatureflipClient(config: config)
await client.initialize()

initialize() loads any cached flags from disk, fetches fresh flag values from the API, starts SSE streaming (or polling), and begins the event processor. The SDK is usable with cached values even if the network request fails.

client.isInitialized: Bool

A read-only property that returns true once the client has successfully loaded flag data.

All variation methods are synchronous and return immediately from an in-memory snapshot. They accept a flag key and a default value. The default is returned if the flag is missing or has an unexpected type.

Since this is a client-side SDK, evaluation context is set at initialization or via identify() — it is not passed per evaluation call.

let enabled = client.boolVariation("dark-mode", default: false)
let color = client.stringVariation("button-color", default: "blue")
let limit = client.numberVariation("rate-limit", default: 100.0)

Returns a Double.

let config = client.jsonVariation("ui-config", default: .dictionary(["theme": .string("light")]))

Returns an AnyCodableValue enum (see Types).

Call identify() to re-evaluate all flags for a new user context — typically after login. This makes a network request to the evaluation API with the new context and updates all cached flag values, applying targeting rules and percentage rollouts to the new attributes.

try await client.identify(context: ["user_id": "u-123", "plan": "pro"])

The streaming or polling data source is also updated with the new context.

Enqueues a custom analytics event. Events are batched and flushed automatically.

client.track("purchase-completed", metadata: [
"plan": .string("pro"),
"amount": .double(29.99),
])

Forces all buffered events to be sent immediately.

await client.flush()

Stops streaming or polling and flushes remaining events.

await client.close()

The SDK automatically observes app lifecycle events:

  • Background: Stops streaming/polling and flushes events
  • Foreground: Resumes streaming/polling

No manual intervention is required.

Two FeatureflipClient(config:) calls with the same clientKey return distinct handle objects that share one underlying core. The core holds all expensive resources (network connections, caches, event processor), while each handle is a lightweight wrapper.

let a = FeatureflipClient(config: config)
let b = FeatureflipClient(config: config)
// a and b share the same underlying core

Each close() call decrements the core’s reference count. The core only shuts down (stops streaming, flushes events) when the last handle calls close().

await a.close() // core stays alive (b still open)
await b.close() // core shuts down

initialize() is idempotent on the shared core. The first call performs the real work (disk cache load, network fetch, start streaming). Subsequent calls from any handle return immediately.

Each handle has its own FeatureFlagProvider instance. FeatureFlagProvider works per-handle — inject the handle’s flagProvider into the SwiftUI environment as usual.

The SDK provides a FeatureFlagProvider for reactive SwiftUI updates. Access it via the client’s flagProvider property.

struct ContentView: View {
@ObservedObject var flags: FeatureFlagProvider
init(client: FeatureflipClient) {
self.flags = client.flagProvider
}
var body: some View {
if flags.boolVariation("show-banner", default: false) {
Text("New feature available!")
}
}
}

The provider is an ObservableObject that triggers view updates when flag values change via streaming or identify().

Creates a test client with static flag overrides. No network calls are made and no background tasks are started. The client is immediately initialized.

let client = FeatureflipClient.forTesting([
"dark-mode": true,
"pricing-tier": "enterprise",
"rate-limit": 500,
])
client.boolVariation("dark-mode", default: false) // true
client.stringVariation("pricing-tier", default: "free") // "enterprise"
client.boolVariation("unknown", default: false) // false (default)

Signature:

static func forTesting(_ overrides: [String: Any]) -> FeatureflipClient

Supported override value types: Bool, String, Int, Double.

A type-erased enum used for JSON flag values and event metadata:

public enum AnyCodableValue: Sendable, Equatable, Codable {
case bool(Bool)
case string(String)
case int(Int)
case double(Double)
case dictionary([String: AnyCodableValue])
case array([AnyCodableValue])
case null
}

The internal representation of a flag returned by the server:

PropertyTypeDescription
valueAnyCodableValueThe evaluated flag value
variationStringThe key of the matched variation
reasonStringWhy this value was returned
ValueDescription
"Fallthrough"No rules matched; the fallthrough variation was served
"RuleMatch"A targeting rule matched the context
"FlagDisabled"The flag is disabled; the off variation was served
"FlagNotFound"The flag key does not exist
"Error"An error occurred during evaluation
"TEST"Returned by test clients
PlatformMinimum Version
iOS15.0
macOS13.0
tvOS15.0
watchOS8.0