@@ -19,7 +19,7 @@ import Foundation
19
19
class DefaultDatafileHandler : OPTDatafileHandler {
20
20
static public var endPointStringFormat = " https://cdn.optimizely.com/datafiles/%@.json "
21
21
lazy var logger = HandlerRegistryService . shared. injectLogger ( )
22
- var timers : [ String : Timer ] = [ String: Timer] ( )
22
+ var timers : AtomicProperty < [ String : Timer ] > = AtomicProperty ( property : [ String: Timer] ( ) )
23
23
let dataStore = DataStoreUserDefaults ( )
24
24
25
25
required init ( ) {
@@ -28,35 +28,34 @@ class DefaultDatafileHandler : OPTDatafileHandler {
28
28
29
29
func downloadDatafile( sdkKey: String ) -> Data ? {
30
30
31
- let config = URLSessionConfiguration . ephemeral
32
- let session = URLSession ( configuration: config)
33
- let str = String ( format: DefaultDatafileHandler . endPointStringFormat, sdkKey)
34
- var result : Data ?
31
+ var datafile : Data ?
35
32
let group = DispatchGroup ( )
36
33
37
34
group. enter ( )
38
35
39
- if let url = URL ( string: str) {
40
- let task = session. downloadTask ( with: url) { ( url, response, error) in
41
- self . logger? . log ( level: . debug, message: response. debugDescription)
42
- if let response = response as? HTTPURLResponse , response. statusCode == 200 ,
43
- let url = url, let projectConfig = try ? Data ( contentsOf: url) {
44
- result = projectConfig
45
- self . saveDatafile ( sdkKey: sdkKey, dataFile: projectConfig)
46
- }
47
- group. leave ( )
36
+ downloadDatafile ( sdkKey: sdkKey) { ( result) in
37
+ switch result {
38
+ case . success( let data) :
39
+ datafile = data
40
+ case . failure( let error) :
41
+ self . logger? . log ( level: . error, message: error. localizedDescription)
48
42
}
49
-
50
- task. resume ( )
51
-
52
- group. wait ( )
53
-
43
+ group. leave ( )
54
44
}
55
- return result
45
+
46
+ group. wait ( )
47
+
48
+ return datafile
56
49
}
57
50
58
- open func downloadDatafile( sdkKey: String , completionHandler: @escaping ( Result < Data ? , DatafileDownloadError > ) -> Void ) {
51
+ open func downloadDatafile( sdkKey: String ,
52
+ resourceTimeoutInterval: Double ? = nil ,
53
+ completionHandler: @escaping DatafileDownloadCompletionHandler ) {
59
54
let config = URLSessionConfiguration . ephemeral
55
+ if let resourceTimeoutInterval = resourceTimeoutInterval,
56
+ resourceTimeoutInterval > 0 {
57
+ config. timeoutIntervalForResource = TimeInterval ( resourceTimeoutInterval)
58
+ }
60
59
let session = URLSession ( configuration: config)
61
60
let str = String ( format: DefaultDatafileHandler . endPointStringFormat, sdkKey)
62
61
if let url = URL ( string: str) {
@@ -106,49 +105,112 @@ class DefaultDatafileHandler : OPTDatafileHandler {
106
105
}
107
106
108
107
func startPeriodicUpdates( sdkKey: String , updateInterval: Int , datafileChangeNotification: ( ( Data ) -> Void ) ? ) {
109
- if let _ = timers [ sdkKey] {
110
- logger? . log ( level: . info, message: " Timer already started for datafile updates " )
111
- return
112
- }
108
+
109
+ let now = Date ( )
113
110
if #available( iOS 10 . 0 , tvOS 10 . 0 , * ) {
114
111
DispatchQueue . main. async {
115
- let timer = Timer . scheduledTimer ( withTimeInterval: TimeInterval ( updateInterval) , repeats: true ) { ( timer) in
116
- self . downloadDatafile ( sdkKey: sdkKey) { ( result) in
117
- if let datafileChangeNotification = datafileChangeNotification {
118
- switch result {
119
- case . success( let data) :
120
- if let data = data {
121
- datafileChangeNotification ( data)
122
- }
123
- case . failure( _) : break
124
- // don't do anything.
125
- }
126
- }
127
- // background download saves to cache
128
- }
112
+ if let timer = self . timers. property ? [ sdkKey] , timer. isValid {
113
+ return
129
114
}
130
- self . timers [ sdkKey] = timer
131
-
115
+
116
+ let timer = Timer . scheduledTimer ( withTimeInterval: TimeInterval ( updateInterval) , repeats: false ) { ( timer) in
117
+
118
+ self . performPerodicDownload ( sdkKey: sdkKey,
119
+ startTime: now,
120
+ updateInterval: updateInterval,
121
+ datafileChangeNotification: datafileChangeNotification)
122
+
123
+ timer. invalidate ( )
124
+ }
125
+ self . timers. performAtomic ( atomicOperation: { ( timers) in
126
+ timers [ sdkKey] = timer
127
+ } )
132
128
}
133
129
} else {
134
130
// Fallback on earlier versions
131
+ DispatchQueue . main. async {
132
+ if let timer = self . timers. property ? [ sdkKey] , timer. isValid {
133
+ return
134
+ }
135
+
136
+ let timer = Timer . scheduledTimer ( timeInterval: TimeInterval ( updateInterval) , target: self , selector: #selector( self . timerFired ( timer: ) ) , userInfo: [ " sdkKey " : sdkKey, " startTime " : Date ( ) , " updateInterval " : updateInterval, " datafileChangeNotification " : datafileChangeNotification ?? { ( data) in } ] , repeats: false )
137
+
138
+ self . timers. performAtomic ( atomicOperation: { ( timers) in
139
+ timers [ sdkKey] = timer
140
+ } )
141
+ }
142
+
135
143
}
136
144
}
137
145
138
- func stopPeriodicUpdates( sdkKey: String ) {
139
- if let timer = timers [ sdkKey] {
140
- logger? . log ( level: . info, message: " Stopping timer for datafile updates sdkKey: " + sdkKey)
146
+ @objc
147
+ func timerFired( timer: Timer ) {
148
+ if let info = timer. userInfo as? [ String : Any ] ,
149
+ let sdkKey = info [ " sdkKey " ] as? String ,
150
+ let updateInterval = info [ " updateInterval " ] as? Int ,
151
+ let startDate = info [ " startDate " ] as? Date ,
152
+ let datafileChangeNotification = info [ " datafileChangeNotification " ] as? ( ( Data ) -> Void ) {
153
+ self . performPerodicDownload ( sdkKey: sdkKey, startTime: startDate, updateInterval: updateInterval, datafileChangeNotification: datafileChangeNotification)
154
+ }
155
+ timer. invalidate ( )
156
+
157
+ }
158
+
159
+ func hasPeriodUpdates( sdkKey: String ) -> Bool {
160
+ var restart = true
161
+ self . timers. performAtomic ( atomicOperation: { ( timers) in
162
+ if !timers. contains ( where: { $0. key == sdkKey} ) {
163
+ restart = false
164
+ }
165
+ } )
166
+
167
+ return restart
168
+ }
169
+
170
+ func performPerodicDownload( sdkKey: String ,
171
+ startTime: Date ,
172
+ updateInterval: Int ,
173
+ datafileChangeNotification: ( ( Data ) -> Void ) ? ) {
174
+ self . downloadDatafile ( sdkKey: sdkKey) { ( result) in
175
+ switch result {
176
+ case . success( let data) :
177
+ if let data = data,
178
+ let datafileChangeNotification = datafileChangeNotification {
179
+ datafileChangeNotification ( data)
180
+ }
181
+ case . failure( let error) :
182
+ self . logger? . log ( level: . error, message: error. localizedDescription)
183
+ }
141
184
142
- timer. invalidate ( )
143
- timers. removeValue ( forKey: sdkKey)
185
+ if self . hasPeriodUpdates ( sdkKey: sdkKey) {
186
+ let minutesSinceFire = startTime. minutesPastSinceNow ( )
187
+ var diff = updateInterval - minutesSinceFire
188
+ if diff < 0 {
189
+ diff = 0
190
+ }
191
+ self . startPeriodicUpdates ( sdkKey: sdkKey, updateInterval: diff, datafileChangeNotification: datafileChangeNotification)
192
+ }
144
193
}
194
+ }
195
+
196
+ func stopPeriodicUpdates( sdkKey: String ) {
197
+ timers. performAtomic { ( timers) in
198
+ if let timer = timers [ sdkKey] {
199
+ logger? . log ( level: . info, message: " Stopping timer for datafile updates sdkKey: \( sdkKey) " )
200
+
201
+ timer. invalidate ( )
202
+ timers. removeValue ( forKey: sdkKey)
203
+ }
145
204
205
+ }
146
206
}
147
207
148
208
func stopPeriodicUpdates( ) {
149
- for key in timers. keys {
150
- logger? . log ( level: . info, message: " Stopping timer for all datafile updates " )
151
- stopPeriodicUpdates ( sdkKey: key)
209
+ timers. performAtomic { ( timers) in
210
+ for key in timers. keys {
211
+ logger? . log ( level: . info, message: " Stopping timer for all datafile updates " )
212
+ stopPeriodicUpdates ( sdkKey: key)
213
+ }
152
214
}
153
215
154
216
}
@@ -205,6 +267,4 @@ class DefaultDatafileHandler : OPTDatafileHandler {
205
267
}
206
268
207
269
}
208
-
209
-
210
270
}
0 commit comments