TaskScheduler.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. // LoopFollow
  2. // TaskScheduler.swift
  3. import Foundation
  4. import UIKit
  5. enum TaskID: CaseIterable {
  6. case profile
  7. case deviceStatus
  8. case treatments
  9. case fetchBG
  10. case minAgoUpdate
  11. case calendarWrite
  12. case alarmCheck
  13. }
  14. struct ScheduledTask {
  15. var nextRun: Date
  16. var action: () -> Void
  17. }
  18. class TaskScheduler {
  19. static let shared = TaskScheduler()
  20. private let queue = DispatchQueue(label: "com.LoopFollow.TaskSchedulerQueue")
  21. private var tasks: [TaskID: ScheduledTask] = [:]
  22. private var currentTimer: DispatchSourceTimer?
  23. private init() {}
  24. // MARK: - Public API
  25. func scheduleTask(id: TaskID, nextRun: Date, action: @escaping () -> Void) {
  26. queue.async {
  27. let timeString = self.formatTime(nextRun)
  28. LogManager.shared.log(category: .taskScheduler, message: "scheduleTask(\(id)): next run = \(timeString)", isDebug: true)
  29. self.tasks[id] = ScheduledTask(nextRun: nextRun, action: action)
  30. self.rescheduleTimer()
  31. }
  32. }
  33. func rescheduleTask(id: TaskID, to newRunDate: Date) {
  34. // let timeString = formatTime(newRunDate)
  35. // LogManager.shared.log(category: .taskScheduler, message: "Reschedule Task \(id): next run = \(timeString)", isDebug: true)
  36. queue.async {
  37. guard var existingTask = self.tasks[id] else { return }
  38. existingTask.nextRun = newRunDate
  39. self.tasks[id] = existingTask
  40. self.checkTasksNow()
  41. }
  42. }
  43. func checkTasksNow() {
  44. queue.async {
  45. self.fireOverdueTasks()
  46. self.rescheduleTimer()
  47. }
  48. }
  49. // MARK: - Private
  50. private func rescheduleTimer() {
  51. currentTimer?.cancel()
  52. currentTimer = nil
  53. guard let (_, earliestTask) = tasks.min(by: { $0.value.nextRun < $1.value.nextRun }) else {
  54. LogManager.shared.log(category: .taskScheduler, message: "No tasks, no timer scheduled.")
  55. return
  56. }
  57. let interval = earliestTask.nextRun.timeIntervalSinceNow
  58. let safeInterval = max(interval, 0)
  59. let timer = DispatchSource.makeTimerSource(queue: queue)
  60. timer.schedule(deadline: .now() + safeInterval)
  61. timer.setEventHandler { [weak self] in
  62. guard let self = self else { return }
  63. self.fireOverdueTasks()
  64. self.rescheduleTimer()
  65. }
  66. currentTimer = timer
  67. timer.resume()
  68. }
  69. private func fireOverdueTasks() {
  70. BackgroundAlertManager.shared.scheduleBackgroundAlert()
  71. let now = Date()
  72. for taskID in TaskID.allCases {
  73. guard let task = tasks[taskID], task.nextRun <= now else {
  74. continue
  75. }
  76. var updatedTask = task
  77. updatedTask.nextRun = .distantFuture
  78. tasks[taskID] = updatedTask
  79. // LogManager.shared.log(category: .taskScheduler, message: "Executing Task \(taskID)", isDebug: true)
  80. DispatchQueue.main.async {
  81. task.action()
  82. }
  83. }
  84. }
  85. private func formatTime(_ date: Date) -> String {
  86. let formatter = DateFormatter()
  87. formatter.dateStyle = .none
  88. formatter.timeStyle = .medium
  89. return formatter.string(from: date)
  90. }
  91. }