Skip to content

Commit 55484cc

Browse files
authored
fix: remove accumulated polling when app comes back to foreground (#301)
1 parent 2b0dd1e commit 55484cc

File tree

2 files changed

+85
-20
lines changed

2 files changed

+85
-20
lines changed

Sources/Implementation/DefaultDatafileHandler.swift

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -168,18 +168,6 @@ class DefaultDatafileHandler: OPTDatafileHandler {
168168
}
169169
}
170170

171-
@objc
172-
func timerFired(timer: Timer) {
173-
if let info = timer.userInfo as? [String: Any],
174-
let sdkKey = info["sdkKey"] as? String,
175-
let updateInterval = info["updateInterval"] as? Int,
176-
let startDate = info["startTime"] as? Date,
177-
let datafileChangeNotification = info["datafileChangeNotification"] as? ((Data) -> Void) {
178-
self.performPerodicDownload(sdkKey: sdkKey, startTime: startDate, updateInterval: updateInterval, datafileChangeNotification: datafileChangeNotification)
179-
}
180-
timer.invalidate()
181-
}
182-
183171
func hasPeriodUpdates(sdkKey: String) -> Bool {
184172
var restart = true
185173
self.timers.performAtomic(atomicOperation: { (timers) in
@@ -195,6 +183,7 @@ class DefaultDatafileHandler: OPTDatafileHandler {
195183
startTime: Date,
196184
updateInterval: Int,
197185
datafileChangeNotification: ((Data) -> Void)?) {
186+
let beginDownloading = Date()
198187
self.downloadDatafile(sdkKey: sdkKey) { (result) in
199188
switch result {
200189
case .success(let data):
@@ -207,15 +196,18 @@ class DefaultDatafileHandler: OPTDatafileHandler {
207196
}
208197

209198
if self.hasPeriodUpdates(sdkKey: sdkKey) {
210-
let interval = self.timers.property?[sdkKey]?.interval ?? updateInterval
211-
let actualDiff = (Int(abs(startTime.timeIntervalSinceNow)) - updateInterval)
212-
var nextInterval = interval
213-
if actualDiff > 0 {
214-
nextInterval -= actualDiff
199+
// adjust the next fire time so that events will be fired at fixed interval regardless of the download latency
200+
// if latency is too big (or returning from background mode), fire the next event immediately once
201+
202+
var interval = self.timers.property?[sdkKey]?.interval ?? updateInterval
203+
let delay = Int(Date().timeIntervalSince(beginDownloading))
204+
interval -= delay
205+
if interval < 0 {
206+
interval = 0
215207
}
216208

217-
self.logger.d("next datafile download is \(nextInterval) seconds \(Date())")
218-
self.startPeriodicUpdates(sdkKey: sdkKey, updateInterval: nextInterval, datafileChangeNotification: datafileChangeNotification)
209+
self.logger.d("next datafile download is \(interval) seconds \(Date())")
210+
self.startPeriodicUpdates(sdkKey: sdkKey, updateInterval: interval, datafileChangeNotification: datafileChangeNotification)
219211
}
220212
}
221213
}

Tests/OptimizelyTests-Common/DatafileHandlerTests.swift

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,83 @@ class DatafileHandlerTests: XCTestCase {
193193

194194
XCTAssert(count == 10)
195195
XCTAssert(seconds == 10)
196+
}
197+
198+
func testPeriodicDownload_PollingShouldNotBeAccumulatedWhileInBackground() {
199+
class FakeDatafileHandler: DefaultDatafileHandler {
200+
let data = Data()
201+
override func downloadDatafile(sdkKey: String,
202+
returnCacheIfNoChange: Bool,
203+
resourceTimeoutInterval: Double?,
204+
completionHandler: @escaping DatafileDownloadCompletionHandler) {
205+
completionHandler(.success(data))
206+
}
207+
}
208+
209+
let expectation = XCTestExpectation(description: "polling")
210+
let handler = FakeDatafileHandler()
211+
let now = Date()
196212

213+
let updateInterval = 1
214+
let idleTime = 5
215+
var count = 0
216+
var seconds = 0
217+
handler.startPeriodicUpdates(sdkKey: "notrealkey", updateInterval: updateInterval) { _ in
218+
// simulate going to background and coming back to foreground after 5secs
219+
if count == 0 {
220+
sleep(UInt32(idleTime))
221+
}
222+
223+
count += 1
224+
225+
// check if delayed polling not accumulated and completed back-to-back
226+
if count == 5 {
227+
handler.stopPeriodicUpdates()
228+
expectation.fulfill()
229+
seconds = Int(abs(now.timeIntervalSinceNow))
230+
}
231+
}
232+
233+
wait(for: [expectation], timeout: 30)
234+
235+
XCTAssert(seconds >= idleTime + 3) // 3 instead of 5 for tolerating timer inaccuracy
197236
}
198237

238+
func testPeriodicDownload_PollingPeriodAdjustedByDelay() {
239+
class FakeDatafileHandler: DefaultDatafileHandler {
240+
let data = Data()
241+
override func downloadDatafile(sdkKey: String,
242+
returnCacheIfNoChange: Bool,
243+
resourceTimeoutInterval: Double?,
244+
completionHandler: @escaping DatafileDownloadCompletionHandler) {
245+
sleep(1)
246+
completionHandler(.success(data))
247+
}
248+
}
249+
250+
let expectation = XCTestExpectation(description: "polling")
251+
let handler = FakeDatafileHandler()
252+
let now = Date()
253+
254+
let updateInterval = 2
255+
let maxCount = 5
256+
var count = 0
257+
var seconds = 0
258+
handler.startPeriodicUpdates(sdkKey: "notrealkey", updateInterval: updateInterval) { _ in
259+
count += 1
260+
261+
if count == maxCount {
262+
handler.stopPeriodicUpdates()
263+
expectation.fulfill()
264+
seconds = Int(abs(now.timeIntervalSinceNow))
265+
}
266+
}
267+
268+
wait(for: [expectation], timeout: 30)
269+
XCTAssert(seconds <= updateInterval * (maxCount + 1))
270+
}
271+
272+
199273
func testPeriodicDownloadWithOptimizlyClient() {
200274
class FakeDatafileHandler: DefaultDatafileHandler {
201275
let data = OTUtils.loadJSONDatafile("typed_audience_datafile")
@@ -228,7 +302,6 @@ class DatafileHandlerTests: XCTestCase {
228302
wait(for: [expection], timeout: 10)
229303

230304
XCTAssert(count == 9)
231-
232305
}
233306

234307
func testDownloadTimeout() {

0 commit comments

Comments
 (0)