Swift SDK Reference
TL;DR: Add the
featureflip-swiftSwift Package, callclient.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).
Requirements
Section titled “Requirements”- Swift 5.9+
| Platform | Minimum Version |
|---|---|
| iOS | 15.0 |
| macOS | 13.0 |
| tvOS | 15.0 |
| watchOS | 8.0 |
Installation
Section titled “Installation”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")]Configuration
Section titled “Configuration”The FeatureflipConfig struct accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
clientKey | String | required | Client SDK key from your project settings |
baseUrl | String | "https://eval.featureflip.io" | Evaluation API base URL |
context | [String: String] | [:] | Initial evaluation context (user attributes) |
streaming | Bool | true | Enable SSE for real-time flag updates |
pollInterval | TimeInterval | 30 | Polling interval in seconds (used when streaming is disabled) |
flushInterval | TimeInterval | 30 | Interval in seconds between event flush batches |
flushBatchSize | Int | 100 | Maximum number of events per flush batch |
initTimeout | TimeInterval | 10 | Maximum time in seconds to wait for initial flag fetch |
Example configuration
Section titled “Example configuration”let config = FeatureflipConfig( clientKey: "your-client-sdk-key", context: ["user_id": "u-123"], streaming: true, pollInterval: 60, initTimeout: 15)Initialization
Section titled “Initialization”Instance-based
Section titled “Instance-based”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.
isInitialized
Section titled “isInitialized”client.isInitialized: BoolA read-only property that returns true once the client has successfully loaded flag data.
Evaluating Flags
Section titled “Evaluating Flags”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.
boolVariation(_:default:)
Section titled “boolVariation(_:default:)”let enabled = client.boolVariation("dark-mode", default: false)stringVariation(_:default:)
Section titled “stringVariation(_:default:)”let color = client.stringVariation("button-color", default: "blue")numberVariation(_:default:)
Section titled “numberVariation(_:default:)”let limit = client.numberVariation("rate-limit", default: 100.0)Returns a Double.
jsonVariation(_:default:)
Section titled “jsonVariation(_:default:)”let config = client.jsonVariation("ui-config", default: .dictionary(["theme": .string("light")]))Returns an AnyCodableValue enum (see Types).
Identifying Users
Section titled “Identifying Users”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.
Event Tracking
Section titled “Event Tracking”track(_:metadata:)
Section titled “track(_:metadata:)”Enqueues a custom analytics event. Events are batched and flushed automatically.
client.track("purchase-completed", metadata: [ "plan": .string("pro"), "amount": .double(29.99),])flush()
Section titled “flush()”Forces all buffered events to be sent immediately.
await client.flush()Cleanup
Section titled “Cleanup”close()
Section titled “close()”Stops streaming or polling and flushes remaining events.
await client.close()Lifecycle management
Section titled “Lifecycle management”The SDK automatically observes app lifecycle events:
- Background: Stops streaming/polling and flushes events
- Foreground: Resumes streaming/polling
No manual intervention is required.
Lifetime
Section titled “Lifetime”Shared core
Section titled “Shared core”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 coreRefcounted close()
Section titled “Refcounted close()”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 downIdempotent initialize()
Section titled “Idempotent initialize()”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.
SwiftUI
Section titled “SwiftUI”Each handle has its own FeatureFlagProvider instance. FeatureFlagProvider works per-handle — inject the handle’s flagProvider into the SwiftUI environment as usual.
SwiftUI Integration
Section titled “SwiftUI Integration”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().
Testing
Section titled “Testing”FeatureflipClient.forTesting(_:)
Section titled “FeatureflipClient.forTesting(_:)”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) // trueclient.stringVariation("pricing-tier", default: "free") // "enterprise"client.boolVariation("unknown", default: false) // false (default)Signature:
static func forTesting(_ overrides: [String: Any]) -> FeatureflipClientSupported override value types: Bool, String, Int, Double.
AnyCodableValue
Section titled “AnyCodableValue”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}FlagValue
Section titled “FlagValue”The internal representation of a flag returned by the server:
| Property | Type | Description |
|---|---|---|
value | AnyCodableValue | The evaluated flag value |
variation | String | The key of the matched variation |
reason | String | Why this value was returned |
Flag reasons
Section titled “Flag reasons”| Value | Description |
|---|---|
"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 |
Platforms
Section titled “Platforms”| Platform | Minimum Version |
|---|---|
| iOS | 15.0 |
| macOS | 13.0 |
| tvOS | 15.0 |
| watchOS | 8.0 |
See also
Section titled “See also”- Swift Quickstart — get started in under 5 minutes
- Targeting & Segments — control which users see which variation
- Evaluation API — REST API reference
- SDK Overview — compare server-side and client-side SDKs