@@ -24,6 +24,7 @@ open class OptimizelyClient: NSObject {
2424 // MARK: - Properties
2525
2626 var sdkKey : String
27+
2728 private var atomicConfig : AtomicProperty < ProjectConfig > = AtomicProperty < ProjectConfig > ( )
2829 var config : ProjectConfig ? {
2930 get {
@@ -39,6 +40,14 @@ open class OptimizelyClient: NSObject {
3940 }
4041
4142 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+ }
4251
4352 // MARK: - Customizable Services
4453
@@ -54,8 +63,8 @@ open class OptimizelyClient: NSObject {
5463 return HandlerRegistryService . shared. injectDecisionService ( sdkKey: self . sdkKey) !
5564 }
5665
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)
5968 }
6069
6170 public var notificationCenter : OPTNotificationCenter ? {
@@ -106,18 +115,22 @@ open class OptimizelyClient: NSObject {
106115 /// - resourceTimeout: timeout for datafile download (optional)
107116 /// - completion: callback when initialization is completed
108117 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
110119 switch result {
111- case . failure:
112- completion ? ( result)
113120 case . success( let datafile) :
121+ guard let datafile = datafile else {
122+ completion ? ( . failure( . datafileLoadingFailed( self . sdkKey) ) )
123+ return
124+ }
125+
114126 do {
115127 try self . configSDK ( datafile: datafile)
116-
117- completion ? ( result)
128+ completion ? ( . success( datafile) )
118129 } catch {
119130 completion ? ( . failure( error as! OptimizelyError ) )
120131 }
132+ case . failure( let error) :
133+ completion ? ( . failure( error) )
121134 }
122135 }
123136 }
@@ -139,80 +152,76 @@ open class OptimizelyClient: NSObject {
139152 /// - datafile: This datafile will be used when cached copy is not available (fresh start)
140153 /// A cached copy from previous download is used if it's available.
141154 /// 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.
142158 /// - doFetchDatafileBackground: This is for debugging purposes when
143159 /// you don't want to download the datafile. In practice, you should allow the
144160 /// 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)
147165 let selectedDatafile = cachedDatafile ?? datafile
148166
149167 try configSDK ( datafile: selectedDatafile)
150168
151169 // 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+ }
153183 }
154184
155185 func configSDK( datafile: Data ) throws {
156186 do {
157187 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)
179192 }
180- } catch {
193+ } catch let error as OptimizelyError {
181194 // .datafileInvalid
182195 // .datafaileVersionInvalid
183196 // .datafaileLoadingFailed
197+ self . logger. e ( error)
198+ throw error
199+ } catch {
200+ self . logger. e ( error. localizedDescription)
184201 throw error
185202 }
186203 }
187204
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+ }
189209
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
213212 }
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)
214223 }
215-
224+
216225 /**
217226 * Use the activate method to start an experiment.
218227 *
@@ -836,7 +845,7 @@ extension OptimizelyClient {
836845extension OptimizelyClient {
837846
838847 public func close( ) {
839- datafileHandler. stopUpdates ( sdkKey: sdkKey)
848+ datafileHandler? . stopUpdates ( sdkKey: sdkKey)
840849 eventLock. sync { }
841850 eventDispatcher? . close ( )
842851 }
0 commit comments