diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 63676d1..fa5333f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,4 +23,4 @@ jobs: - name: Build run: swift build -v - name: Run unit tests - run: swift test -v \ No newline at end of file + run: swift test -v diff --git a/Sources/FeaturevisorSDK/Instance+Refresh.swift b/Sources/FeaturevisorSDK/Instance+Refresh.swift index 935f199..2d46111 100644 --- a/Sources/FeaturevisorSDK/Instance+Refresh.swift +++ b/Sources/FeaturevisorSDK/Instance+Refresh.swift @@ -52,53 +52,47 @@ extension FeaturevisorInstance { } public func startRefreshing() { + refreshQueue.async { [weak self] in + guard let self else { return } - guard datafileUrl != nil else { - logger.error("cannot start refreshing since `datafileUrl` is not provided") - return - } + guard self.datafileUrl != nil else { + self.logger.error("cannot start refreshing since `datafileUrl` is not provided") + return + } - guard timer == nil else { - logger.warn("refreshing has already started") - return - } + guard self.refreshTimer == nil else { + self.logger.warn("refreshing has already started") + return + } - guard let refreshInterval else { - logger.warn("no `refreshInterval` option provided") - return - } + guard let refreshInterval = self.refreshInterval else { + self.logger.warn("no `refreshInterval` option provided") + return + } - DispatchQueue.global() - .async { [weak self] in - self?.timer = Timer.scheduledTimer( - withTimeInterval: TimeInterval(refreshInterval), - repeats: true - ) { _ in - self?.refresh() - } - - if let timer = self?.timer { - RunLoop.current.add(timer, forMode: .common) - RunLoop.current.run() - } + let timer = DispatchSource.makeTimerSource(queue: self.refreshQueue) + timer.schedule(deadline: .now() + refreshInterval, repeating: refreshInterval) + timer.setEventHandler { [weak self] in + self?.refresh() } + timer.resume() + + self.refreshTimer = timer + } } public func stopRefreshing() { + refreshQueue.async { [weak self] in + guard let self else { return } - DispatchQueue.global() - .async { [weak self] in - - guard let intervalId = self?.timer else { - self?.logger.warn("refreshing has not started yet") - return - } - - intervalId.invalidate() - self?.timer = nil + guard let timer = self.refreshTimer else { + self.logger.warn("refreshing has not started yet") + return } - logger.warn("refreshing has stopped") + timer.cancel() + self.refreshTimer = nil + self.logger.warn("refreshing has stopped") + } } - } diff --git a/Sources/FeaturevisorSDK/Instance.swift b/Sources/FeaturevisorSDK/Instance.swift index 10f8b3d..92e08e7 100644 --- a/Sources/FeaturevisorSDK/Instance.swift +++ b/Sources/FeaturevisorSDK/Instance.swift @@ -185,7 +185,8 @@ public class FeaturevisorInstance { internal var stickyFeatures: StickyFeatures? // internally created - internal var timer: Timer? + internal let refreshQueue = DispatchQueue(label: "com.featurevisor.datafile.refresh") + internal var refreshTimer: DispatchSourceTimer? internal var datafileReader: DatafileReader internal var emitter: Emitter internal var statuses: Statuses diff --git a/Tests/FeaturevisorSDKTests/InstanceTests.swift b/Tests/FeaturevisorSDKTests/InstanceTests.swift index 47182da..454e57e 100644 --- a/Tests/FeaturevisorSDKTests/InstanceTests.swift +++ b/Tests/FeaturevisorSDKTests/InstanceTests.swift @@ -986,10 +986,12 @@ class FeaturevisorInstanceTests: XCTestCase { func testShouldStopRefreshing() { // GIVEN - var isRefreshingStopped = false var revision = 1 let refreshInterval = 1.0 + let readyExpectation = expectation(description: "sdk_ready") + let stoppedExpectation = expectation(description: "refreshing_stopped") + MockURLProtocol.requestHandler = { request in let jsonString = "{\"schemaVersion\":\"1\",\"revision\":\"\(revision)\",\"attributes\":[],\"segments\":[],\"features\":[]}" @@ -1007,25 +1009,27 @@ class FeaturevisorInstanceTests: XCTestCase { options.sessionConfiguration.protocolClasses = [MockURLProtocol.self] options.refreshInterval = refreshInterval options.datafileUrl = "https://featurevisor-awesome-url.com/tags.json" + options.onReady = { _ in + readyExpectation.fulfill() + } options.logger = createLogger { level, message, details in guard case .warn = level else { return } if message.contains("refreshing has stopped") { - isRefreshingStopped = true + stoppedExpectation.fulfill() } } // WHEN let sdk = try! createInstance(options: options) - - XCTAssertEqual(isRefreshingStopped, false) + wait(for: [readyExpectation], timeout: 1.0) sdk.stopRefreshing() // THEN - XCTAssertEqual(isRefreshingStopped, true) + wait(for: [stoppedExpectation], timeout: 1.0) } func testSetDatafileByValidJSON() {