AwaitlessKit
is a collection of Swift macros and utilities that lets you commit unspeakable asynchronous sins - like calling async
functions from a nonasync
context without awaiting them properly.
In other words, it simplifies the migration to async/await
code from Structured Concurrency using some loopholes. It likely performs better than your ad hoc hacks.
Remember! This framework deliberately sidesteps type-safety guardrails. Though it leverages battle-tested patterns, do your due diligence on edge cases before using it in production.
From Swift's Evolution Improving the approachability of data-race safety:
Introducing async/await into an existing codebase is difficult to do incrementally, because the language does not provide tools to bridge between synchronous and asynchronous code. Sometimes programmers can kick off a new unstructured task to perform the async work, and other times that is not suitable, e.g. because the synchronous code needs a result from the async operation. It’s also not always possible to propagate async throughout callers, because the function signature might be declared in a library dependency that you don’t own.
AwaitlessKit
aims to bridge that gap and simplify the adoption of async/await
.
While AwaitlessKit
should work with Xcode 15 and Swift 5.x, it's less tested, and support is considered experimental.
For the best experience, Xcode 16 with Swift 6.0 compiler is highly recommended. Your project can still be in Swift 5.x.
Note: not yet available in Swift 5.x / Xcode 15
A freestanding expression macro that executes async
code blocks synchronously.
Particularly valuable when interfacing with third-party APIs or legacy systems where asynchronous context isn't available, but you need to integrate with your async
implementations.
An attached macro that automatically generates synchronous counterparts for your async
functions.
Ideal for API design patterns requiring both synchronous and asynchronous interfaces, eliminating the need to manually maintain duplicate implementations and providing simple deprecation for the future.
An attached property macro that implements a serial dispatch queue to provide thread-safe access to nonisolated(unsafe)
properties.
Offers runtime concurrency protection when compile-time isolation isn't feasible, effectively preventing data races through a property protected with DispatchQueue
.
Allows to run async
code in noasync
context.
Powers @Awaitless()
and #awaitless()
macros.
More details in Calling Swift Concurrency async code synchronously in Swift
import AwaitlessKit
final class AwaitlessExample: Sendable {
// Basic usage - generates a sync version with same name
@Awaitless
func fetchData() async throws -> Data {
// ...async implementation
}
func onlyAsyncFetchData() async throws -> Data {
// ...async implementation
}
// With deprecation warning
@Awaitless(.deprecated("Synchronous API will be phased out, migrate to async version"))
func processItems() async throws -> [String] {
// ...async implementation
}
// Make sync version unavailable
@Awaitless(.unavailable("Synchronous API has been removed, use async version"))
func loadResources() async throws -> [Resource] {
// ...async implementation
}
// Custom prefix for generated function
@Awaitless(prefix: "sync_")
func loadConfig() async -> Config {
// ...async implementation
}
public func run() {
// Call generated sync versions
let data = try fetchData()
let items = try processItems() // Shows deprecation warning
let config = sync_loadConfig()
// Or use the freestanding macro
let result = try #awaitless(try onlyAsyncFetchData())
}
}
final class IsolatedSafeExample: Sendable {
// (1a) Thread-safe wrapper for unsafe property access
@IsolatedSafe
private nonisolated(unsafe) var _unsafeStrings: [String] = ["Hello", "World"]
// (2a) Thread-safe wrapper with write access
@IsolatedSafe(writable: true)
private nonisolated(unsafe) var _unsafeProcessCount: Int = 0
public func run() {
// (1b) Safe access to "_unsafeStrings" through generated thread-safe "string" property
strings.append("and")
strings.append("universe")
// (2b) Safe access to "_unsafeProcessCount" through generated thread-safe "processCount" property
processCount += 1
}
}
Add AwaitlessKit
to your Package.swift
:
dependencies: [
.package(url: "https://github.com/yourusername/AwaitlessKit.git", from: "5.0.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: ["AwaitlessKit"]
)
]
The SampleApp
included in the repo is just a simple demo app showing these features in action.
- Wade Tregaskis for
Task.noasync
from Calling Swift Concurrency async code synchronously in Swift - Zed Editor for its powerful GenAI support
- Anthropic for its 3.7 and 4.0 models