Skip to content

Commit 910a2eb

Browse files
muukiiclaude
andauthored
Enhance README with comprehensive documentation (#12)
## Summary - Transformed basic README into comprehensive documentation with professional structure - Added clear value proposition explaining why TaskManager is needed - Included practical examples demonstrating real-world usage patterns ## Changes - ✨ Professional header with badges, tagline, and clear introduction - 📦 Installation instructions for Swift Package Manager with requirements - 📚 Core concepts documentation (TaskKey, Execution Modes, Task Isolation) - 💡 Basic usage examples with simple and real-world scenarios - 🎨 SwiftUI integration documentation with property wrapper example - 🔥 Advanced usage patterns (dynamic keys, batch operations, state management) - 🏗️ Architecture patterns (Repository and ViewModel examples) - 📖 Complete API reference for all public APIs - 🤝 Contributing section and acknowledgments ## Test plan - [x] Review README formatting and ensure all code examples are correct - [x] Verify all links work properly - [x] Check that examples align with actual API implementation - [ ] Community feedback on documentation clarity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <[email protected]>
1 parent bb1a916 commit 910a2eb

File tree

1 file changed

+330
-24
lines changed

1 file changed

+330
-24
lines changed

README.md

Lines changed: 330 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,350 @@
1-
# TaskManager - Swift Concurrency
1+
# 🎯 TaskManager for Swift Concurrency
22

3-
## Overview
3+
> Elegant task orchestration for Swift apps - Control concurrent operations with precision
44
5-
swift-concurrency supports structured concurrency but it also supports unstructured concurrency using Task API.
6-
Task API runs immediately its work item. Although using closure can make deferred tasks.
5+
[![Swift](https://img.shields.io/badge/Swift-6.0-orange.svg)](https://swift.org)
6+
[![Platform](https://img.shields.io/badge/platform-iOS%2014%2B%20%7C%20macOS%2011%2B%20%7C%20tvOS%2016%2B%20%7C%20watchOS%2010%2B-lightgrey.svg)](https://swift.org)
7+
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
78

8-
TaskManager accepts work items to run in a serial queue isolated by the key.
9-
Passing key and mode (drop current items / wait in current items)
9+
## Introduction
1010

11-
## Usage
11+
TaskManager is a powerful Swift library that brings order to chaos in asynchronous programming. While Swift's structured concurrency is excellent, unstructured tasks created with `Task { }` run immediately and can lead to race conditions, redundant operations, and unpredictable behavior.
12+
13+
TaskManager solves this by providing:
14+
- **Task isolation by key** - Group related operations together
15+
- **Execution control** - Choose whether to cancel existing tasks or queue new ones
16+
- **SwiftUI integration** - First-class support for UI-driven async operations
17+
- **Actor-based safety** - Thread-safe by design using Swift actors
18+
19+
## 🚀 Installation
20+
21+
### Swift Package Manager
22+
23+
Add TaskManager to your `Package.swift`:
24+
25+
```swift
26+
dependencies: [
27+
.package(url: "https://github.com/muukii/swift-concurrency-task-manager.git", from: "1.0.0")
28+
]
29+
```
30+
31+
Or add it through Xcode:
32+
1. File → Add Package Dependencies
33+
2. Enter the repository URL
34+
3. Click "Add Package"
35+
36+
## 📋 Requirements
37+
38+
- Swift 6.0+
39+
- iOS 14.0+ / macOS 11.0+ / tvOS 16.0+ / watchOS 10.0+
40+
- Xcode 15.0+
41+
42+
## 🎓 Core Concepts
43+
44+
### TaskKey
45+
46+
A `TaskKey` is a unique identifier that groups related operations. Tasks with the same key are managed together, allowing you to control their execution behavior.
47+
48+
```swift
49+
// Type-based keys for strong typing
50+
enum UserOperations: TaskKeyType {}
51+
let key = TaskKey(UserOperations.self)
52+
53+
// String-based keys for simplicity
54+
let key: TaskKey = "user-fetch"
55+
56+
// Dynamic keys with combined values
57+
let key = TaskKey(UserOperations.self).combined(userID)
58+
59+
// Unique keys for one-off operations
60+
let key = TaskKey.distinct()
61+
62+
// Code location-based keys
63+
let key = TaskKey.code() // Uses file:line:column
64+
```
65+
66+
### Execution Modes
67+
68+
TaskManager offers two execution modes:
69+
70+
- **`.dropCurrent`** - Cancels any running task with the same key before starting the new one
71+
- **`.waitInCurrent`** - Queues the new task to run after existing tasks complete
72+
73+
### Task Isolation
74+
75+
Tasks are isolated by their keys, meaning operations with different keys run concurrently, while operations with the same key are managed according to their mode.
76+
77+
## 💡 Basic Usage
78+
79+
### Simple Task Management
80+
81+
```swift
82+
let manager = TaskManagerActor()
83+
84+
// Drop any existing user fetch and start a new one
85+
let task = await manager.task(
86+
key: TaskKey("user-fetch"),
87+
mode: .dropCurrent
88+
) {
89+
let user = try await api.fetchUser()
90+
return user
91+
}
92+
93+
// Wait for the result
94+
let user = try await task.value
95+
```
96+
97+
### Real-World Example: Search-as-you-type
98+
99+
```swift
100+
class SearchViewModel {
101+
let taskManager = TaskManagerActor()
102+
103+
func search(query: String) async {
104+
// Cancel previous search when user types
105+
await taskManager.task(
106+
key: TaskKey("search"),
107+
mode: .dropCurrent
108+
) {
109+
// Debounce
110+
try await Task.sleep(for: .milliseconds(300))
111+
112+
let results = try await api.search(query)
113+
await MainActor.run {
114+
self.searchResults = results
115+
}
116+
}
117+
}
118+
}
119+
```
120+
121+
## 🎨 SwiftUI Integration
122+
123+
TaskManager provides a property wrapper for seamless SwiftUI integration:
124+
125+
```swift
126+
struct UserProfileView: View {
127+
@TaskManager var taskManager
128+
@State private var isLoading = false
129+
@State private var user: User?
130+
131+
var body: some View {
132+
VStack {
133+
if isLoading {
134+
ProgressView()
135+
} else if let user {
136+
Text(user.name)
137+
}
138+
139+
Button("Refresh") {
140+
taskManager.task(
141+
isRunning: $isLoading,
142+
key: TaskKey("fetch-user"),
143+
mode: .dropCurrent
144+
) {
145+
user = try await api.fetchCurrentUser()
146+
}
147+
}
148+
}
149+
}
150+
}
151+
```
152+
153+
## 🔥 Advanced Usage
154+
155+
### Dynamic Task Keys
156+
157+
Create sophisticated task isolation strategies:
12158

13159
```swift
160+
// Isolate tasks per user
161+
func updateUserStatus(userID: String, isFavorite: Bool) async {
162+
let key = TaskKey(UserOperations.self).combined(userID)
163+
164+
await taskManager.task(key: key, mode: .dropCurrent) {
165+
try await api.updateUserStatus(userID, favorite: isFavorite)
166+
}
167+
}
168+
169+
// Isolate tasks per resource and operation
170+
func downloadImage(url: URL, size: ImageSize) async {
171+
let key = TaskKey("image-download")
172+
.combined(url.absoluteString)
173+
.combined(size.rawValue)
174+
175+
await taskManager.task(key: key, mode: .waitInCurrent) {
176+
try await imageLoader.download(url, size: size)
177+
}
178+
}
179+
```
14180

15-
enum MyTask: TaskKeyType {}
181+
### Batch Operations
16182

17-
let manager = TaskManager()
183+
Execute multiple operations efficiently:
18184

19-
// this `await` is just for appending task item since TaskManager is Actor.
20-
let ref = await manager.task(key: .init(MyTask.self), mode: .dropCurrent) {
21-
// work
185+
```swift
186+
await taskManager.batch { manager in
187+
// These run concurrently (different keys)
188+
manager.task(key: TaskKey("fetch-user"), mode: .dropCurrent) {
189+
userData = try await api.fetchUser()
190+
}
191+
192+
manager.task(key: TaskKey("fetch-posts"), mode: .dropCurrent) {
193+
posts = try await api.fetchPosts()
194+
}
195+
196+
manager.task(key: TaskKey("fetch-settings"), mode: .dropCurrent) {
197+
settings = try await api.fetchSettings()
198+
}
22199
}
200+
```
201+
202+
### Task State Management
203+
204+
Control task execution flow:
205+
206+
```swift
207+
let manager = TaskManagerActor()
208+
209+
// Pause all task execution
210+
await manager.setIsRunning(false)
23211

24-
// to wait the completion of the task, use `ref.value`.
25-
await ref.value
212+
// Tasks will queue but not execute
213+
await manager.task(key: TaskKey("operation"), mode: .waitInCurrent) {
214+
// This won't run until isRunning is true
215+
}
216+
217+
// Resume execution
218+
await manager.setIsRunning(true)
219+
220+
// Check if a specific task is running
221+
let isRunning = await manager.isRunning(for: TaskKey("operation"))
26222
```
27223

28-
## Use cases
224+
### Error Handling
29225

30-
**Toggle user status**
31-
Picture some social service - toggling the user status like favorite or not.
32-
For the client, it needs to dispatch asynchronous requests for them.
33-
In some case final user state would be different from what the client expected if the client dispatched multiple requests for the toggle - like the user tapped the update button continuously.
226+
TaskManager preserves Swift's native error handling:
34227

35228
```swift
36-
enum SomeRequestKey: TaskKeyType {}
229+
do {
230+
let result = try await taskManager.task(
231+
key: TaskKey("risky-operation"),
232+
mode: .dropCurrent
233+
) {
234+
try await riskyOperation()
235+
}.value
236+
} catch is CancellationError {
237+
print("Task was cancelled")
238+
} catch {
239+
print("Task failed: \(error)")
240+
}
241+
```
242+
243+
## 🏗️ Architecture Patterns
244+
245+
### Repository Pattern
246+
247+
```swift
248+
class UserRepository {
249+
private let taskManager = TaskManagerActor()
250+
251+
func fetchUser(id: String, forceRefresh: Bool = false) async throws -> User {
252+
let key = TaskKey(UserOperations.self).combined(id)
253+
let mode: TaskManagerActor.Mode = forceRefresh ? .dropCurrent : .waitInCurrent
254+
255+
return try await taskManager.task(key: key, mode: mode) {
256+
// Check cache first
257+
if !forceRefresh, let cached = await cache.get(id) {
258+
return cached
259+
}
260+
261+
// Fetch from network
262+
let user = try await api.fetchUser(id)
263+
await cache.set(user, for: id)
264+
return user
265+
}.value
266+
}
267+
}
268+
```
37269

38-
let key = TaskKey(SomeRequestKey.self).combined(targetUserID)
270+
### ViewModel Pattern
39271

40-
await taskManager.task(key: key, mode: .dropCurrent) { ... }
272+
```swift
273+
@Observable
274+
class ProductListViewModel {
275+
private let taskManager = TaskManagerActor()
276+
var products: [Product] = []
277+
var isLoading = false
278+
279+
func loadProducts(category: String? = nil) {
280+
Task {
281+
await taskManager.task(
282+
key: TaskKey("load-products").combined(category ?? "all"),
283+
mode: .dropCurrent
284+
) {
285+
await MainActor.run { self.isLoading = true }
286+
defer { Task { @MainActor in self.isLoading = false } }
287+
288+
let products = try await api.fetchProducts(category: category)
289+
await MainActor.run {
290+
self.products = products
291+
}
292+
}
293+
}
294+
}
295+
}
41296
```
42297

43-
To avoid that case, the client stops the current request before starting a new request in the queue.
44-
The above example binds the requests with a typed request key and target user identifier, that makes a queue for that.
298+
## 📚 API Reference
299+
300+
### TaskManagerActor
301+
302+
The main actor that manages task execution.
303+
304+
#### Methods
305+
306+
- `task(label:key:mode:priority:operation:)` - Submit a task for execution
307+
- `taskDetached(label:key:mode:priority:operation:)` - Submit a detached task
308+
- `batch(_:)` - Execute multiple operations in a batch
309+
- `setIsRunning(_:)` - Control task execution state
310+
- `isRunning(for:)` - Check if a task is running for a given key
311+
- `cancelAll()` - Cancel all managed tasks
312+
313+
### TaskKey
314+
315+
Identifies and groups related tasks.
316+
317+
#### Initialization
318+
319+
- `init(_:TaskKeyType)` - Create from a type
320+
- `init(_:String)` - Create from a string
321+
- `init(_:Int)` - Create from an integer
322+
- `init(_:Hashable & Sendable)` - Create from any hashable value
323+
324+
#### Methods
325+
326+
- `combined(_:)` - Combine with another key
327+
- `static func distinct()` - Create a unique key
328+
- `static func code()` - Create a key from source location
329+
330+
### SwiftUI Components
331+
332+
#### @TaskManager Property Wrapper
333+
334+
Provides TaskManager functionality in SwiftUI views with automatic lifecycle management.
335+
336+
#### TaskManagerActorWrapper
337+
338+
SwiftUI-friendly wrapper with `isRunning` binding support.
339+
340+
## 🤝 Contributing
341+
342+
Contributions are welcome! Please feel free to submit a Pull Request.
343+
344+
## 📄 License
345+
346+
TaskManager is available under the Apache 2.0 license. See the [LICENSE](LICENSE) file for more info.
347+
348+
## 🙏 Acknowledgments
349+
350+
Built with ❤️ using Swift's modern concurrency features and inspired by the need for better async task control in real-world applications.

0 commit comments

Comments
 (0)