-
Notifications
You must be signed in to change notification settings - Fork 295
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Fix Constraints Error in `PayPalWebCheckoutViewController` (#1342) * fix constraints warning by adding leading and trailing constraints for nested stackViews * remove print statement * Add RepeatingTimer class * Setup default timeInterval value on RepeatingTimer * Refactor BTAnalyticsService * Add RepeatingTimer Tests * Move RepeatingTimer files * Fix UT * Rename UTs * Remove default value on RepeatingTimer init * Address PR comment * Fix tests * Address PR comments * Update CHANGELOG * Update docstring * Remove unnecessary test * PR Feedback - move RepeatingTimer into Analytics/ directory * Add Pragma marks to RepeatingTimer --------- Co-authored-by: Jax DesMarais-Leder <[email protected]> Co-authored-by: Sammy Cannillo <[email protected]>
- Loading branch information
1 parent
80e324c
commit 5b14e06
Showing
6 changed files
with
161 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import Foundation | ||
|
||
final class RepeatingTimer { | ||
|
||
// MARK: - Private Properties | ||
|
||
/// Amount of time, in seconds. | ||
private let timeInterval: Int | ||
|
||
private lazy var timer: DispatchSourceTimer = { | ||
let timerSource = DispatchSource.makeTimerSource(queue: DispatchQueue.main) | ||
timerSource.schedule( | ||
deadline: .now() + .seconds(timeInterval), | ||
repeating: .seconds(timeInterval), | ||
leeway: .seconds(1) | ||
) | ||
timerSource.setEventHandler { [weak self] in | ||
self?.eventHandler?() | ||
} | ||
return timerSource | ||
}() | ||
|
||
private var state: State = .suspended | ||
|
||
private enum State { | ||
case suspended | ||
case resumed | ||
} | ||
|
||
// MARK: - Internal Properties | ||
|
||
var eventHandler: (() -> Void)? | ||
|
||
// MARK: - Initializer | ||
|
||
init(timeInterval: Int) { | ||
self.timeInterval = timeInterval | ||
} | ||
|
||
deinit { | ||
timer.setEventHandler { } | ||
timer.cancel() | ||
// If the timer is suspended, calling cancel without resuming afterwards | ||
// triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902 | ||
resume() | ||
eventHandler = nil | ||
} | ||
|
||
// MARK: - Internal Methods | ||
|
||
/* | ||
GCD timers are sensitive to errors. It is crucial to maintain | ||
balance between calls to `dispatch_suspend` and `dispatch_resume`. Failure to do so | ||
results in crashes with errors similar to: | ||
|
||
BUG IN CLIENT OF LIBDISPATCH: Over-resume of an object | ||
|
||
Such errors indicate an attempt to resume an already resumed timer. According to the | ||
documentation, each call to `dispatch_suspend` must be matched with a corresponding | ||
call to `dispatch_resume` to ensure proper event delivery and avoid crashes. | ||
For more information: https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html#//apple_ref/doc/uid/TP40008091-CH103-SW8 | ||
*/ | ||
|
||
func resume() { | ||
guard state != .resumed else { return } | ||
|
||
state = .resumed | ||
timer.resume() | ||
} | ||
|
||
func suspend() { | ||
guard state != .suspended else { return } | ||
|
||
state = .suspended | ||
timer.suspend() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
UnitTests/BraintreeCoreTests/Analytics/RepeatingTimer_Tests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import XCTest | ||
@testable import BraintreeCore | ||
|
||
class RepeatingTimerTests: XCTestCase { | ||
|
||
func testResume_callsEventHandler() { | ||
let sut = RepeatingTimer(timeInterval: 2) | ||
let expectation = expectation(description: "Timer should fire") | ||
var handlerCalled = false | ||
|
||
sut.eventHandler = { | ||
handlerCalled = true | ||
expectation.fulfill() | ||
} | ||
|
||
sut.resume() | ||
waitForExpectations(timeout: 3, handler: nil) | ||
XCTAssertTrue(handlerCalled, "Event handler should be called after timer resumes") | ||
} | ||
|
||
func testSuspend_preventsEventHandler() { | ||
let sut = RepeatingTimer(timeInterval: 2) | ||
let expectation = expectation(description: "Timer should not fire after suspension") | ||
var handlerCalled = false | ||
|
||
sut.eventHandler = { | ||
handlerCalled = true | ||
} | ||
|
||
sut.resume() | ||
sut.suspend() | ||
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { | ||
expectation.fulfill() | ||
} | ||
|
||
waitForExpectations(timeout: 4, handler: nil) | ||
XCTAssertFalse(handlerCalled, "Event handler should not be called after timer is suspended") | ||
} | ||
|
||
func testDeinit() { | ||
var sut: RepeatingTimer? = RepeatingTimer(timeInterval: 1) | ||
sut?.resume() | ||
|
||
addTeardownBlock { | ||
sut?.suspend() | ||
} | ||
|
||
sut = nil | ||
XCTAssertNil(sut, "Timer should be deallocated") | ||
} | ||
} |