DispatchTimer.swift 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import Combine
  2. import Foundation
  3. class DispatchTimer {
  4. let timeInterval: TimeInterval
  5. let queue: DispatchQueue
  6. private let subject = PassthroughSubject<Date, Never>()
  7. init(
  8. timeInterval: TimeInterval,
  9. queue: DispatchQueue = DispatchQueue.markedQueue(label: "DispatchTimer.queue", qos: .userInteractive)
  10. ) {
  11. self.timeInterval = timeInterval
  12. self.queue = queue
  13. }
  14. private lazy var timer: DispatchSourceTimer = {
  15. let timer = DispatchSource.makeTimerSource(queue: queue)
  16. timer.schedule(deadline: .now() + timeInterval, repeating: timeInterval)
  17. timer.setEventHandler(handler: { [weak self] in
  18. self?.fire()
  19. })
  20. return timer
  21. }()
  22. func fire() {
  23. subject.send(Date())
  24. eventHandler?()
  25. }
  26. var eventHandler: (() -> Void)?
  27. private enum State {
  28. case suspended
  29. case resumed
  30. }
  31. private var state: State = .suspended
  32. func resume() {
  33. if state == .resumed {
  34. return
  35. }
  36. state = .resumed
  37. timer.resume()
  38. }
  39. func suspend() {
  40. if state == .suspended {
  41. return
  42. }
  43. state = .suspended
  44. timer.suspend()
  45. }
  46. var publisher: AnyPublisher<Date, Never> {
  47. subject.eraseToAnyPublisher()
  48. }
  49. deinit {
  50. timer.setEventHandler {}
  51. timer.cancel()
  52. /*
  53. If the timer is suspended, calling cancel without resuming
  54. triggers a crash. This is documented here
  55. https://forums.developer.apple.com/thread/15902
  56. */
  57. resume()
  58. eventHandler = nil
  59. subject.send(completion: .finished)
  60. }
  61. }