Skip to content

Commit 7a52c97

Browse files
authored
[FSSDK-8554] add blocking version of fetchQualifiedSegments (#489)
- blocking version of fetchQualifiedSegments is added (convenient with asynchronous OptimizelyClient start) - clean up non-blocking version of fetchQualifiedSegments (remove segments in completion handler)
1 parent d55ee3f commit 7a52c97

File tree

3 files changed

+137
-25
lines changed

3 files changed

+137
-25
lines changed

DemoSwiftApp/Samples/SamplesForAPI.swift

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ class SamplesForAPI {
264264
// MARK: - AudienceSegments
265265

266266
static func checkAudienceSegments(optimizely: OptimizelyClient) {
267+
blockingFetchSegments()
268+
nonBlockingFetchSegments()
269+
}
270+
271+
static func blockingFetchSegments() {
267272
// override the default handler if cache size and timeout need to be customized
268273
let optimizely = OptimizelyClient(sdkKey: "VivZyCGPHY369D4z8T9yG", // odp-test
269274
periodicDownloadInterval: 60,
@@ -275,18 +280,47 @@ class SamplesForAPI {
275280
}
276281

277282
let user = optimizely.createUserContext(userId: "user_123", attributes: ["location": "NY"])
278-
user.fetchQualifiedSegments(options: [.ignoreCache]) { _, error in
279-
guard error == nil else {
280-
print("[AudienceSegments] \(error!.errorDescription!)")
281-
return
282-
}
283283

284+
do {
285+
// this will block the calling thread until fetch is completed.
286+
try user.fetchQualifiedSegments(options: [.ignoreCache])
287+
284288
let decision = user.decide(key: "show_coupon", options: [.includeReasons])
285289
print("[AudienceSegments] decision: \(decision)")
290+
} catch OptimizelyError.invalidSegmentIdentifier {
291+
print("[AudienceSegments] audience segments fetch failed (user id is not registered yet or invalid)")
292+
} catch {
293+
print("[AudienceSegments] \(error)")
286294
}
287295
}
288296
}
289297

298+
static func nonBlockingFetchSegments() {
299+
// override the default handler if cache size and timeout need to be customized
300+
let optimizely = OptimizelyClient(sdkKey: "VivZyCGPHY369D4z8T9yG", // odp-test
301+
periodicDownloadInterval: 60,
302+
defaultLogLevel: .debug)
303+
304+
guard let localDatafileUrl = Bundle.main.url(forResource: "demoTestDatafile", withExtension: "json"),
305+
let localDatafile = try? Data(contentsOf: localDatafileUrl)
306+
else {
307+
fatalError("Local datafile cannot be found")
308+
}
309+
310+
try? optimizely.start(datafile: localDatafile)
311+
312+
let user = optimizely.createUserContext(userId: "user_123", attributes: ["location": "NY"])
313+
user.fetchQualifiedSegments(options: [.ignoreCache]) { error in
314+
guard error == nil else {
315+
print("[AudienceSegments] \(error!)")
316+
return
317+
}
318+
319+
let decision = user.decide(key: "show_coupon", options: [.includeReasons])
320+
print("[AudienceSegments] decision: \(decision)")
321+
}
322+
}
323+
290324
// MARK: - Initializations
291325

292326
static func samplesForInitialization() {

Sources/Optimizely+Decide/OptimizelyUserContext.swift

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public class OptimizelyUserContext {
181181

182182
extension OptimizelyUserContext {
183183

184-
/// Fetch all qualified segments for the user context.
184+
/// Fetch (non-blocking) all qualified segments for the user context.
185185
///
186186
/// The segments fetched will be saved in **qualifiedSegments** and can be accessed any time.
187187
/// On failure, **qualifiedSegments** will be nil and one of these errors will be returned:
@@ -190,30 +190,53 @@ extension OptimizelyUserContext {
190190
///
191191
/// - Parameters:
192192
/// - options: A set of options for fetching qualified segments (optional).
193-
/// - completionHandler: A completion handler to be called with the fetch result. On success, it'll pass a non-nil segments array (can be empty) with a nil error. On failure, it'll pass a non-nil error with a nil segments array.
193+
/// - completionHandler: A completion handler to be called with the fetch result. On success, it'll pass a nil error. On failure, it'll pass a non-nil error .
194194
public func fetchQualifiedSegments(options: [OptimizelySegmentOption] = [],
195-
completionHandler: @escaping ([String]?, OptimizelyError?) -> Void) {
195+
completionHandler: @escaping (OptimizelyError?) -> Void) {
196196
// on failure, qualifiedSegments should be reset if a previous value exists.
197197
self.atomicQualifiedSegments.property = nil
198198

199199
guard let optimizely = self.optimizely else {
200-
completionHandler(nil, .sdkNotReady)
200+
completionHandler(.sdkNotReady)
201201
return
202202
}
203203

204204
optimizely.fetchQualifiedSegments(userId: userId, options: options) { segments, err in
205205
guard err == nil, let segments = segments else {
206206
let error = err ?? OptimizelyError.fetchSegmentsFailed("invalid segments")
207207
self.logger.e(error)
208-
completionHandler(nil, error)
208+
completionHandler(error)
209209
return
210210
}
211211

212212
self.atomicQualifiedSegments.property = segments
213-
completionHandler(segments, nil)
213+
completionHandler(nil)
214214
}
215215
}
216216

217+
/// Fetch (blocking) all qualified segments for the user context.
218+
///
219+
/// Note that this call will block the calling thread until fetching is completed.
220+
/// The segments fetched will be saved in **qualifiedSegments** and can be accessed any time.
221+
/// On failure, **qualifiedSegments** will be nil and one of these errors will be thrown:
222+
/// - OptimizelyError.invalidSegmentIdentifier
223+
/// - OptimizelyError.fetchSegmentsFailed(String)
224+
///
225+
/// - Parameters:
226+
/// - options: A set of options for fetching qualified segments (optional).
227+
public func fetchQualifiedSegments(options: [OptimizelySegmentOption] = []) throws {
228+
var error: OptimizelyError?
229+
230+
let semaphore = DispatchSemaphore(value: 0)
231+
fetchQualifiedSegments(options: options) { asyncError in
232+
error = asyncError
233+
semaphore.signal()
234+
}
235+
semaphore.wait()
236+
237+
if let err = error { throw err }
238+
}
239+
217240
/// Check if the user is qualified for the given segment.
218241
///
219242
/// - Parameter segment: the segment name to check qualification for.

Tests/OptimizelyTests-Common/OptimizelyUserContextTests_ODP.swift

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ class OptimizelyUserContextTests_ODP: XCTestCase {
7070
user.qualifiedSegments = []
7171
XCTAssertFalse(user.isQualifiedFor(segment: "a"))
7272
}
73+
74+
}
75+
76+
// MARK: - fetchQualifiedSegments (non-blocking)
77+
78+
extension OptimizelyUserContextTests_ODP {
7379

7480
// MARK: - Success
7581

@@ -78,10 +84,9 @@ class OptimizelyUserContextTests_ODP: XCTestCase {
7884
user = optimizely.createUserContext(userId: kUserId)
7985

8086
let sem = DispatchSemaphore(value: 0)
81-
user.fetchQualifiedSegments { segments, error in
87+
user.fetchQualifiedSegments { error in
8288
XCTAssertNil(error)
83-
XCTAssertEqual(["odp-segment-1"], segments)
84-
XCTAssertEqual(self.user.qualifiedSegments, segments)
89+
XCTAssertEqual(self.user.qualifiedSegments, ["odp-segment-1"])
8590
sem.signal()
8691
}
8792
XCTAssertEqual(.success, sem.wait(timeout: .now() + .seconds(3)))
@@ -95,9 +100,8 @@ class OptimizelyUserContextTests_ODP: XCTestCase {
95100
user.qualifiedSegments = ["dummy"]
96101

97102
let sem = DispatchSemaphore(value: 0)
98-
user.fetchQualifiedSegments { segments, error in
103+
user.fetchQualifiedSegments { error in
99104
XCTAssertEqual(OptimizelyError.sdkNotReady.reason, error?.reason)
100-
XCTAssertNil(segments)
101105
XCTAssertNil(self.user.qualifiedSegments)
102106
sem.signal()
103107
}
@@ -111,9 +115,8 @@ class OptimizelyUserContextTests_ODP: XCTestCase {
111115
// ODP apiKey is not available
112116

113117
let sem = DispatchSemaphore(value: 0)
114-
user.fetchQualifiedSegments { segments, error in
118+
user.fetchQualifiedSegments { error in
115119
XCTAssertNotNil(error)
116-
XCTAssertNil(segments)
117120
XCTAssertNil(self.user.qualifiedSegments)
118121
sem.signal()
119122
}
@@ -127,7 +130,7 @@ class OptimizelyUserContextTests_ODP: XCTestCase {
127130
user = optimizely.createUserContext(userId: kUserId)
128131

129132
let sem = DispatchSemaphore(value: 0)
130-
user.fetchQualifiedSegments { _, _ in
133+
user.fetchQualifiedSegments { _ in
131134
sem.signal()
132135
}
133136
XCTAssertEqual(.success, sem.wait(timeout: .now() + .seconds(3)))
@@ -141,16 +144,68 @@ class OptimizelyUserContextTests_ODP: XCTestCase {
141144
user = optimizely.createUserContext(userId: kUserId)
142145

143146
let sem = DispatchSemaphore(value: 0)
144-
user.fetchQualifiedSegments { segments, error in
147+
user.fetchQualifiedSegments { error in
145148
XCTAssertNil(error)
146-
XCTAssertEqual(segments, [])
147149
sem.signal()
148150
}
149151
XCTAssertEqual(.success, sem.wait(timeout: .now() + .seconds(3)))
150152
}
151153

152154
}
153155

156+
// MARK: - fetchQualifiedSegments (blocking)
157+
158+
extension OptimizelyUserContextTests_ODP {
159+
160+
// MARK: - Success
161+
162+
func testFetchQualifiedSegments_blocking_successDefaultUser() {
163+
try? optimizely.start(datafile: datafile)
164+
user = optimizely.createUserContext(userId: kUserId)
165+
166+
do {
167+
try user.fetchQualifiedSegments()
168+
XCTAssertEqual(user.qualifiedSegments, ["odp-segment-1"])
169+
} catch {
170+
XCTFail(error.localizedDescription)
171+
}
172+
}
173+
174+
// MARK: - Failure
175+
176+
func testFetchQualifiedSegments_blocking_sdkNotReady() {
177+
user = optimizely.createUserContext(userId: kUserId)
178+
user.optimizely = nil
179+
user.qualifiedSegments = ["dummy"]
180+
181+
do {
182+
try user.fetchQualifiedSegments()
183+
XCTFail("expected to fail")
184+
} catch OptimizelyError.sdkNotReady {
185+
XCTAssertNil(user.qualifiedSegments)
186+
} catch {
187+
XCTFail(error.localizedDescription)
188+
}
189+
}
190+
191+
func testFetchQualifiedSegments_blocking_fetchFailed() {
192+
user = optimizely.createUserContext(userId: kUserId)
193+
user.qualifiedSegments = ["dummy"]
194+
195+
// ODP apiKey is not available
196+
197+
do {
198+
try user.fetchQualifiedSegments()
199+
XCTFail("expected to fail")
200+
} catch OptimizelyError.fetchSegmentsFailed {
201+
XCTAssertNil(self.user.qualifiedSegments)
202+
} catch {
203+
XCTFail(error.localizedDescription)
204+
}
205+
}
206+
207+
}
208+
154209
// MARK: - Optional parameters
155210

156211
extension OptimizelyUserContextTests_ODP {
@@ -160,9 +215,9 @@ extension OptimizelyUserContextTests_ODP {
160215
user = optimizely.createUserContext(userId: kUserId)
161216

162217
let sem = DispatchSemaphore(value: 0)
163-
user.fetchQualifiedSegments(options: [.ignoreCache]) { segments, error in
218+
user.fetchQualifiedSegments(options: [.ignoreCache]) { error in
164219
XCTAssertNil(error)
165-
XCTAssertEqual(segments, ["odp-segment-1"])
220+
XCTAssertEqual(self.user.qualifiedSegments, ["odp-segment-1"])
166221
sem.signal()
167222
}
168223
XCTAssertEqual(.success, sem.wait(timeout: .now() + .seconds(3)))
@@ -212,9 +267,9 @@ extension OptimizelyUserContextTests_ODP {
212267
let user = optimizely.createUserContext(userId: testOdpUserId)
213268

214269
let sem = DispatchSemaphore(value: 0)
215-
user.fetchQualifiedSegments { segments, error in
270+
user.fetchQualifiedSegments { error in
216271
XCTAssertNil(error)
217-
XCTAssertEqual([], segments, "none of the test segments in the live ODP server")
272+
XCTAssertEqual([], user.qualifiedSegments, "none of the test segments in the live ODP server")
218273
sem.signal()
219274
}
220275
XCTAssertEqual(.success, sem.wait(timeout: .now() + .seconds(30)))

0 commit comments

Comments
 (0)