Skip to content

Commit d0f5e14

Browse files
iOS SDK rewrite (#488)
* initial ios update * minor edits * Update docs/sdks/client-sdks/ios.md Co-authored-by: Oleksii Shmalko <[email protected]> --------- Co-authored-by: Oleksii Shmalko <[email protected]>
1 parent 1720a42 commit d0f5e14

File tree

1 file changed

+210
-59
lines changed

1 file changed

+210
-59
lines changed

docs/sdks/client-sdks/ios.md

Lines changed: 210 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# iOS
22

3-
Swift implementation of the Eppo Randomization and Feature Flagging SDK.
3+
Eppo's open source Swift SDK can be used for both feature flagging and experiment assignment.
44

55
The repository is hosted at [https://github.com/Eppo-exp/eppo-ios-sdk](https://github.com/Eppo-exp/eppo-ios-sdk)
66

7-
## 1. Install the SDK
7+
## Getting Started
8+
9+
### Installation
810

911
While in XCode:
1012

@@ -13,23 +15,49 @@ While in XCode:
1315
> 3. Set dependency rule to `Up to Next Minor Version` and select `Add Package`
1416
> 4. Add to your project's target.
1517
16-
## 2. Initialize the SDK
18+
### Usage
19+
20+
Begin by initializing a singleton instance of Eppo's client with a key from the [Eppo interface](https://eppo.cloud/feature-flags/keys). The client will configure itself and perform a network request to fetch the latest flag configurations. Once initialized, the client can be used to make assignments anywhere in your app.
1721

18-
Initialize with a SDK key, which can be created in the Eppo web interface:
22+
#### Initialize once
1923

2024
```swift
21-
EppoClient.initialize(sdkKey: "YOUR_EPPO_API_KEY");
25+
Task {
26+
try await EppoClient.initialize(sdkKey: "SDK-KEY-FROM-DASHBOARD");
27+
}
2228
```
2329

24-
During initialization, the SDK sends an API request to Eppo to retrieve the most recent experiment configurations such as variation values and traffic allocation. The SDK stores these configurations in memory so that assignments are effectively instant. For more information, see the [architecture overview](/sdks/architecture) page.
30+
(It is recommended to wrap initialization in a `Task` block in order to perform network request asynchronously)
2531

26-
If you are using the SDK for experiment assignments, make sure to pass in an assignment logging callback (see [section](#define-an-assignment-logger-experiment-assignment-only) below).
32+
#### Assign anywhere
33+
34+
```swift
35+
let assignment = try eppoClient.getStringAssignment(
36+
flagKey: "new-user-onboarding",
37+
subjectKey: user.id,
38+
subjectAttributes: user.attributes,
39+
defaultValue: "control"
40+
);
41+
```
2742

28-
### Define an assignment logger (experiment assignment only)
43+
During initialization, the SDK sends an API request to Eppo to retrieve the most recent experiment configurations: variation values, traffic allocation, etc. The SDK stores these configurations in memory, meaning assignments are effectively instant (accordingly, assignment is a synchronous operation). For more information, see the [architecture overview](/sdks/architecture) page.
44+
45+
Eppo's SDK also supports providing the configuration directly at initialization. For more information, see the [initialization modes](#initialization-modes) section below.
46+
47+
### Connecting an event logger
48+
49+
Eppo is architected so that raw user data never leaves your system. As part of that, instead of pushing subject-level exposure events to Eppo's servers, Eppo's SDKs integrate with your existing logging system. This is done with a logging callback function defined at SDK initialization:
50+
51+
```swift
52+
eppoClient = try await EppoClient.initialize(
53+
sdkKey: "mock-sdk-key",
54+
assignmentLogger: segmentAssignmentLogger
55+
)
56+
```
2957

30-
If you are using the Eppo SDK for experiment assignment (i.e randomization), pass in an `assignmentLogger` to the constructor on SDK initialization. The SDK will invoke this function to capture assignment data whenever a variation is assigned.
58+
This logger takes an [analytic event](#assignment-logger-schema) created by Eppo, `assignment`, and writes it to a table in the data warehouse (Snowflake, Databricks, BigQuery, or Redshift). You can read more on the [Event Logging](/sdks/event-logging) page.
3159

32-
The code below illustrates an example implementation of a logging callback using Segment. You could also use your own logging system, the only requirement is that the SDK receives a `logAssignment` function which sends data into a table in your warehouse which Eppo has read access to. Here we create an instance of an `AssignmentLogger` and configure the `EppoClient` to use this logger:
60+
The code below illustrates an example implementation of a logging callback using Segment. You could also use your own logging system, the only requirement is that the SDK receives a function that takes Eppo's `Assignment` object and writes it to your warehouse. For details on the object's schema, see the [Assignment Logger Schema](#assignment-logger-schema) section below.
3361

3462
```swift
3563
// Example of a simple assignmentLogger function
@@ -44,90 +72,213 @@ func segmentAssignmentLogger(assignment: Assignment) {
4472
]
4573

4674
analytics.track(
47-
name: "AssignmentLogged",
75+
name: "Eppo Assignment",
4876
properties: TrackProperties(assignmentDictionary)
4977
)
5078
}
79+
```
80+
81+
### Getting variations
82+
83+
Now that the SDK is initialized and connected to your event logger, you can check what variant a specific subject (typically user) should see by calling the `get<Type>Assignment` functions.
84+
85+
For example, for a string-valued flag, use `getStringAssignment`:
5186

52-
EppoClient.initialize(sdkKey: "mock-api-key", assignmentLogger: segmentAssignmentLogger)
87+
```swift
88+
let assignment = try eppoClient.getStringAssignment(
89+
flagKey: "new-user-onboarding",
90+
subjectKey: user.id,
91+
subjectAttributes: user.attributes,
92+
defaultValue: "control"
93+
);
5394
```
5495

55-
The SDK will invoke the `logAssignment` function with an `Assignment` object that contains the following fields:
96+
Note that Eppo uses a unified API for feature gates, experiments, and mutually exclusive layers. This makes it easy to turn a flag into an experiment or vice versa without having to do a code release.
5697

57-
| Field | Description | Example |
58-
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- |
59-
| `allocation` (string) | An Eppo allocation key | "allocation-17" |
60-
| `experiment` (string) | An Eppo experiment key | "recommendation-algo-allocation-17" |
61-
| `featureFlag` (string) | An Eppo feature flag key | "recommendation-algo" |
62-
| `variation` (string) | The experiment variation the subject was assigned to | "control" |
63-
| `subject` (string) | An identifier of the subject or user assigned to the experiment variation | UUID |
64-
| `timestamp` (string) | The time when the subject was assigned to the variation | 2021-06-22T17:35:12.000Z |
65-
| `subjectAttributes` (map) | A free-form map of metadata about the subject. These attributes are only logged if passed to the SDK assignment function | `{ "country": "US" }` |
98+
The `getStringAssignment` function takes four inputs to assign a variation:
6699

67-
:::note
68-
More details about logging and examples (with Segment, Rudderstack, mParticle, and Snowplow) can be found in the [event logging](/sdks/event-logging/) page.
69-
:::
100+
- `flagKey` - The key for the flag you are evaluating. This key is available on the feature flag detail page (see below).
101+
- `subjectKey` - A unique identifier for the subject being experimented on (e.g., user), typically represented by a UUID. This key is used to deterministically assign subjects to variants.
102+
- `subjectAttributes` - A map of metadata about the subject used for [targeting](/feature-flagging/concepts/targeting/). If targeting is not needed, pass in an empty object.
103+
- `defaultValue` - The value that will be returned if no allocation matches the subject, if the flag is not enabled, if `getStringAssignment` is invoked before a configuration has been loaded, or if the SDK was not able to retrieve the flag configuration. Its type must match the `get<Type>Assignment` call.
70104

71105

72-
## 3. Assign Variations
106+
#### Considerations
73107

74-
Assigning users to flags or experiments with a single `getStringAssignment` function:
108+
For applications, wrapping assignment in an `ObservableObject` is the best practice. This will create an object that will update Swift UI when the assignment is received.
75109

76110
```swift
77-
Task {
78-
do {
79-
try await EppoClient.initialize(sdkKey: "YOUR_EPPO_API_KEY");
80-
self.assignment = try EppoClient.shared().getStringAssignment(
81-
flagKey: "ios-test-app-treatment",
82-
subjectKey: "test-user",
83-
subjectAttributes: ["country": "US"],
84-
defaultVariation: "control"
85-
);
86-
} catch {
87-
self.assignment = nil;
111+
@MainActor
112+
public class AssignmentObserver: ObservableObject {
113+
@Published var assignment: String?
114+
115+
public init() {
116+
do {
117+
// Use the shared instance after it has been configured.
118+
self.assignment = try EppoClient.shared().getStringAssignment(
119+
flagKey: "new-user-onboarding",
120+
subjectKey: user.id,
121+
subjectAttributes: user.attributes,
122+
defaultValue: "control"
123+
);
124+
} catch {
125+
self.assignment = nil
126+
}
88127
}
89128
}
90129
```
91130

92-
It is recommended to wrap initialization in a `Task` block in order to perform network request asynchronously.
93-
94-
For applications wrapping initialization and assignment in an `ObservableObject` is recommended. This will create an object that will update Swift UI when the assignment is received.
131+
You can also choose to combinate instantiation and assignment within the same `ObservableObject`; the internal state will ensure only a single object and network request is created.
95132

96133
```swift
97134
@MainActor
98-
class AssignmentObserver : ObservableObject {
135+
public class AssignmentObserver: ObservableObject {
99136
@Published var assignment: String?
100137

101138
public init() {
102-
do {
103-
let client = try EppoClient.shared()
104-
self.assignment = try client.getStringAssignment(
105-
flagKey: "ios-test-app-treatment",
106-
subjectKey: "test-user",
107-
subjectAttributes: ["country": "US"],
108-
defaultVariation: "control"
109-
);
110-
} catch {
111-
self.assignment = nil;
139+
Task {
140+
do {
141+
// The initialization method has controls to maintain a single instance.
142+
try await EppoClient.initialize(sdkKey: "SDK-KEY-FROM-DASHBOARD");
143+
self.assignment = try EppoClient.shared().getStringAssignment(
144+
flagKey: "new-user-onboarding",
145+
subjectKey: user.id,
146+
subjectAttributes: user.attributes,
147+
defaultValue: "control"
148+
);
149+
} catch {
150+
self.assignment = nil
151+
}
112152
}
113153
}
114154
}
115155
```
116156

117-
The `getStringAssignment` function takes four required parameters to assign a variation:
157+
Rendering the view:
118158

119-
- `flagKey` - This key is available on the detail page for both flags and experiments. Can also be an experiment key.
120-
- `subjectKey` - The entity ID that is being experimented on.
121-
- `subjectAttributes` - An optional map of metadata about the subject used for targeting. If you create rules based on attributes on a flag/experiment, those attributes should be passed in on every assignment call.
122-
- `defaultVariation` - The default variation to return if the SDK is unable to make an assignment.
159+
```swift
160+
import SwiftUI
161+
162+
struct ContentView: View {
163+
@StateObject private var observer = AssignmentObserver()
164+
165+
var body: some View {
166+
VStack {
167+
if let assignment = observer.assignment {
168+
Text("Assignment: \(assignment)")
169+
.font(.headline)
170+
.padding()
171+
} else {
172+
Text("Loading assignment...")
173+
.font(.subheadline)
174+
.padding()
175+
}
176+
}
177+
.onAppear {
178+
// You can perform additional actions on appear if needed
179+
}
180+
}
181+
}
182+
```
123183

124184
### Typed assignments
125185

126-
Additional functions are available:
186+
Every Eppo flag has a return type that is set once on creation in the dashboard. Once a flag is created, assignments in code should be made using the corresponding typed function:
127187

128188
```swift
129189
getBooleanAssignment(...)
130-
getIntegerAssignment(...)
131190
getNumericAssignment(...)
191+
getIntegerAssignment(...)
192+
getStringAssignment(...)
132193
getJSONStringAssignment(...)
133194
```
195+
196+
Each function has the same signature, but returns the type in the function name. The only exception is `defaultValue`, which should be the same type as the flag. For boolean flags for instance, you should use `getBooleanAssignment`, which has the following signature:
197+
198+
```swift
199+
func getBooleanAssignment(
200+
flagKey: String,
201+
subjectKey: String,
202+
subjectAttributes: [String: Any],
203+
defaultValue: Bool
204+
) -> Bool
205+
```
206+
207+
## Initialization Modes
208+
209+
When initializing the SDK, you can pass in a pre-fetched flag configuration JSON string:
210+
211+
```swift
212+
Task {
213+
try await EppoClient.initialize(
214+
sdkKey: "SDK-KEY-FROM-DASHBOARD",
215+
initialConfiguration: try Configuration(
216+
flagsConfigurationJson: Data(#"{ "pre-loaded-JSON": "passed in here" }"#.utf8),
217+
obfuscated: false
218+
)
219+
);
220+
}
221+
```
222+
223+
This will still perform a network request to fetch the latest flag configurations. The provided configuration will be used until the network request is successful.
224+
225+
If you'd instead like to initialize Eppo's client without performing a network request, you can pass in a pre-fetched configuration JSON string and use the `initializeOffline` method:
226+
227+
```swift
228+
try EppoClient.initializeOffline(
229+
sdkKey: "SDK-KEY-FROM-DASHBOARD",
230+
initialConfiguration: try Configuration(
231+
flagsConfigurationJson: Data(#"{ "pre-loaded-JSON": "passed in here" }"#.utf8),
232+
obfuscated: false
233+
)
234+
);
235+
```
236+
237+
The `obfuscated` parameter is used to inform the SDK if the flag configuration is obfuscated.
238+
239+
This initialization method is synchronous and allows you to perform assignments immediately.
240+
241+
### Fetching the configuration from the remote source on-demand
242+
243+
After the client has been initialized, you can use `load()` to asynchronously fetch the latest flag configuration from the remote source.
244+
245+
```swift
246+
try EppoClient.initializeOffline(
247+
sdkKey: "SDK-KEY-FROM-DASHBOARD",
248+
initialConfiguration: try Configuration(
249+
flagsConfigurationJson: Data(#"{ "pre-loaded-JSON": "passed in here" }"#.utf8),
250+
obfuscated: false
251+
)
252+
);
253+
254+
...
255+
256+
Task {
257+
try await EppoClient.shared().load();
258+
}
259+
```
260+
261+
As modern iOS devices have substantial memory, applications are often kept in memory across sessions. This means that the flag configurations are not automatically reloaded on subsequent launches.
262+
263+
It is recommended to use the `load()` method to fetch the latest flag configurations when the application is launched.
264+
265+
266+
## Assignment Logger Schema
267+
268+
269+
The SDK will invoke the `logAssignment` function with an `Assignment` object that contains the following fields:
270+
271+
| Field | Description | Example |
272+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- |
273+
| `allocation` (string) | An Eppo allocation key | "allocation-17" |
274+
| `experiment` (string) | An Eppo experiment key | "recommendation-algo-allocation-17" |
275+
| `featureFlag` (string) | An Eppo feature flag key | "recommendation-algo" |
276+
| `variation` (string) | The experiment variation the subject was assigned to | "control" |
277+
| `subject` (string) | An identifier of the subject or user assigned to the experiment variation | UUID |
278+
| `timestamp` (string) | The time when the subject was assigned to the variation | 2021-06-22T17:35:12.000Z |
279+
| `subjectAttributes` (map) | A free-form map of metadata about the subject. These attributes are only logged if passed to the SDK assignment function | `{ "country": "US" }` |
280+
281+
:::note
282+
More details about logging and examples (with Segment, Rudderstack, mParticle, and Snowplow) can be found in the [event logging](/sdks/event-logging/) page.
283+
:::
284+

0 commit comments

Comments
 (0)