Skip to content

junebash/swift-task-store

Repository files navigation

TaskStore

A Swift 6 library for managing concurrent async tasks by key, with configurable behavior for duplicate requests.

Background

Installation

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/junebash/swift-task-store.git", from: "0.1.0")
]

Usage

Note: TaskStore must be used from an isolated context (e.g., @MainActor or a custom actor). The Swift 6.2 compiler enforces this requirement.

import TaskStore

@Observable
@MainActor
final class MyModel {
    let tasks = TaskStore<TaskKey>()

    enum TaskKey: Hashable {
        case fetchUser
        case saveDocument
    }

    func fetchUser() {
        // Inherits MainActor isolation
        tasks.addIsolatedTask(forKey: .fetchUser) {
            try? await api.fetchUser()
        }
    }

    func processInBackground() {
        // Runs on global concurrent executor
        tasks.addConcurrentTask(forKey: .processData) {
            await heavyComputation()
        }
    }

    var isFetching: Bool {
        tasks.taskIsRunning(forKey: .fetchUser)
    }
}

Duplicate Key Behavior

When adding a task for a key that's already running:

Behavior Description
.cancelPrevious(wait: false) Cancel previous, start new immediately (default)
.cancelPrevious(wait: true) Cancel previous, wait for it, then start new
.wait Wait for previous to complete, then start new
.runConcurrently Run both tasks simultaneously
.preferPrevious Keep existing task, ignore new request
// Search: cancel previous searches immediately
tasks.addConcurrentTask(forKey: .search, duplicateKeyBehavior: .cancelPrevious(wait: false)) {
    await performSearch(query)
}

// Save: wait for previous save to complete
tasks.addIsolatedTask(forKey: .save, duplicateKeyBehavior: .wait) {
    await saveDocument()
}

Task Naming

TaskStore supports optional task naming for debugging and instrumentation via the TaskNameProvider protocol:

// Use key description as task names
let store = TaskStore<TaskKey>(nameProvider: .keyDescription)

// Use a constant name
let store = TaskStore<TaskKey>(nameProvider: .constant("BackgroundTask"))

// Use a custom closure
let store = TaskStore<TaskKey>(nameProvider: .fromKey { key in
    "Task-\(key)"
})

// Compose with modifiers
let store = TaskStore<TaskKey>(
    nameProvider: .keyDescription
        .withPrefix("MyViewModel")
        .withIncrementingSuffix()
)
// Produces: "MyViewModel.fetchUser.0", "MyViewModel.fetchUser.1", etc.

The name can be accessed from within the task via Task.name.

Provider Description
.keyDescription Uses String(describing:) on the key
.constant("name") Returns the same name for all keys
.fromKey { ... } Uses a custom closure to generate names
Modifier Description
.withPrefix("prefix") Prepends a prefix (e.g., "App.fetchUser")
.withIncrementingSuffix() Appends incrementing numbers (e.g., "fetchUser.0")

API

// Add a task that runs on the global concurrent executor
@discardableResult
func addConcurrentTask(
    forKey key: Key,
    duplicateKeyBehavior: TaskStoreDuplicateKeyBehavior = .cancelPrevious(wait: false),
    priority: TaskPriority? = nil,
    operation: @escaping @Sendable () async -> Void
) -> Task<Void, Never>

// Add a task that inherits the caller's actor isolation
@discardableResult
func addIsolatedTask(
    forKey key: Key,
    duplicateKeyBehavior: TaskStoreDuplicateKeyBehavior = .cancelPrevious(wait: false),
    priority: TaskPriority? = nil,
    operation: @escaping () async -> Void
) -> Task<Void, Never>

// Add a task with immediate execution (Swift 6.2+, SE-0472)
// Runs synchronously until first suspension point, then continues asynchronously
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
@discardableResult
func addImmediateTask(
    forKey key: Key,
    duplicateKeyBehavior: TaskStoreDuplicateKeyBehavior = .cancelPrevious(wait: false),
    priority: TaskPriority? = nil,
    operation: @escaping () async -> Void
) -> Task<Void, Never>

// Cancel a task
func cancelTask(forKey key: Key)

// Cancel all tasks
func cancelAllTasks()

// Check if a task is running
func taskIsRunning(forKey key: Key) -> Bool

// Get the current task for a key
func currentTask(forKey key: Key) -> Task<Void, Never>?

// Running task info
var runningTaskCount: Int
var runningTaskKeys: Set<Key>

License

MIT

About

Manage unstructured async tasks with ease

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •