Skip to content

Commit 65449ee

Browse files
committed
wip
1 parent 34c40c9 commit 65449ee

4 files changed

Lines changed: 178 additions & 0 deletions

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

Package.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// swift-tools-version: 6.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "AsyncTimer",
8+
platforms: [
9+
.macOS(.v10_15),
10+
.iOS(.v13),
11+
.tvOS(.v13),
12+
.watchOS(.v6),
13+
.macCatalyst(.v13),
14+
.visionOS(.v1),
15+
],
16+
17+
products: [
18+
// Products define the executables and libraries a package produces, making them visible to other packages.
19+
.library(
20+
name: "AsyncTimer",
21+
targets: ["AsyncTimer"]
22+
),
23+
],
24+
targets: [
25+
// Targets are the basic building blocks of a package, defining a module or a test suite.
26+
// Targets can depend on other targets in this package and products from dependencies.
27+
.target(
28+
name: "AsyncTimer"),
29+
.testTarget(
30+
name: "AsyncTimerTests",
31+
dependencies: ["AsyncTimer"]
32+
),
33+
],
34+
swiftLanguageModes: [.v6]
35+
)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//
2+
// AsyncTimer.swift
3+
// AsyncTimer
4+
//
5+
// Created by CodingIran on 2025/5/20.
6+
//
7+
8+
import Foundation
9+
10+
// Enforce minimum Swift version for all platforms and build systems.
11+
#if swift(<5.9)
12+
#error("AsyncTimer doesn't support Swift versions below 5.9")
13+
#endif
14+
15+
public enum AsyncTimerInfo: Sendable {
16+
/// Current AsyncTimer version.
17+
public static let version = "0.0.1"
18+
}
19+
20+
/// A simple repeating timer that runs a task at a specified interval.
21+
final actor AsyncTimer {
22+
// MARK: - Properties
23+
24+
/// Repeating task handler
25+
typealias RepeatHandler = @Sendable () async -> Void
26+
27+
/// Cancel handler
28+
typealias CancelHandler = @Sendable () async -> Void
29+
30+
/// The task that runs the repeating timer.
31+
private var task: Task<Void, Error>?
32+
33+
/// The interval at which the timer fires.
34+
private var interval: TimeInterval
35+
36+
/// The priority of the task.
37+
private let priority: TaskPriority
38+
39+
/// Whether the timer should repeat.
40+
private let repeating: Bool
41+
42+
/// Whether the timer should fire immediately upon starting.
43+
private let firesImmediately: Bool
44+
45+
/// This handler is called when the timer fires.
46+
private var handler: RepeatHandler
47+
48+
/// This handler is called when the timer is cancelled.
49+
private var cancelHandler: CancelHandler?
50+
51+
/// Initializes a new `AsyncRepeatingTimer` instance.
52+
/// - Parameters:
53+
/// - interval: The interval at which the timer fires.
54+
/// - priority: The priority of the task. Default is `.medium`.
55+
/// - repeating: Whether the timer should repeat. Default is `false`.
56+
/// - firesImmediately: Whether the timer should fire immediately upon starting. Default is `true`. It is only effective when `repeating` is `true`.
57+
/// - handler: The handler that is called when the timer fires.
58+
/// - cancelHandler: The handler that is called when the timer is cancelled.
59+
/// - Returns: A new `AsyncRepeatingTimer` instance.
60+
init(interval: TimeInterval,
61+
priority: TaskPriority = .medium,
62+
repeating: Bool = false,
63+
firesImmediately: Bool = true,
64+
handler: @escaping RepeatHandler,
65+
cancelHandler: CancelHandler? = nil)
66+
{
67+
self.interval = interval
68+
self.priority = priority
69+
self.firesImmediately = firesImmediately
70+
self.repeating = repeating
71+
self.handler = handler
72+
self.cancelHandler = cancelHandler
73+
}
74+
75+
/// Starts the timer.
76+
/// - Note: If the timer is already running, it will be stopped and restarted.
77+
func start() {
78+
stop()
79+
task = Task(priority: priority) {
80+
guard repeating else {
81+
// one-time timer
82+
try await Self.sleep(interval)
83+
await self.handler()
84+
return
85+
}
86+
87+
// repeating timer
88+
if !firesImmediately {
89+
try await Self.sleep(interval)
90+
}
91+
do {
92+
while !Task.isCancelled {
93+
await self.handler()
94+
try await Self.sleep(interval)
95+
}
96+
} catch is CancellationError {
97+
await cancelHandler?()
98+
} catch {}
99+
}
100+
}
101+
102+
/// Stops the timer.
103+
func stop() {
104+
guard let task else { return }
105+
task.cancel()
106+
self.task = nil
107+
}
108+
109+
/// Restarts the timer.
110+
func restart() {
111+
stop()
112+
start()
113+
}
114+
115+
/// Modifies the interval of the timer.
116+
/// - Parameter newInterval: The new interval at which the timer should fire.
117+
/// - Note: This will also restart the timer.
118+
func setInterval(_ newInterval: TimeInterval) {
119+
interval = newInterval
120+
restart()
121+
}
122+
}
123+
124+
extension AsyncTimer {
125+
/// Sleep for the specified interval.
126+
static func sleep(_ interval: TimeInterval) async throws {
127+
try await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))
128+
}
129+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Testing
2+
@testable import AsyncTimer
3+
4+
@Test func example() async throws {
5+
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
6+
}

0 commit comments

Comments
 (0)