Skip to content

Commit 4b65be8

Browse files
committed
init
0 parents  commit 4b65be8

9 files changed

Lines changed: 328 additions & 0 deletions

File tree

.github/workflows/CI.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# This workflow will build a Swift project
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
3+
4+
name: CI
5+
6+
on:
7+
push:
8+
branches: [ "main" ]
9+
pull_request:
10+
branches: [ "main" ]
11+
12+
jobs:
13+
build:
14+
15+
runs-on: macos-latest
16+
17+
steps:
18+
- uses: actions/checkout@v3
19+
- name: Build
20+
run: swift build -v
21+
- name: Run tests
22+
run: swift test -v

.github/workflows/docc.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: docc
2+
on:
3+
push:
4+
branches: ["main"]
5+
permissions:
6+
contents: read
7+
pages: write
8+
id-token: write
9+
concurrency:
10+
group: "pages"
11+
cancel-in-progress: true
12+
jobs:
13+
pages:
14+
environment:
15+
name: github-pages
16+
url: ${{ steps.deployment.outputs.page_url }}
17+
runs-on: macos-12
18+
steps:
19+
- name: git checkout
20+
uses: actions/checkout@v3
21+
- name: docbuild
22+
run: |
23+
xcodebuild docbuild -scheme TaskManager \
24+
-derivedDataPath /tmp/docbuild \
25+
-destination 'generic/platform=iOS';
26+
$(xcrun --find docc) process-archive \
27+
transform-for-static-hosting /tmp/docbuild/Build/Products/Debug-iphoneos/TaskManager.doccarchive \
28+
--hosting-base-path TaskManager \
29+
--output-path docs;
30+
echo "<script>window.location.href += \"/documentation/taskmanager\"</script>" > docs/index.html;
31+
- name: artifacts
32+
uses: actions/upload-pages-artifact@v1
33+
with:
34+
path: 'docs'
35+
- name: deploy
36+
id: deployment
37+
uses: actions/deploy-pages@v1

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/config/registries.json
8+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9+
.netrc
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

Package.resolved

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// swift-tools-version: 5.7
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: "TaskManager",
8+
platforms: [
9+
.iOS(.v14),
10+
.macOS(.v11),
11+
.watchOS(.v7),
12+
.tvOS(.v14)
13+
],
14+
products: [
15+
// Products define the executables and libraries a package produces, and make them visible to other packages.
16+
.library(
17+
name: "TaskManager",
18+
targets: ["TaskManager"]
19+
)
20+
],
21+
dependencies: [
22+
// Dependencies declare other packages that this package depends on.
23+
.package(url: "https://github.com/0xOpenBytes/Cache", from: "0.1.0")
24+
],
25+
targets: [
26+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
27+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
28+
.target(
29+
name: "TaskManager",
30+
dependencies: [
31+
"Cache"
32+
]
33+
),
34+
.testTarget(
35+
name: "TaskManagerTests",
36+
dependencies: ["TaskManager"]
37+
)
38+
]
39+
)

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# TaskManager
2+
3+
`TaskManager` is a cache for asynchronous tasks in Swift. It allows you to manage the execution of tasks with a simple API that lets you add, cancel, and wait for tasks.
4+
5+
## Usage
6+
7+
### Creating a TaskManager
8+
9+
To create a `TaskManager`, simply instantiate it with a key type that conforms to `Hashable`. It is recommended to use an enum as the key type to avoid typos and ensure type safety:
10+
11+
```swift
12+
enum TaskKey: Hashable {
13+
case fetchUserData(userId: String)
14+
case downloadFile(url: URL)
15+
}
16+
17+
let taskManager = TaskManager<TaskKey>()
18+
```
19+
20+
### Adding a Task
21+
22+
To add a new task to the `TaskManager`, use the `task(key:priority:operation:)` method. The `key` parameter is the unique identifier for the task, `priority` is the priority of the task (optional), and `operation` is the async operation to be performed:
23+
24+
```swift
25+
taskManager.task(key: .fetchUserData(userId: "1234"), priority: .high) {
26+
// Perform async operation to fetch user data
27+
return userData
28+
}
29+
```
30+
31+
### Retrieving a Task Result
32+
33+
To retrieve the result of a task, use the `value(for:as:)` method. The `for` parameter is the key of the task, and `as` is the type of the result you expect to receive:
34+
35+
```swift
36+
let userData = try await taskManager.value(for: .fetchUserData(userId: "1234"), as: UserData.self)
37+
```
38+
39+
or
40+
41+
```swift
42+
let userData: UserData = try await taskManager.value(for: .fetchUserData(userId: "1234"))
43+
```
44+
45+
### Canceling a Task
46+
47+
To cancel a task, use the `cancel(key:)` method:
48+
49+
```swift
50+
taskManager.cancel(key: .fetchUserData(userId: "1234"))
51+
```
52+
53+
### Canceling All Tasks
54+
55+
To cancel all tasks, use the `cancelAll()` method:
56+
57+
```swift
58+
taskManager.cancelAll()
59+
```
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import c
2+
import Cache
3+
4+
/// A `TaskManager` is a cache for asynchronous tasks.
5+
open class TaskManager<Key: Hashable>: Cache<Key, Task<Sendable, Error>> {
6+
/// Initializes a new instance of `TaskManager`.
7+
///
8+
/// - Parameter initialValues: A dictionary of initial key-value pairs to be added to the cache.
9+
public required init(initialValues: [Key: Value] = [:]) {
10+
super.init(initialValues: initialValues)
11+
}
12+
13+
deinit {
14+
cancelAll()
15+
}
16+
17+
/// Cancels a task for a given key.
18+
///
19+
/// - Parameter key: The key for the task to be canceled.
20+
open func cancel(key: Key) {
21+
get(key)?.cancel()
22+
remove(key)
23+
}
24+
25+
/// Cancels all tasks in the cache.
26+
open func cancelAll() {
27+
let allTasks = allValues
28+
29+
allTasks.forEach { managedTask in
30+
cancel(key: managedTask.key)
31+
}
32+
}
33+
34+
/// Adds a new task to the cache for a given key.
35+
///
36+
/// - Parameters:
37+
/// - key: The key for the new task.
38+
/// - priority: The priority of the task. Default value is `nil`.
39+
/// - operation: The operation to be performed by the task.
40+
open func task(
41+
key: Key,
42+
priority: TaskPriority? = nil,
43+
operation: @escaping () async throws -> Sendable
44+
) {
45+
cancel(key: key)
46+
47+
let managedTask = Task(priority: priority) {
48+
try await operation()
49+
}
50+
51+
set(value: managedTask, forKey: key)
52+
}
53+
54+
/// Wait for the task with the given key to complete, but ignore its result.
55+
///
56+
/// - Parameter key: The key of the task to wait for.
57+
open func wait(for key: Key) async throws {
58+
let _ = try await value(for: key, as: Sendable.self)
59+
}
60+
61+
/// Wait for the result of the task with the given key.
62+
///
63+
/// - Parameters:
64+
/// - key: The key of the task to get the result of.
65+
/// - type: The expected type of the result.
66+
///
67+
/// - Returns: The result of the task, if it has completed and its result is of the expected type.
68+
///
69+
/// - Throws: An `InvalidTypeError` if the task result is not of the expected type.
70+
open func value<Success: Sendable>(
71+
for key: Key,
72+
as type: Success.Type = Success.self
73+
) async throws -> Success {
74+
let managedTask = try resolve(key)
75+
76+
let value = try await managedTask.value
77+
78+
guard let success = value as? Success else {
79+
throw c.InvalidTypeError(expectedType: type, actualValue: value)
80+
}
81+
82+
return success
83+
}
84+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import XCTest
2+
@testable import TaskManager
3+
4+
final class TaskManagerTests: XCTestCase {
5+
func testValue() async throws {
6+
enum TaskKey {
7+
case getPi
8+
}
9+
10+
let taskManager = TaskManager<TaskKey>()
11+
12+
taskManager.task(key: .getPi) {
13+
Double.pi
14+
}
15+
16+
let result: Double = try await taskManager.value(for: .getPi)
17+
18+
XCTAssertEqual(result, Double.pi)
19+
}
20+
21+
func testCancel() async throws {
22+
enum TaskKey {
23+
case loop
24+
}
25+
26+
let taskManager = TaskManager<TaskKey>()
27+
28+
taskManager.task(key: .loop) {
29+
while true { /* ... */ }
30+
}
31+
32+
taskManager.cancel(key: .loop)
33+
34+
try await taskManager.wait(for: .loop)
35+
36+
print("Infinite Loop Task was cancelled")
37+
}
38+
}

0 commit comments

Comments
 (0)