-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathSTTimer.swift
More file actions
134 lines (116 loc) · 4.02 KB
/
Copy pathSTTimer.swift
File metadata and controls
134 lines (116 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//
// STTimer.swift
// STBaseProject
//
// Created by 寒江孤影 on 2024/01/01.
//
import Foundation
/// First-fire timing for `STTimer.start`.
public enum STTimerFirstFire: Sendable, Equatable {
/// Fire once immediately on start, then every `interval`.
case immediate
/// Wait one full `interval` before the first fire.
case afterInterval
public var isImmediate: Bool { self == .immediate }
}
public final class STTimer {
public typealias STTimerHandler = (STTimer) -> Void
private enum State {
case idle
case running
case paused
}
private var timer: DispatchSourceTimer?
private let queue: DispatchQueue
private let interval: TimeInterval
private let leeway: DispatchTimeInterval
private var state: State = .idle
private var handler: STTimerHandler?
public private(set) var fireCount: Int = 0
/// - Parameters:
/// - interval: 触发间隔(秒)
/// - leeway: 系统允许的最大延迟抖动,默认 10ms(越小精度越高,越耗电)
/// - queue: 内部 GCD 队列,默认创建独立串行队列
public init(interval: TimeInterval, leeway: DispatchTimeInterval = .milliseconds(10), queue: DispatchQueue = DispatchQueue(label: "com.st.timer.queue", qos: .userInteractive)) {
precondition(interval > 0, "STTimer: interval 必须大于 0")
self.interval = interval
self.leeway = leeway
self.queue = queue
}
deinit {
if self.state == .paused {
self.timer?.resume()
}
self.timer?.cancel()
}
/// 启动定时器
/// - Parameters:
/// - firstFire: 首次触发时机,默认 `.afterInterval`(等待第一个 interval 后触发)
/// - handler: 触发回调,在主线程执行,参数为定时器自身
public func start(firstFire: STTimerFirstFire = .afterInterval, handler: @escaping STTimerHandler) {
self.queue.async { [weak self] in
guard let self, self.state == .idle else { return }
self.handler = handler
self.fireCount = 0
self.createAndResumeTimer(firstFire: firstFire)
self.state = .running
}
}
/// 暂停(可通过 resume 恢复)
public func pause() {
self.queue.async { [weak self] in
guard let self, self.state == .running else { return }
self.timer?.suspend()
self.state = .paused
}
}
/// 恢复已暂停的定时器
public func resume() {
self.queue.async { [weak self] in
guard let self, self.state == .paused else { return }
self.timer?.resume()
self.state = .running
}
}
/// 停止并重置
public func stop() {
self.queue.async { [weak self] in
self?.internalStop()
}
}
public var isRunning: Bool {
self.queue.sync { state == .running }
}
public var isPaused: Bool {
self.queue.sync { state == .paused }
}
private func createAndResumeTimer(firstFire: STTimerFirstFire) {
self.timer?.cancel()
let source = DispatchSource.makeTimerSource(flags: [], queue: self.queue)
let deadline: DispatchTime = firstFire.isImmediate ? .now() : .now() + self.interval
source.schedule(deadline: deadline, repeating: .milliseconds(Int(self.interval * 1000)), leeway: self.leeway)
source.setEventHandler { [weak self] in
self?.handleFire()
}
self.timer = source
source.resume()
}
private func handleFire() {
guard self.state == .running else { return }
self.fireCount += 1
let handler = self.handler
DispatchQueue.main.async { [weak self] in
guard let self else { return }
handler?(self)
}
}
private func internalStop() {
if self.state == .paused {
self.timer?.resume()
}
self.timer?.cancel()
self.timer = nil
self.state = .idle
self.fireCount = 0
}
}