This is a port of Featurevisor Javascript SDK v2.x to Swift, providing a way to evaluate feature flags, variations, and variables in your Swift applications.
This SDK is compatible with Featurevisor v2.0 projects and above.
- Installation
- Initialization
- Evaluation types
- Context
- Check if enabled
- Getting variation
- Getting variables
- Getting all evaluations
- Sticky
- Setting datafile
- Logging
- Events
- Evaluation details
- Hooks
- Child instance
- Close
- CLI usage
- Development of this package
- License
In your Swift application, add this package using Swift Package Manager:
.package(url: "https://github.com/featurevisor/featurevisor-swift2.git", from: "0.1.0")Then add the product dependency:
.product(name: "Featurevisor", package: "featurevisor-swift2")The SDK can be initialized by passing datafile content directly:
import Foundation
import Featurevisor
let datafileURL = URL(string: "https://cdn.yoursite.com/datafile.json")!
let data = try Data(contentsOf: datafileURL)
let datafileContent = try DatafileContent.fromData(data)
let f = createInstance(
InstanceOptions(
datafile: datafileContent
)
)We can evaluate 3 types of values against a particular feature:
- Flag (
Bool): whether the feature is enabled or not - Variation (
String): the variation of the feature (if any) - Variables: variable values of the feature (if any)
These evaluations are run against the provided context.
Contexts are attribute values that we pass to SDK for evaluating features against.
Think of the conditions that you define in your segments, which are used in your feature's rules.
They are plain dictionaries:
let context: Context = [
"userId": .string("123"),
"country": .string("nl"),
]You can set context at the time of initialization:
let f = createInstance(
InstanceOptions(
context: [
"deviceId": .string("123"),
"country": .string("nl"),
]
)
)You can also set more context after the SDK has been initialized:
f.setContext([
"userId": .string("234"),
])This will merge the new context with the existing one (if already set).
If you wish to fully replace the existing context, you can pass true in second argument:
f.setContext(
[
"deviceId": .string("123"),
"userId": .string("234"),
"country": .string("nl"),
"browser": .string("chrome"),
],
replace: true
)You can optionally pass additional context manually for each and every evaluation separately, without needing to set it to the SDK instance affecting all evaluations:
let context: Context = [
"userId": .string("123"),
"country": .string("nl"),
]
let isEnabled = f.isEnabled("my_feature", context)
let variation = f.getVariation("my_feature", context)
let variableValue = f.getVariable("my_feature", "my_variable", context)When manually passing context, it will merge with existing context set to the SDK instance before evaluating the specific value.
Once the SDK is initialized, you can check if a feature is enabled or not:
let featureKey = "my_feature"
let isEnabled = f.isEnabled(featureKey)
if isEnabled {
// do something
}You can also pass additional context per evaluation:
let isEnabled = f.isEnabled(featureKey, [
// ...additional context
])If your feature has any variations defined, you can evaluate them as follows:
let featureKey = "my_feature"
let variation = f.getVariation(featureKey)
if variation == "treatment" {
// do something for treatment variation
} else {
// handle default/control variation
}Additional context per evaluation can also be passed:
let variation = f.getVariation(featureKey, [
// ...additional context
])Your features may also include variables, which can be evaluated as follows:
let variableKey = "bgColor"
let bgColorValue = f.getVariable("my_feature", variableKey)Additional context per evaluation can also be passed:
let bgColorValue = f.getVariable("my_feature", variableKey, [
// ...additional context
])Next to generic getVariable() methods, there are also type specific methods available for convenience:
f.getVariableBoolean(featureKey, variableKey, context)
f.getVariableString(featureKey, variableKey, context)
f.getVariableInteger(featureKey, variableKey, context)
f.getVariableDouble(featureKey, variableKey, context)
f.getVariableArray(featureKey, variableKey, context)
f.getVariableObject(featureKey, variableKey, context)
f.getVariableJSON(featureKey, variableKey, context)You can get evaluations of all features available in the SDK instance:
let allEvaluations = f.getAllEvaluations([:])
print(allEvaluations)This is handy especially when you want to pass all evaluations from a backend application to the frontend.
For the lifecycle of the SDK instance in your application, you can set some features with sticky values, meaning that they will not be evaluated against the fetched datafile:
let f = createInstance(
InstanceOptions(
sticky: [
"myFeatureKey": EvaluatedFeature(
enabled: true,
variation: "treatment",
variables: ["myVariableKey": .string("myVariableValue")]
),
"anotherFeatureKey": EvaluatedFeature(enabled: false),
]
)
)f.setSticky([
"myFeatureKey": EvaluatedFeature(
enabled: true,
variation: "treatment",
variables: ["myVariableKey": .string("myVariableValue")]
),
"anotherFeatureKey": EvaluatedFeature(enabled: false),
], replace: true)You may also initialize the SDK without passing datafile, and set it later on:
f.setDatafile(datafileContent)You can also set using raw JSON string:
f.setDatafile(json: jsonString)You can set the datafile as many times as you want in your application, which will result in emitting a datafile_set event that you can listen and react to accordingly.
import Foundation
let interval: TimeInterval = 5 * 60
Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in
if let data = try? Data(contentsOf: datafileURL),
let datafile = try? DatafileContent.fromData(data) {
f.setDatafile(datafile)
}
}By default, Featurevisor SDK logs from info level and above.
These are available log levels:
debuginfowarnerrorfatal
You can set log level at initialization:
let f = createInstance(
InstanceOptions(
logLevel: .debug
)
)Or set it afterwards:
f.setLogLevel(.debug)If you want to fully control log output, pass a custom logger:
let logger = createLogger(level: .debug) { level, message, details in
print("[\(level)] \(message) \(details)")
}
let f = createInstance(
InstanceOptions(
datafile: datafileContent,
logger: logger
)
)Featurevisor SDK implements a simple event emitter that allows you to listen to runtime events.
let unsubscribe = f.on(.datafileSet) { payload in
print(payload.params)
}
unsubscribe()let unsubscribe = f.on(.contextSet) { _ in
// handle context updates
}
unsubscribe()let unsubscribe = f.on(.stickySet) { _ in
// handle sticky updates
}
unsubscribe()If you need evaluation metadata, use:
let flagDetails = f.evaluateFlag("my_feature")
let variationDetails = f.evaluateVariation("my_feature")
let variableDetails = f.evaluateVariable("my_feature", "my_variable")Hooks allow you to intercept evaluation inputs and outputs.
let hook = Hook(
name: "my-hook",
before: { input in
input
},
after: { evaluation, _ in
evaluation
}
)let f = createInstance(
InstanceOptions(
hooks: [hook]
)
)
let removeHook = f.addHook(hook)
removeHook()You can spawn child instances with inherited context:
let child = f.spawn([
"userId": .string("123"),
])
let enabled = child.isEnabled("my_feature")To clear listeners and close resources:
f.close()The package also ships an executable named featurevisor.
swift run featurevisor test \
--projectDirectoryPath=/path/to/featurevisor-projectWith scoped and tagged datafiles:
swift run featurevisor test \
--projectDirectoryPath=/path/to/featurevisor-project \
--with-scopes \
--with-tagsswift run featurevisor benchmark \
--projectDirectoryPath=/path/to/featurevisor-project \
--environment=production \
--feature=my_feature \
--context='{"userId":"123"}' \
--n=1000swift run featurevisor assess-distribution \
--projectDirectoryPath=/path/to/featurevisor-project \
--environment=production \
--feature=my_feature \
--populateUuid=userId \
--n=1000swift testMIT © Fahad Heylaal