@@ -24,6 +24,7 @@ open class OptimizelyClient: NSObject {
24
24
// MARK: - Properties
25
25
26
26
var sdkKey : String
27
+
27
28
private var atomicConfig : AtomicProperty < ProjectConfig > = AtomicProperty < ProjectConfig > ( )
28
29
var config : ProjectConfig ? {
29
30
get {
@@ -39,6 +40,14 @@ open class OptimizelyClient: NSObject {
39
40
}
40
41
41
42
let eventLock = DispatchQueue ( label: " com.optimizely.client " )
43
+
44
+ private var isPeriodicPollingEnabled : Bool {
45
+ if let handler = datafileHandler as? DefaultDatafileHandler {
46
+ return handler. hasPeriodUpdates ( sdkKey: sdkKey)
47
+ } else {
48
+ return false
49
+ }
50
+ }
42
51
43
52
// MARK: - Customizable Services
44
53
@@ -54,8 +63,8 @@ open class OptimizelyClient: NSObject {
54
63
return HandlerRegistryService . shared. injectDecisionService ( sdkKey: self . sdkKey) !
55
64
}
56
65
57
- public var datafileHandler : OPTDatafileHandler {
58
- return HandlerRegistryService . shared. injectDatafileHandler ( sdkKey: self . sdkKey) !
66
+ public var datafileHandler : OPTDatafileHandler ? {
67
+ return HandlerRegistryService . shared. injectDatafileHandler ( sdkKey: self . sdkKey)
59
68
}
60
69
61
70
public var notificationCenter : OPTNotificationCenter ? {
@@ -106,18 +115,22 @@ open class OptimizelyClient: NSObject {
106
115
/// - resourceTimeout: timeout for datafile download (optional)
107
116
/// - completion: callback when initialization is completed
108
117
public func start( resourceTimeout: Double ? = nil , completion: ( ( OptimizelyResult < Data > ) -> Void ) ? = nil ) {
109
- fetchDatafileBackground ( resourceTimeout : resourceTimeout ) { result in
118
+ datafileHandler ? . downloadDatafile ( sdkKey : sdkKey , returnCacheIfNoChange : true ) { result in
110
119
switch result {
111
- case . failure:
112
- completion ? ( result)
113
120
case . success( let datafile) :
121
+ guard let datafile = datafile else {
122
+ completion ? ( . failure( . datafileLoadingFailed( self . sdkKey) ) )
123
+ return
124
+ }
125
+
114
126
do {
115
127
try self . configSDK ( datafile: datafile)
116
-
117
- completion ? ( result)
128
+ completion ? ( . success( datafile) )
118
129
} catch {
119
130
completion ? ( . failure( error as! OptimizelyError ) )
120
131
}
132
+ case . failure( let error) :
133
+ completion ? ( . failure( error) )
121
134
}
122
135
}
123
136
}
@@ -139,80 +152,76 @@ open class OptimizelyClient: NSObject {
139
152
/// - datafile: This datafile will be used when cached copy is not available (fresh start)
140
153
/// A cached copy from previous download is used if it's available.
141
154
/// The datafile will be updated from the server in the background thread.
155
+ /// - doUpdateConfigOnNewDatafile: When a new datafile is fetched from the server in the background thread,
156
+ /// the SDK will be updated with the new datafile immediately if this value is set to true.
157
+ /// When it's set to false (default), the new datafile is cached and will be used when the SDK is started again.
142
158
/// - doFetchDatafileBackground: This is for debugging purposes when
143
159
/// you don't want to download the datafile. In practice, you should allow the
144
160
/// background thread to update the cache copy (optional)
145
- public func start( datafile: Data , doFetchDatafileBackground: Bool = true ) throws {
146
- let cachedDatafile = self . datafileHandler. loadSavedDatafile ( sdkKey: self . sdkKey)
161
+ public func start( datafile: Data ,
162
+ doUpdateConfigOnNewDatafile: Bool = false ,
163
+ doFetchDatafileBackground: Bool = true ) throws {
164
+ let cachedDatafile = datafileHandler? . loadSavedDatafile ( sdkKey: self . sdkKey)
147
165
let selectedDatafile = cachedDatafile ?? datafile
148
166
149
167
try configSDK ( datafile: selectedDatafile)
150
168
151
169
// continue to fetch updated datafile from the server in background and cache it for next sessions
152
- if doFetchDatafileBackground { fetchDatafileBackground ( ) }
170
+
171
+ if !doFetchDatafileBackground { return }
172
+
173
+ datafileHandler? . downloadDatafile ( sdkKey: sdkKey, returnCacheIfNoChange: false ) { result in
174
+ // override to update always if periodic datafile polling is enabled
175
+ // this is necessary for the case that the first cache download gets the updated datafile
176
+ guard doUpdateConfigOnNewDatafile || self . isPeriodicPollingEnabled else { return }
177
+
178
+ if case . success( let data) = result, let datafile = data {
179
+ // new datafile came in
180
+ self . updateConfigFromBackgroundFetch ( data: datafile)
181
+ }
182
+ }
153
183
}
154
184
155
185
func configSDK( datafile: Data ) throws {
156
186
do {
157
187
self . config = try ProjectConfig ( datafile: datafile)
158
-
159
- datafileHandler. startUpdates ( sdkKey: self . sdkKey) { data in
160
- // new datafile came in...
161
- if let config = try ? ProjectConfig ( datafile: data) {
162
- do {
163
- if let users = self . config? . whitelistUsers {
164
- config. whitelistUsers = users
165
- }
166
-
167
- self . config = config
168
-
169
- // call reinit on the services we know we are reinitializing.
170
-
171
- for component in HandlerRegistryService . shared. lookupComponents ( sdkKey: self . sdkKey) ?? [ ] {
172
- HandlerRegistryService . shared. reInitializeComponent ( service: component, sdkKey: self . sdkKey)
173
- }
174
-
175
- }
176
-
177
- self . sendDatafileChangeNotification ( data: data)
178
- }
188
+
189
+ datafileHandler? . startUpdates ( sdkKey: self . sdkKey) { data in
190
+ // new datafile came in
191
+ self . updateConfigFromBackgroundFetch ( data: data)
179
192
}
180
- } catch {
193
+ } catch let error as OptimizelyError {
181
194
// .datafileInvalid
182
195
// .datafaileVersionInvalid
183
196
// .datafaileLoadingFailed
197
+ self . logger. e ( error)
198
+ throw error
199
+ } catch {
200
+ self . logger. e ( error. localizedDescription)
184
201
throw error
185
202
}
186
203
}
187
204
188
- func fetchDatafileBackground( resourceTimeout: Double ? = nil , completion: ( ( OptimizelyResult < Data > ) -> Void ) ? = nil ) {
205
+ func updateConfigFromBackgroundFetch( data: Data ) {
206
+ guard let config = try ? ProjectConfig ( datafile: data) else {
207
+ return
208
+ }
189
209
190
- datafileHandler. downloadDatafile ( sdkKey: self . sdkKey, resourceTimeoutInterval: resourceTimeout) { result in
191
- var fetchResult : OptimizelyResult < Data >
192
-
193
- switch result {
194
- case . failure( let error) :
195
- fetchResult = . failure( error)
196
- case . success( let datafile) :
197
- // we got a new datafile.
198
- if let datafile = datafile {
199
- fetchResult = . success( datafile)
200
- }
201
- // we got a success but no datafile 304. So, load the saved datafile.
202
- else if let data = self . datafileHandler. loadSavedDatafile ( sdkKey: self . sdkKey) {
203
- fetchResult = . success( data)
204
- }
205
- // if that fails, we have a problem.
206
- else {
207
- fetchResult = . failure( . datafileLoadingFailed( self . sdkKey) )
208
- }
209
-
210
- }
211
-
212
- completion ? ( fetchResult)
210
+ if let users = self . config? . whitelistUsers {
211
+ config. whitelistUsers = users
213
212
}
213
+
214
+ self . config = config
215
+
216
+ // call reinit on the services we know we are reinitializing.
217
+
218
+ for component in HandlerRegistryService . shared. lookupComponents ( sdkKey: self . sdkKey) ?? [ ] {
219
+ HandlerRegistryService . shared. reInitializeComponent ( service: component, sdkKey: self . sdkKey)
220
+ }
221
+
222
+ self . sendDatafileChangeNotification ( data: data)
214
223
}
215
-
224
+
216
225
/**
217
226
* Use the activate method to start an experiment.
218
227
*
@@ -836,7 +845,7 @@ extension OptimizelyClient {
836
845
extension OptimizelyClient {
837
846
838
847
public func close( ) {
839
- datafileHandler. stopUpdates ( sdkKey: sdkKey)
848
+ datafileHandler? . stopUpdates ( sdkKey: sdkKey)
840
849
eventLock. sync { }
841
850
eventDispatcher? . close ( )
842
851
}
0 commit comments